mirror of
https://github.com/r4sas/py-i2phosts
synced 2025-01-08 22:07:55 +00:00
e2c2eeeaae
Master log file first opened as root, than daemonization with privilege drop happens and master then unable to log to this logfile because of insufficient permissions. So chown logfile to "runas" user to avoid this situation.
168 lines
4.5 KiB
Python
Executable File
168 lines
4.5 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import os
|
|
import sys
|
|
import pwd
|
|
import errno
|
|
import argparse
|
|
import time
|
|
import configobj
|
|
import validate
|
|
import subprocess
|
|
import threading
|
|
import daemon
|
|
# workaround for python-daemon >= 1.6
|
|
try:
|
|
import daemon.pidlockfile as pidfile
|
|
except ImportError:
|
|
import daemon.pidfile as pidfile
|
|
|
|
class Thread(threading.Thread):
|
|
|
|
def __init__(self):
|
|
threading.Thread.__init__(self)
|
|
self.setDaemon(True)
|
|
|
|
|
|
class FetcherThread(Thread):
|
|
""" Run py-i2phosts-fetcher periodically """
|
|
|
|
def run(self):
|
|
while True:
|
|
run_prog('py-i2phosts-fetcher')
|
|
time.sleep(float(config['fetch_interval']))
|
|
|
|
|
|
class CheckerThread(Thread):
|
|
""" Run py-i2phosts-checker, py-i2phosts-maint, py-i2phosts-builder periodically """
|
|
|
|
def run(self):
|
|
while True:
|
|
run_prog('py-i2phosts-checker')
|
|
run_prog('py-i2phosts-maint')
|
|
run_prog('py-i2phosts-builder')
|
|
time.sleep(float(config['check_interval']))
|
|
|
|
|
|
def run_prog(prog):
|
|
try:
|
|
log.info('starting: %s', prog)
|
|
sp_args = [prog]
|
|
if args.debug or args.verbose:
|
|
sp_args.append('-d')
|
|
p = subprocess.Popen(sp_args, shell=False)
|
|
except OSError, e:
|
|
log.error('failed to exec %s: %s', prog, e)
|
|
if e.errno == errno.ENOENT:
|
|
log.error(' maybe it isn\'t in PATH?')
|
|
else:
|
|
p.wait()
|
|
log.info('finished: %s', prog)
|
|
|
|
|
|
def main():
|
|
def run_fetcher():
|
|
fetcher = FetcherThread()
|
|
fetcher.start()
|
|
return fetcher
|
|
|
|
def run_checker():
|
|
checker = CheckerThread()
|
|
checker.start()
|
|
return checker
|
|
|
|
# if we're just started, wait while fetcher get some new hosts
|
|
fetcher = run_fetcher()
|
|
log.debug('just started, delaying checker run for 300 secs')
|
|
fetcher.join(300) # wait for 5 mins
|
|
# start checker and other
|
|
checker = run_checker()
|
|
|
|
while True:
|
|
log.debug('checking fetcher and checker threads status')
|
|
if fetcher.isAlive() == False:
|
|
log.warning('fetcher thread is dead, respawning...')
|
|
fetcher = run_fetcher()
|
|
else:
|
|
log.debug('fetcher thread: alive')
|
|
if checker.isAlive() == False:
|
|
log.warning('checker thread is dead, respawning...')
|
|
checker = run_checker()
|
|
else:
|
|
log.debug('checker thread: alive')
|
|
# do check every 30 mins
|
|
time.sleep(1800)
|
|
|
|
# parse command line options
|
|
parser = argparse.ArgumentParser(
|
|
description='Master daemon for py-i2phosts.',
|
|
epilog='Report bugs to http://zzz.i2p/topics/733')
|
|
parser.add_argument('-d', '--debug', action='store_true',
|
|
help='run in debug mode without detaching from terminal'),
|
|
parser.add_argument('-v', '--verbose', action='store_true',
|
|
help='run in verbose mode without detaching from terminal'),
|
|
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/master.conf', dest='config_file',
|
|
help='config file to use')
|
|
args = parser.parse_args()
|
|
|
|
# read and validate config
|
|
spec = '''
|
|
log_file = string(default='/var/log/py-i2phosts/master.log')
|
|
log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info')
|
|
pid_file = string(default='/var/run/py-i2phosts/master.pid')
|
|
runas = string(default='_pyi2phosts')
|
|
check_interval = integer(default=43200)
|
|
fetch_interval = integer(default=1800)
|
|
'''
|
|
spec = spec.split('\n')
|
|
config = configobj.ConfigObj(args.config_file, configspec=spec)
|
|
if 'include' in config:
|
|
config_included = configobj.ConfigObj(config['include'])
|
|
config.merge(config_included)
|
|
|
|
# django setup
|
|
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
|
|
if 'DJANGO_PROJECT_PATH' in config:
|
|
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
|
|
else:
|
|
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
|
|
sys.path.insert(1, DJANGO_PROJECT_PATH)
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
|
|
from pyi2phosts.lib.utils import get_logger
|
|
from pyi2phosts.lib.utils import validate_config
|
|
|
|
# validate config
|
|
validate_config(config)
|
|
|
|
# configure logger
|
|
if args.debug == True:
|
|
log_level = 'debug'
|
|
log_file = None
|
|
elif args.verbose == True:
|
|
log_level = 'info'
|
|
log_file = None
|
|
else:
|
|
log_level = config['log_level']
|
|
log_file = config['log_file']
|
|
|
|
if not args.debug and not args.verbose:
|
|
# get pid object for daemon
|
|
pid = pidfile.TimeoutPIDLockFile(config['pid_file'], 10)
|
|
# create daemon context
|
|
d = daemon.DaemonContext(pidfile=pid, umask=077)
|
|
# write stderr to logfile # FIXME: and how we will deal with log rotation?
|
|
logfile = open(config['log_file'], 'a')
|
|
d.stderr = logfile
|
|
d.stdout = logfile
|
|
# drop privileges when started as root
|
|
if os.getuid() == 0:
|
|
runas = config['runas']
|
|
pw_entry = pwd.getpwnam(runas)
|
|
d.uid = pw_entry[2]
|
|
d.gid = pw_entry[3]
|
|
os.chown(config['log_file'], d.uid, d.gid)
|
|
d.open() # become daemon
|
|
log = get_logger(filename=log_file, log_level=log_level)
|
|
log.info('started')
|
|
main()
|