Hard to maintain that linux_server under UNIX? Process manager
#1
Hi all,

I guess all those nasty shell scripts and screen`ing and other unclear stuff just get your time wasted if all you want is just to run some servers, stop and restart them... and if you're not there to restart it, sheesh...

I spent this evening coding this script up:
Code:
#!/usr/bin/env python2.6 """ Process manager by Drakas Under zlib license. Revision 1 Create a file nmpx.cfg.py: config = { "curses": True, "procs": [ {'name':'Public Server 1','dir':'/home/user/AssaultCube_v1.0.4/','autorestart':True, 'args':['bash','-c','./bin_unix/linux_server -mlocalhost -f1234 -nPS1 -Nps1']}, {'name':'Public Server 2','dir':'/home/user/AssaultCube_v1.0.4/','autorestart':False, 'args':['bash','-c','./bin_unix/linux_server -mlocalhost -f1236 -nPS2 -Nps2']}, {'name':'Private Server 1','dir':'/home/user/AssaultCube_v1.0.4/','autorestart':False, 'args':['bash','-c','./bin_unix/linux_server -mlocalhost -f1238 -nPS3 -Npr1']}, {'name':'Private Server 2','dir':'/home/user/AssaultCube_v1.0.4/','autorestart':True, 'args':['./bin_unix/linux_server','-mlocalhost','-f1240','-nPS4 xyz','-Npr2']} ] } name: the service name, for your own reference dir: the working directory of the service autorestart: automatically restart if it died, or if the configuration was changed (!) args: the command concerned. Every argument written separately, but you can use bash -c 'command' to make it easier on yourself. The configuration file is reloaded every 5 seconds, and new processes are started if necessary. """ import time import datetime import subprocess import curses import os procs = {} ll = [] ml = [] myscreen = None mcwd = None # Not implemented yet: # This function would permit us to read all our logs live, and perhaps dump them as well. # Need help here def mlog(txt,service_name): global myscreen if myscreen is not None: global ml if len(ml) > 20: ml.pop(0) ml.append("[%s] %s" % service_name.rjust(25," "), txt) # This function just takes any logmessages and outputs them def alog(txt): global myscreen if myscreen is not None: global ll if len(ll) > 5: ll.pop(0) ll.append("[%s] %s"% (time.strftime('%Y%m%d %H:%M:%S'), txt)) else: print(txt) # This function loads the configuration. def lconfig(): global procs global config global mcwd g = {} os.chdir(mcwd) execfile("nmpx.cfg.py",g) config = g["config"] for pro in config["procs"]: if not pro["name"] in procs: print "Adding new process, name %s" % pro["name"] procs[pro["name"]] = {"ran":False,"process":None,"name":None,"kill":False,"dir":None,"args":None,"modified":True} p = procs[pro["name"]] if p["name"] != pro["name"]: p["name"] = pro["name"] p["modified"] = True if p["dir"] != pro["dir"]: p["dir"] = pro["dir"] p["autorestart"] = pro["autorestart"] if p["args"] != pro["args"]: p["args"] = pro["args"] p["modified"] = True # This function outputs the status def status(): global myscreen n = 0 if myscreen is not None: myscreen.clear myscreen.border(0) myscreen.addstr(2, 2, "Process manager") for k in procs.keys(): p = procs[k] if myscreen is None: #print ... alog("True") else: n=n+1 sstring = "(%s) %s " % (['started','stopped'][p["process"] is None],p["name"].rjust(25," ")) myscreen.addstr(3+n,2,sstring) if myscreen is not None: ni = 0 global ll for az in ll: ni = ni + 1 myscreen.addstr (4+n+ni,2, az) myscreen.refresh() # This function checks the processes def checkProcesses(): global procs for k in procs.keys(): p = procs[k] if p["process"] is not None and p["process"].poll() is not None: alog("Process %s died" % p["name"]) p["process"] = None if p["modified"] and p["autorestart"]: if p["process"] is not None: alog("Process %s was modified, so stopping" % p["name"]) p["process"].kill() p["process"] = None if p["process"] is not None and p["kill"]: alog("Killing process %s" % p["name"]) p["process"].kill() p["process"] = None if p["process"] is None and (p["autorestart"] or p["ran"] is False) and not p["kill"]: alog("Process %s starting" % p["name"]) os.chdir(p["dir"]) p["process"] = subprocess.Popen(p["args"],stdout=subprocess.PIPE) p["modified"] = False p["ran"] = True def gquit(): global procs cquit() for k in procs.keys(): if procs[k]["process"] is not None: alog("Killing %s "%procs[k]["name"]) procs[k]["process"].kill() alog("Quitting") quit() def cquit(): global myscreen myscreen.keypad(0) curses.echo() curses.nocbreak() curses.endwin() myscreen = None def main(): global mcwd mcwd = os.getcwd() alog("Loading config") lconfig() global myscreen # if config["curses"]: myscreen = curses.initscr() myscreen.nodelay(1) else: myscreen = None alog("Checking processes") checkProcesses() timer = 0 while True: timer = timer + 1 time.sleep(0.1) # Reload the configuration every 5 seconds if timer % 50 is 0: timer = 0 lconfig() checkProcesses() if myscreen is not None: ch = myscreen.getch() if ch > 0 and ch < 256: cc = chr(ch) # Allow only 1 - 10, so only 10 processes can be managed for now if cc.isdigit(): k = procs.keys() cc = int(cc) if cc == 0: cc = 10 if cc <= len(k): p = procs[k[cc-1]] if p["process"] is not None: # We tell it to get killed and stop procs[k[cc-1]]["kill"] = True else: # We tell it to start like before procs[k[cc-1]]["ran"] = False elif cc == "q": gquit() status() alog("Quitting") if __name__ == "__main__": try: main() if myscreen is not None: cquit() except: if myscreen is not None: cquit() # Bring back the error into a reset terminal, of course raise
Name this file as nmpx.py, and then run it with
Code:
screen python nmpx.py

Now you have an ncurses interface inside screen to start/stop your servers. Simply press 1/2/3/4/5/... to stop or start that server.

I've never written a Python script before (this is my first time...) so don't expect my code to be ueber-great. Yes, there are a lot of dodgy things (obviously), so I hope that I can get help from people who know what they're doing, and will submit any updates and improvements here.

What I'd like to have in the end is an interface where:
* You can select more than 10 servers and control them with a nice ncurses interface
* Read all logs, live, at the same time, and also dump them into one single file
* Interact with this application by other means, especially for remote control

Hope this helps :)

Thank you :D
Thanks given by:
#2
nice!!
Thanks given by:
#3
Dude wat, you beast O.o
Thanks given by:
#4
[Image: python.png]
Thanks given by:
#5
hihi :)

Anyone tried this? I haven't yet applied this to any of my servers.
Thanks given by:
#6
Thanks, I'll try when I have time
Thanks given by: