#!/usr/bin/python2 import time, sys, os, errno, logging, signal, copy, select, socket, grp, random daily = 86400 twice_daily = 86400 / 2 hourly = 3600 bi_hourly = 7200 tri_hourly = 10800 twice_hourly = 1800 ten_minutely = 600 five_minutely = 300 maxtime = 86400 mintime = 60 jobs = {} MAX_JOBS = 6 #earPath = '/home/mirror/merlin/merlin.sock' earPath = '/mirror/merlin/run/merlin.sock' cmd_buf_size = 4096 repos = { 'debian': { 'command': '~/bin/csc-sync-debian debian debian.mirror.rafal.ca debian', 'interval': bi_hourly, 'max-sync-time': maxtime, }, # 'debian-cdimage': { # 'command': '~/bin/csc-sync-cdimage debian-cdimage cdimage.debian.org cdimage', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, 'ubuntu': { 'command': '~/bin/csc-sync-debian ubuntu archive.ubuntu.com ubuntu drescher.canonical.com', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'ubuntu-ports': { 'command': '~/bin/csc-sync-debian ubuntu-ports ports.ubuntu.com ubuntu-ports drescher.canonical.com', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'linuxmint-packages': { 'command': '~/bin/csc-sync-debian linuxmint-packages rsync-packages.linuxmint.com packages', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'debian-multimedia': { 'command': '~/bin/csc-sync-debian debian-multimedia www.deb-multimedia.org deb', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'debian-backports': { 'command': '~/bin/csc-sync-debian debian-backports debian.mirror.rafal.ca debian-backports', 'interval': bi_hourly, 'max-sync-time': maxtime, }, # 'debian-volatile': { # 'command': '~/bin/csc-sync-debian debian-volatile debian.mirror.rafal.ca debian-volatile', # 'interval': bi_hourly, # 'max-sync-time': maxtime, # }, 'debian-security': { 'command': '~/bin/csc-sync-debian debian-security rsync.security.debian.org debian-security security-master.debian.org', 'interval': twice_hourly, 'max-sync-time': maxtime, }, 'ubuntu-releases': { 'command': '~/bin/csc-sync-standard ubuntu-releases rsync.releases.ubuntu.com releases', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'xubuntu-releases': { 'command': '~/bin/csc-sync-standard xubuntu-releases cdimage.ubuntu.com cdimage/xubuntu/releases/', 'interval': bi_hourly, 'max-sync-time': maxtime, }, # 'emdebian': { # 'command': '~/bin/csc-sync-badperms emdebian www.emdebian.org debian', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, 'puppylinux': { 'command': '~/bin/csc-sync-standard puppylinux distro.ibiblio.org puppylinux', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'CPAN': { 'command': '~/bin/csc-sync-standard CPAN cpan-rsync.perl.org CPAN', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'CRAN': { 'command': '~/bin/csc-sync-ssh CRAN cran.r-project.org "" cran-rsync ~/.ssh/id_cran_rsa', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'CTAN': { 'command': '~/bin/csc-sync-standard CTAN rsync.dante.ctan.org CTAN', 'interval': twice_daily, 'max-sync-time': maxtime, }, # 'openoffice': { # 'command': '~/bin/csc-sync-standard openoffice rsync.services.openoffice.org openoffice-extended', # 'command': '~/bin/csc-sync-standard openoffice ftp.snt.utwente.nl openoffice-extended', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, 'fedora-epel': { 'command': '~/bin/csc-sync-standard fedora/epel mirrors.kernel.org fedora-epel && ~/bin/report_mirror >/dev/null', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'cygwin': { 'command': '~/bin/csc-sync-standard cygwin cygwin.com cygwin-ftp', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'gnu': { #'command': '~/bin/csc-sync-standard gnu mirrors.ibiblio.org gnuftp/gnu/', 'command': '~/bin/csc-sync-standard gnu ftp.gnu.org gnu', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'nongnu': { # 'command': '~/bin/csc-sync-standard nongnu dl.sv.gnu.org releases --ignore-errors', 'command': '~/bin/csc-sync-standard nongnu dl.sv.gnu.org releases', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'mysql': { #'command': '~/bin/csc-sync-standard mysql mysql.he.net mysql', 'command': '~/bin/csc-sync-standard mysql rsync.mirrorservice.org ftp.mysql.com', 'interval': twice_daily, 'max-sync-time': maxtime, }, # No longer syncs, and no longer really relevant # 'mozdev': { # 'command': '~/bin/csc-sync-standard mozdev rsync.mozdev.org mozdev', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, 'gnome': { 'command': '~/bin/csc-sync-standard gnome master.gnome.org gnomeftp gnome', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'damnsmalllinux': { 'command': '~/bin/csc-sync-standard damnsmalllinux ftp.heanet.ie mirrors/damnsmalllinux.org/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'linuxmint': { 'command': '~/bin/csc-sync-standard linuxmint pub.linuxmint.com pub', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'kernel.org-linux': { 'command': '~/bin/csc-sync-standard kernel.org/linux rsync.kernel.org pub/linux/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'kernel.org-software': { 'command': '~/bin/csc-sync-standard kernel.org/software rsync.kernel.org pub/software/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'apache': { 'command': '~/bin/csc-sync-apache apache rsync.us.apache.org apache-dist', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'eclipse': { 'command': '~/bin/csc-sync-standard eclipse download.eclipse.org eclipseMirror', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'kde': { 'command': '~/bin/csc-sync-standard kde rsync.kde.org kdeftp', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'kde-applicationdata': { 'command': '~/bin/csc-sync-standard kde-applicationdata rsync.kde.org applicationdata', 'interval': twice_daily, 'max-sync-time': maxtime, }, # We are a Tier 1 arch mirror (https://bugs.archlinux.org/task/52853) # so our IP is important. 'archlinux': { #'command': '~/bin/csc-sync-standard archlinux archlinux.mirror.rafal.ca archlinux', 'command': '~/bin/csc-sync-archlinux archlinux', 'interval': five_minutely, 'max-sync-time': maxtime, }, 'artixlinux': { 'command': '~/bin/csc-sync-standard artixlinux mirror1.artixlinux.org artix-linux', 'interval': bi_hourly, 'max-sync-time': maxtime, }, # We stopped mirroring debian-ports on 2021-12-22 due to disk performance issues. #'debian-ports': { # 'command': '~/bin/csc-sync-standard debian-ports ftp.de.debian.org debian-ports', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, 'slackware': { 'command': '~/bin/csc-sync-standard slackware slackware.cs.utah.edu slackware', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'debian-cd': { 'command': '~/bin/csc-sync-debian-cd', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'x.org': { #'command': '~/bin/csc-sync-standard x.org xorg.freedesktop.org xorg-archive', #'command': '~/bin/csc-sync-standard x.org mirror.us.leaseweb.net xorg', 'command': '~/bin/csc-sync-standard x.org rsync.mirrorservice.org ftp.x.org/pub/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'centos': { 'command': '~/bin/csc-sync-standard centos us-msync.centos.org CentOS', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'opensuse': { 'command': '~/bin/csc-sync-standard opensuse stage.opensuse.org opensuse-full/opensuse/ #"--exclude distribution/.timestamp_invisible"', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'FreeBSD': { # Has not updated since at least June 2018 #'command': '~/bin/csc-sync-standard FreeBSD ftp10.us.freebsd.org FreeBSD', 'command': '~/bin/csc-sync-standard FreeBSD ftp2.uk.freebsd.org ftp.freebsd.org/pub/FreeBSD/', #'command': '~/bin/csc-sync-standard FreeBSD ftp3.us.freebsd.org FreeBSD/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'fedora-enchilada': { # 'command': '~/bin/csc-sync-standard fedora/linux mirrors.kernel.org fedora-enchilada/linux/ --ignore-errors && ~/bin/report_mirror >/dev/null', 'command': '~/bin/csc-sync-standard fedora/linux mirrors.kernel.org fedora-enchilada/linux/ && ~/bin/report_mirror >/dev/null', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'ubuntu-ports-releases': { 'command': '~/bin/csc-sync-standard ubuntu-ports-releases cdimage.ubuntu.com cdimage/releases/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'gentoo-distfiles': { 'command': '~/bin/csc-sync-gentoo', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'gentoo-portage': { 'command': '~/bin/csc-sync-standard gentoo-portage rsync1.us.gentoo.org gentoo-portage', 'interval': twice_hourly, 'max-sync-time': maxtime, }, # This project is no longer available for mirroring # https://bugzilla.mozilla.org/show_bug.cgi?id=807543 #'mozilla.org': { # 'command': '~/bin/csc-sync-standard mozilla.org releases-rsync.mozilla.org mozilla-releases', # 'interval': twice_hourly, # 'max-sync-time': maxtime, #}, 'gutenberg': { 'command': '~/bin/csc-sync-standard gutenberg ftp@ftp.ibiblio.org gutenberg', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'racket-installers': { 'command': '~/bin/csc-sync-wget racket/racket-installers https://mirror.racket-lang.org/installers/ 1', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'plt-bundles': { 'command': '~/bin/csc-sync-standard racket/plt-bundles mirror.racket-lang.org plt-bundles', 'interval': twice_daily, 'max-sync-time': maxtime, }, # We stopped mirroring OpenBSD on 2021-12-22 due to degraded disk performance #'OpenBSD': { # 'command': '~/bin/csc-sync-standard OpenBSD ftp3.usa.openbsd.org ftp', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, 'xiph': { #'command': '~/bin/csc-sync-standard xiph downloads.xiph.org xiph/releases', 'command': '~/bin/csc-sync-standard xiph ftp.osuosl.org xiph', 'interval': twice_daily, 'max-sync-time': maxtime, }, # We currently don't have the disk space 'netbsd': { 'command': '~/bin/csc-sync-standard NetBSD rsync.netbsd.org NetBSD', 'interval': twice_daily, 'max-sync-time': maxtime, }, # We stopped mirroring pkgsrc on 2021-12-22 due to disk performance issues. #'netbsd-pkgsrc': { # 'command': '~/bin/csc-sync-standard pkgsrc rsync.netbsd.org pkgsrc', # #'command': '~/bin/csc-sync-standard pkgsrc rsync3.jp.netbsd.org pub/pkgsrc/', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, 'macports-release': { 'command': '~/bin/csc-sync-standard MacPorts/release rsync.macports.org macports/release/', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'macports-distfiles': { 'command': '~/bin/csc-sync-standard MacPorts/mpdistfiles rsync.macports.org macports/distfiles/', 'interval': bi_hourly, 'max-sync-time': maxtime, }, # 'raspberrypi': { # 'command': '~/bin/csc-sync-standard raspberrypi mirrors.rit.edu rpi', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, 'sagemath': { #'command': '~/bin/csc-sync-standard sage mirror.clibre.uqam.ca sage', 'command': '~/bin/csc-sync-standard sage rsync.sagemath.org sage', 'interval': twice_daily, 'max-sync-time': maxtime, }, # 'cs136': { # 'command': '~/bin/csc-sync-ssh uw-coursewear/cs136 linux024.student.cs.uwaterloo.ca /u/cs136/mirror.uwaterloo.ca csc01 ~/.ssh/id_rsa_csc01', # 'interval': hourly, # 'max-sync-time': maxtime, # }, 'vlc': { 'command': '~/bin/csc-sync-standard vlc rsync.videolan.org videolan-ftp', 'interval': twice_daily, 'max-sync-time': maxtime, }, # We stopped mirroring qtproject on 2021-12-22 due to disk performance issues. #'qtproject': { # 'command': '~/bin/csc-sync-standard qtproject master.qt.io qt-all', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, 'tdf': { 'command': '~/bin/csc-sync-standard tdf rsync.documentfoundation.org tdf-pub', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'saltstack': { 'command': '~/bin/csc-sync-s3 saltstack https://s3.repo.saltproject.io', 'interval': daily, 'max-sync-time': maxtime, }, # 'kali': { # 'command': '~/bin/csc-sync-standard kali kali.mirror.globo.tech kali', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, # 'kali-images': { # 'command': '~/bin/csc-sync-standard kali-images kali.mirror.globo.tech kali-images', # 'interval': twice_daily, # 'max-sync-time': maxtime, # }, 'alpine': { 'command': '~/bin/csc-sync-standard alpine rsync.alpinelinux.org alpine', 'interval': hourly, 'max-sync-time': maxtime, }, 'raspbian': { 'command': '~/bin/csc-sync-standard raspbian raspbian.freemirror.org raspbian', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'raspberrypi': { 'command': '~/bin/csc-sync-standard-ipv6 raspberrypi apt-repo.raspberrypi.org archive', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'ipfire': { 'command': '~/bin/csc-sync-standard ipfire rsync.ipfire.org full', 'interval': hourly, 'max-sync-time': maxtime, }, 'manjaro': { 'command': '~/bin/csc-sync-standard manjaro mirrorservice.org repo.manjaro.org/repos/', 'interval': hourly, 'max-sync-time': maxtime, }, 'mxlinux': { 'command': '~/bin/csc-sync-standard mxlinux mirror.math.princeton.edu pub/mxlinux/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'mxlinux-iso': { 'command': '~/bin/csc-sync-standard mxlinux-iso mirror.math.princeton.edu pub/mxlinux-iso/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'parabola': { 'command': '~/bin/csc-sync-standard parabola repo.parabola.nu:875 repos/', 'interval': twice_daily, 'max-sync-time': maxtime, }, #'hyperbola-sources': { # 'command': '~/bin/csc-sync-chmod hyperbola/sources repo.hyperbola.info:52000 repo/', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, #'hyperbola-stable': { # 'command': '~/bin/csc-sync-chmod hyperbola/gnu-plus-linux-libre/stable repo.hyperbola.info:52012 repo/', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, #'hyperbola-testing': { # 'command': '~/bin/csc-sync-chmod hyperbola/gnu-plus-linux-libre/testing repo.hyperbola.info:52011 repo/', # 'interval': twice_daily, # 'max-sync-time': maxtime, #}, 'trisquel-packages': { 'command': '~/bin/csc-sync-standard trisquel/packages rsync.trisquel.info trisquel.packages/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'trisquel-iso': { 'command': '~/bin/csc-sync-standard trisquel/iso rsync.trisquel.info trisquel.iso/', 'interval': twice_daily, 'max-sync-time': maxtime, }, 'almalinux': { 'command': '~/bin/csc-sync-standard almalinux rsync.repo.almalinux.org almalinux/', 'interval': bi_hourly, 'max-sync-time': maxtime, }, 'ceph': { 'command': '~/bin/csc-sync-ceph -q -s global -t ceph', 'interval': tri_hourly, 'max-sync-time': maxtime, }, } def mirror_status(): out = [] for x in repos: repository = repos[x] last_attempt = repository['last-attempt'] next_attempt = repository['last-attempt'] + repository['interval'] out.append( [x, last_attempt, next_attempt] ) out.sort(key= lambda x: x[2]) #turn floating point time values into human readable strings for x in out: for y in (1,2): x[y] = time.ctime(x[y]) out.insert(0,['Repository', 'Last Synced', 'Next Expected Sync']) #calculate maximum width of each column widths = [] i = 0 while True: try: column_width = max([len(row[i]) for row in out]) widths.append(column_width + 3) i = i + 1 except: break #compose table string and pad out columns status_string = '%s%s%s\n' % (out[0][0].ljust(widths[0]), out[0][1].ljust(widths[1]), out[0][2].ljust(widths[2])) for x in out[1:]: for y in (0,1,2): status_string = status_string + ('%s' % x[y].rjust(widths[y])) status_string = status_string + '\n' return status_string def init_last_sync(): if not os.path.isdir('stamps'): os.mkdir('stamps') now = time.time() for repo in repos: try: last = os.stat('stamps/%s' % repo).st_mtime repos[repo]['last-sync'], repos[repo]['last-attempt'] = last, last logging.info('repo %s last synced %d seconds ago' % (repo, now - last)) except OSError: repos[repo]['last-sync'], repos[repo]['last-attempt'] = 0, 0 logging.warning('repo %s has never been synced' % repo) def update_last_attempt(repo, start_time): repos[repo]['last-attempt'] = start_time def update_last_sync(repo, start_time, duration, exit_code): repos[repo]['last-sync'] = start_time update_last_attempt(repo, start_time) # touch the timestamp file with open('stamps/%s' % repo, 'w') as f: f.write('%d,%d,%d' % (start_time, duration, exit_code)) # open('stamps/%s' % repo, 'w').close() def handler(signum, frame): logging.info("Caught signal %d" % signum) os.remove(earPath) for job in jobs: os.kill(job, signal.SIGTERM) time.sleep(1) for job in jobs: os.kill(job, signal.SIGKILL) try: while True: pid, status = os.wait() logging.info("Child process %d has terminated" % pid) #ECHILD is thrown when there are no child processes left except OSError, e: if e.errno != errno.ECHILD: raise logging.info("All child processes terminated. Goodbye!") sys.exit() def setup_logging(): if not os.path.isdir('logs'): os.mkdir('logs') try: os.unlink(earPath) except OSError: pass if '-d' in sys.argv: logging.basicConfig(filename="/home/mirror/merlin/logs/merlin.log", level=logging.DEBUG, format="%(asctime)-15s %(message)s") else: logging.basicConfig(level=logging.DEBUG, format="%(asctime)-15s %(message)s") def sync(current, now): pid = os.fork() if not pid: try: logfd = os.open('logs/%s' % current, os.O_WRONLY|os.O_APPEND|os.O_CREAT, 0644) nulfd = os.open('/dev/null', os.O_RDONLY) os.dup2(nulfd, 0) os.dup2(logfd, 1) os.dup2(logfd, 2) os.close(logfd) os.close(nulfd) os.execv("/bin/sh", ['sh', '-c', repos[current]['command']]) except OSError, e: print >>sys.stderr, 'failed to exec: %s' % e os.exit(1) #There exists a race condition that manifests if merlin is asked to terminate ( by receiving signal ) after fork() but before the jobs table is updated. #Normally you would mask off signals to avoid attempting shutdown in that time period, but this is not supported in the version of Python for which merlin was written. #TODO: #The Linux Programming Interface 24.5 illustrates how to synchronize parent and child using signals. Might be applicable. jobs[pid] = {'name': current, 'start_time': now, 'status': 'running'} def zfssync(current): pid = os.fork() if not pid: try: logfd = os.open('logs/zfssync-%s.log' % current, os.O_WRONLY|os.O_APPEND|os.O_CREAT, 0644) nulfd = os.open('/dev/null', os.O_RDONLY) os.dup2(nulfd, 0) os.dup2(logfd, 1) os.dup2(logfd, 2) os.close(logfd) os.close(nulfd) os.execv("/home/mirror/bin/zfssync", ['zfssync', current]) except OSError, e: print >>sys.stderr, 'failed to exec: %s' % e os.exit(1) def await_command(ear): if select.select([ear],[],[],1) == ([ear],[],[]): #handle command heard on ear socket s, address = ear.accept() cmdstring = '' while True: data = s.recv(cmd_buf_size) if not data: break cmdstring = cmdstring + data cmdstring = cmdstring.split(':',1) command = cmdstring[0] try: if command == 'sync': try: arg = cmdstring[1] if arg in repos: if arg in (x['name'] for x in jobs.itervalues()): logging.info('Cannot force sync: %s. Already syncing.' % arg) s.send('Cannot force sync %s, already syncing.' % command) else: logging.info('Forcing sync: %s' % arg) s.send('Forcing sync: %s' % arg) sync(arg, time.time()) else: logging.info('%s not tracked, cannot sync.' % arg) s.send('%s not tracked, cannot sync.' % arg) except: s.send('Could not parse sync command, forced sync fails.') raise elif command == 'status': s.send(mirror_status()) elif command == 'dump': s.send(str(jobs)) s.send("\n") s.send(str(repos)) else: logging.error('Received unrecognized command: %s' % command) s.send('Bad command: %s' % command) s.close() except socket.error, e: logging.error('Could not communicate with arthur over socket.') def new_jobs(now): #To prevent repos at the 'bottom' of the dictionary from getting neglected when mirror is under unusual load (and merlin is running at MAX_JOBS) keys = repos.keys() random.shuffle(keys) for current in keys: if len(jobs) >= MAX_JOBS: break if now <= repos[current]['last-attempt'] + mintime: continue if current in (x['name'] for x in jobs.itervalues()): continue when_due = repos[current]['last-sync'] + repos[current]['interval'] if now >= when_due: logging.debug("syncing %s, due for %d seconds" % (current, now - when_due)) sync(current, now) def handle_completed(now): try: pid, status = os.waitpid(-1, os.WNOHANG) #waitpid() likes to throw an exception when there's nothing to wait for #Don't know if we should be forever ignoring hung jobs if they eventually return... if pid != 0 and pid in jobs: job = jobs.pop(pid) if os.WIFSIGNALED(status): logging.error("%s %s sync terminated with signal %d" % (job['status'], job['name'], os.WTERMSIG(status))) update_last_attempt(job['name'], job['start_time']) elif os.WIFEXITED(status): exit_status = os.WEXITSTATUS(status) logging.error("%s %s sync exited with status %d " % (job['status'], job['name'], exit_status)) update_last_attempt(job['name'], job['start_time']) interval = repos[job['name']]['interval'] sync_took = int(now - job['start_time']) next_sync = max(0, interval - sync_took) update_last_sync(job['name'], job['start_time'], sync_took, exit_status) logging.info('%s sync complete, took %d seconds, syncs again in %d seconds' % (job['name'], sync_took, next_sync)) zfssync(job['name']) except OSError, e: if e.errno != errno.ECHILD: raise def check_hung(now): for pid in jobs: #check if hung runtime = now - jobs[pid]['start_time'] repo = jobs[pid]['name'] if jobs[pid]['status'] == 'running' and runtime > repos[repo]['max-sync-time']: jobs[pid]['status'] = 'hung' logging.error("%s sync process hung, process pid %d" % (repo, pid)) def main(): if not os.geteuid(): print "Don't run merlin as root!" sys.exit(1); setup_logging() logging.info("Starting Merlin") signal.signal(signal.SIGTERM, handler) signal.signal(signal.SIGINT, handler) init_last_sync() old_umask = os.umask(0o002) ear = socket.socket(socket.AF_UNIX) ear.bind(earPath) ear.listen(1) os.umask(old_umask) os.chown(earPath, -1, grp.getgrnam("push").gr_gid) while True: await_command(ear) now = time.time() new_jobs(now) handle_completed(now) check_hung(now) if __name__ == '__main__': main()