Skip to content

Instantly share code, notes, and snippets.

@b1naryth1ef
Last active December 20, 2015 01:09
Show Gist options
  • Save b1naryth1ef/6047079 to your computer and use it in GitHub Desktop.
Save b1naryth1ef/6047079 to your computer and use it in GitHub Desktop.
This is a script for managing Quake 3 (ioUrT) instances over stdin/stdout and Redis. I built this for use with a hosting platform I am working on. Due to the very unreliable system that RCON is based on, it simply wasn't an option to just use RCON. This system has the advantage of speed, write reliability, with the downside of write-read (respon…
import os, subprocess, redis, thread, json, time, daemon, re
red = redis.Redis("localhost", password=os.getenv("REDISPASS"))
class InterCom(object):
def __init__(self, id):
self.id = id
self.prefix = "server-%s-" % self.id
def getCvar(self, i):
def conf(i):
res = re.findall('".*?" is:"(.*?)\^7"', i)
if len(res):
return True
return False
i = self.get(i, need=conf)
if not i: return None
res = re.findall('".*?" is:"(.*?)\^7"', i[0]['msg'])
if len(res):
return res[0]
def get(self, cmd, lines=1, end=None, need=None):
"""
This is complicated as dicks, and probablly not perfect.
It's entirely Quake3's fault for not offering decent support for a tool
like rcon. I'm gonna fucking make sure UrTHD has decent rcon/socket support.
GODDAMNIT!
"""
i = 0 # Thread Retries
value = [] # We need an object so it can be set globally, list/dict works
running = []
def resp(a): # Function to pass to readLoop
if need:
if need(a['msg']):
value.append(a)
running.append(False)
return False
return True
value.append(a)
if len(value) >= lines:
running.append(False)
return False
if end(a['msg']):
running.append(False)
return False
return True
thread.start_new_thread(self.readLoop, (resp,))
self.cmd(cmd)
while not len(running):
time.sleep(0.05)
i += 1
if i > 25: return None
return value
def readLoop(self, f, *args):
sub = red.pubsub()
sub.subscribe(self.prefix+"read")
looping = True
for l in sub.listen():
if l['type'] == "message":
msg = json.loads(l['data'])
looping = (f(msg, *args) != False)
if not looping:
sub.unsubscribe(self.prefix+"read")
return True
def quit(self):
red.rpush(self.prefix+"write", json.dumps({"action": "quit"}))
def restart(self):
red.rpush(self.prefix+"write", json.dumps({"action": "restart"}))
def cmd(self, cmd):
red.rpush(self.prefix+"write", json.dumps({
"action": "write",
"content": cmd
}))
class Process(object):
def __init__(self, id, exe, config="server.cfg", port=27960):
self.id = id
self.exe = exe
self.config = config
self.port = port
self.running = False
self.proc = None
self.prefix = "server-%s-" % self.id
def start(self):
if self.running: return
cmd = "%s +set net_port %s +exec %s +set dedicated 2" % (self.exe, self.port, self.config)
self.proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self.running = True
thread.start_new_thread(self.writeToThread, ())
thread.start_new_thread(self.readFromThread, ())
self.proc.wait()
self.running = False
return
def stop(self, new=False):
if not self.running: return
self.proc.kill()
def push(self, t, m):
red.publish(self.prefix+"read", json.dumps({
"type": t,
"time": time.time(),
"sid": self.id,
"msg": m}))
# Write stuff into our process
def writeToThread(self):
while self.running:
msg = red.blpop(self.prefix+"write")
try:
msg = json.loads(msg[1])
if msg['action'] == "write":
self.proc.stdin.write(msg['content']+"\n")
elif msg['action'] == "quit":
self.stop()
elif msg['action'] == "restart":
self.stop(new=True)
except Exception, e:
print "Error loading message %s (%s)" % (str(msg), e)
continue
# Read stuff from our process
def readFromThread(self):
while self.running:
line = self.proc.stdout.readline().rstrip()
print line
self.push("raw", line)
self.push("action", "exit")
if __name__ == "__main__":
# with daemon.DaemonContext():
p = Process(1, "~/Downloads/UrbanTerror42/Quake3-UrT-Ded.x86_64")
p.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment