introduce 'to_home' and systemd user services
- frappy is now a systemd user service - add 'frappy' and 'boxweb' bash function
This commit is contained in:
195
install.py
195
install.py
@ -128,11 +128,22 @@ WantedBy = multi-user.target
|
||||
|
||||
FRAPPY_SERVICE = """[Unit]
|
||||
Description = Running frappy server
|
||||
|
||||
[Service]
|
||||
Environment=PYTHONPATH=/home/l_samenv/.local/lib/python3.11/site-packages/
|
||||
ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/frappy-server %s
|
||||
|
||||
[Install]
|
||||
WantedBy = default.target
|
||||
"""
|
||||
|
||||
DISPLAY_SERVICE = f"""[Unit]
|
||||
Description = status display
|
||||
After = network.target
|
||||
|
||||
[Service]
|
||||
User = l_samenv
|
||||
ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/frappy-server %s
|
||||
User = root
|
||||
ExecStart = /usr/bin/python3 {TOOLS}/display.py -d
|
||||
|
||||
[Install]
|
||||
WantedBy = multi-user.target
|
||||
@ -150,6 +161,17 @@ ExecStart = /usr/bin/python3 {TOOLS}/display.py -d
|
||||
WantedBy = multi-user.target
|
||||
"""
|
||||
|
||||
BOXWEB_SERVICE = """[Unit]
|
||||
Description = Web service for local GUI on a box
|
||||
After = network.target
|
||||
|
||||
[Service]
|
||||
Environment=PYTHONPATH=/home/l_samenv/.local/lib/python3.11/site-packages/
|
||||
ExecStart = /usr/bin/python3 /home/l_samenv/boxweb/flaskserver.py %i {port}
|
||||
|
||||
[Install]
|
||||
WantedBy = multi-user.target
|
||||
"""
|
||||
|
||||
pip_requirements = {
|
||||
'root': {},
|
||||
@ -164,8 +186,8 @@ dhcp_server_cfg = []
|
||||
TO_SYSTEM = TOOLS / 'to_system', TOOLS / f'{box.typ}_system'
|
||||
|
||||
|
||||
def do_cmd(*command):
|
||||
unix_cmd(*command, execute=doit) # sudo=True by default
|
||||
def do_cmd(*command, sudo=True):
|
||||
unix_cmd(*command, execute=doit, sudo=sudo)
|
||||
show.dirty = True
|
||||
|
||||
|
||||
@ -179,7 +201,7 @@ def frappy(cfg=None, port=None, requirements='', **kwds):
|
||||
cfg = '-p %s %s' % (port, cfg)
|
||||
with open('/home/l_samenv/frappy/requirements.txt') as f:
|
||||
req['frappy main'] = f.read()
|
||||
return FRAPPY_SERVICE % cfg
|
||||
return False, FRAPPY_SERVICE % cfg
|
||||
|
||||
|
||||
def router(firewall=False, **opts):
|
||||
@ -207,7 +229,7 @@ def router(firewall=False, **opts):
|
||||
pip_requirements['root']['tools'] = f.read()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return ROUTER_SERVICE
|
||||
return True, ROUTER_SERVICE
|
||||
|
||||
|
||||
def display_update(cfg):
|
||||
@ -225,7 +247,7 @@ def display_update(cfg):
|
||||
def display(**opts):
|
||||
if not opts:
|
||||
return None
|
||||
return DISPLAY_SERVICE
|
||||
return True, DISPLAY_SERVICE
|
||||
|
||||
|
||||
def pip():
|
||||
@ -253,7 +275,13 @@ def pip():
|
||||
show.dirty = True
|
||||
|
||||
|
||||
SERVICES = dict(router=router, frappy=frappy, display=display)
|
||||
def boxweb(port=80, **opts):
|
||||
port = int(port)
|
||||
return port <= 1024, BOXWEB_SERVICE.format(port=port)
|
||||
|
||||
|
||||
SERVICES = dict(router=router, display=display, frappy=frappy, boxweb=boxweb)
|
||||
AS_ROOT = {'router', 'display'}
|
||||
|
||||
|
||||
def write_when_new(filename, content, as_root=False, ignore_reduction=False):
|
||||
@ -391,44 +419,54 @@ def create_if(name, cfg):
|
||||
return result + '\n'
|
||||
|
||||
|
||||
def walk_dir(action, srcpath, dstroot, files):
|
||||
if not files:
|
||||
return
|
||||
action.srcpath = srcpath = Path(srcpath)
|
||||
action.dstpath = dstpath = dstroot / srcpath
|
||||
match, mismatch, missing = filecmp.cmpfiles(srcpath, dstpath, files)
|
||||
if mismatch:
|
||||
newer = [f for f in mismatch if getmtime(dstpath / f) > getmtime(srcpath / f)]
|
||||
if newer:
|
||||
action.newer(newer)
|
||||
if len(newer) < len(mismatch):
|
||||
newer = set(newer)
|
||||
action.older([f for f in mismatch if f not in newer])
|
||||
if missing:
|
||||
if DEL in missing:
|
||||
missing.remove(DEL)
|
||||
with open(srcpath / DEL) as fil:
|
||||
to_delete = []
|
||||
for fname in fil:
|
||||
fname = fname.strip()
|
||||
if fname and exists(dstpath / fname):
|
||||
if (srcpath / fname).exists():
|
||||
print('ERROR: %s in %s, but also in repo -> ignored' % (fname, DEL))
|
||||
else:
|
||||
to_delete.append(fname)
|
||||
action.delete(to_delete)
|
||||
if missing:
|
||||
action.missing(missing)
|
||||
|
||||
|
||||
def walk(action):
|
||||
action.as_root = True
|
||||
for rootpath in TO_SYSTEM:
|
||||
if not rootpath.exists():
|
||||
continue
|
||||
os.chdir(rootpath)
|
||||
for dirpath, _, files in os.walk('.'):
|
||||
syspath = Path(dirpath[1:]) # remove leading '.' -> absolute path
|
||||
action.dirpath = dirpath = Path(dirpath)
|
||||
action.syspath = syspath
|
||||
if files:
|
||||
match, mismatch, missing = filecmp.cmpfiles(dirpath, syspath, files)
|
||||
if mismatch:
|
||||
newer = [f for f in mismatch if getmtime(syspath / f) > getmtime(dirpath / f)]
|
||||
if newer:
|
||||
action.newer(newer)
|
||||
if len(newer) < len(mismatch):
|
||||
newer = set(newer)
|
||||
action.older([f for f in mismatch if f not in newer])
|
||||
if missing:
|
||||
if DEL in missing:
|
||||
missing.remove(DEL)
|
||||
with open(dirpath / DEL) as fil:
|
||||
to_delete = []
|
||||
for fname in fil:
|
||||
fname = fname.strip()
|
||||
if fname and exists(syspath / fname):
|
||||
if (dirpath / fname).exists():
|
||||
print('ERROR: %s in %s, but also in repo -> ignored' % (fname, DEL))
|
||||
else:
|
||||
to_delete.append(fname)
|
||||
action.delete(to_delete)
|
||||
if missing:
|
||||
action.missing(missing)
|
||||
for srcpath, _, files in os.walk('.'):
|
||||
walk_dir(action, srcpath, Path('/'), files)
|
||||
os.chdir(TOOLS / 'to_home')
|
||||
action.as_root = False
|
||||
for srcpath, _, files in os.walk('.'):
|
||||
walk_dir(action, srcpath, Path.home(), files)
|
||||
|
||||
|
||||
class Walker:
|
||||
dirpath = None
|
||||
syspath = None
|
||||
srcpath = None
|
||||
dstpath = None
|
||||
as_root = None
|
||||
|
||||
|
||||
class Show(Walker):
|
||||
@ -439,46 +477,45 @@ class Show(Walker):
|
||||
if more_info:
|
||||
for f in files:
|
||||
if f.endswith(more_info):
|
||||
print('diff %s %s' % (self.dirpath / f, self.syspath / f))
|
||||
print('diff %s %s' % (self.srcpath / f, self.dstpath / f))
|
||||
print('.' * 80)
|
||||
else:
|
||||
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
|
||||
print('%s %s:\n %s' % (title, self.dstpath, ' '.join(files)))
|
||||
|
||||
def show(self, title, dirpath, files):
|
||||
def show(self, title, srcpath, files):
|
||||
self.dirty = True
|
||||
if more_info:
|
||||
for f in files:
|
||||
print('cat %s' % (dirpath / f))
|
||||
print('cat %s' % (srcpath / f))
|
||||
print('.' * 80)
|
||||
else:
|
||||
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
|
||||
print('%s %s:\n %s' % (title, self.dstpath, ' '.join(files)))
|
||||
|
||||
def newer(self, files):
|
||||
self.show('get from', self.dirpath, files)
|
||||
self.show('get from', self.srcpath, files)
|
||||
|
||||
def older(self, files):
|
||||
self.show('replace in', self.dirpath, files)
|
||||
self.show('replace in', self.srcpath, files)
|
||||
|
||||
def missing(self, files):
|
||||
self.show('install in', self.dirpath, files)
|
||||
self.show('install in', self.srcpath, files)
|
||||
|
||||
def delete(self, files):
|
||||
self.show('remove from', self.syspath, files)
|
||||
self.show('remove from', self.dstpath, files)
|
||||
|
||||
|
||||
class Do(Walker):
|
||||
def newer(self, files):
|
||||
usesudo = not self.syspath.is_relative_to(Path.home())
|
||||
unix_cmd('mkdir', '-p', str(self.syspath), sudo=usesudo)
|
||||
unix_cmd('cp', *(str(self.dirpath / f) for f in files), str(self.syspath),
|
||||
sudo=usesudo)
|
||||
unix_cmd('mkdir', '-p', str(self.dstpath), sudo=self.as_root)
|
||||
unix_cmd('cp', *(str(self.srcpath / f) for f in files), str(self.dstpath),
|
||||
sudo=self.as_root)
|
||||
|
||||
older = newer
|
||||
|
||||
missing = newer
|
||||
|
||||
def delete(self, files):
|
||||
unix_cmd('rm', '-f', *(str(self.syspath / f) for f in files))
|
||||
unix_cmd('rm', '-f', *(str(self.dstpath / f) for f in files))
|
||||
|
||||
|
||||
def handle_config():
|
||||
@ -562,15 +599,15 @@ def handle_config():
|
||||
box.hostname = newhostname
|
||||
if cfgfile is None:
|
||||
return False
|
||||
to_start = {}
|
||||
to_start = {} # dict <service> of <action>, <as_root>
|
||||
ifname = ''
|
||||
try:
|
||||
netcfg = config.get('NETWORK', {})
|
||||
if netcfg: # when not network is specified, do not handle network at all
|
||||
for name in netcfg:
|
||||
if name not in box.network_interfaces:
|
||||
print(f'{name} is not a valid network interface name')
|
||||
raise RuntimeError('network interface name system does not match')
|
||||
print(f'{name} is currently not a valid network interface - skip')
|
||||
continue
|
||||
for ifname in box.network_interfaces:
|
||||
content = create_if(ifname, netcfg.get(ifname, 'off'))
|
||||
# content = '\n'.join('%s=%s' % kv for kv in content.items())
|
||||
@ -578,7 +615,7 @@ def handle_config():
|
||||
if todo and not more_info:
|
||||
print('change', ifname)
|
||||
show.dirty = True
|
||||
to_start[ifname] = 'if_restart'
|
||||
to_start[ifname] = 'if_restart', True
|
||||
if dhcp_server_cfg:
|
||||
content = [DHCP_HEADER]
|
||||
for subnet, rangelist in dhcp_server_cfg:
|
||||
@ -596,7 +633,7 @@ def handle_config():
|
||||
todo = write_when_new('/etc/dhcp/dhcpd.conf', content, as_root=True)
|
||||
if todo:
|
||||
print('change dhcpd.conf')
|
||||
to_start['isc-dhcp-server'] = 'restart'
|
||||
to_start['isc-dhcp-server'] = 'restart', True
|
||||
show.dirty = True
|
||||
try:
|
||||
if replace_in_file(
|
||||
@ -612,7 +649,7 @@ def handle_config():
|
||||
unix_cmd('systemctl disable isc-dhcp-server')
|
||||
displaycfg = config.get('DISPLAY')
|
||||
if displaycfg and display_update(displaycfg):
|
||||
to_start['display'] = 'restart'
|
||||
to_start['display'] = 'restart', True
|
||||
if change_to_gitea(doit):
|
||||
show.dirty = True
|
||||
except Exception as e:
|
||||
@ -620,43 +657,54 @@ def handle_config():
|
||||
raise
|
||||
# return False
|
||||
|
||||
reload_systemd = False
|
||||
reload_systemd = set()
|
||||
for service, service_func in SERVICES.items():
|
||||
section = service.upper()
|
||||
section_dict = config.get(section, {})
|
||||
servicecfg = service_func(**section_dict)
|
||||
ret = service_func(**section_dict)
|
||||
active, enabled = check_service(service)
|
||||
if servicecfg is None:
|
||||
if ret is None:
|
||||
if active or enabled:
|
||||
check_service(service, False, doit)
|
||||
show.dirty = True
|
||||
continue
|
||||
else:
|
||||
as_root, servicecfg = ret
|
||||
if not enabled:
|
||||
to_start[service] = 'enable'
|
||||
to_start[service] = 'enable', as_root
|
||||
elif not active:
|
||||
to_start[service] = 'restart'
|
||||
if write_when_new(f'/etc/systemd/system/{service}.service', servicecfg, as_root=True):
|
||||
to_start[service] = 'restart', as_root
|
||||
if as_root:
|
||||
systemdir = Path('/etc/systemd/system')
|
||||
else:
|
||||
systemdir = Path('~/.config/systemd/user').expanduser()
|
||||
if write_when_new(systemdir / f'{service}.service', servicecfg, as_root=as_root):
|
||||
show.dirty = True
|
||||
reload_systemd = True
|
||||
reload_systemd.add(as_root)
|
||||
if servicecfg and to_start.get('service') is None:
|
||||
to_start[service] = 'restart'
|
||||
to_start[service] = 'restart', as_root
|
||||
|
||||
if 'dialout' not in unix_cmd('id l_samenv'):
|
||||
do_cmd('usermod -a -G dialout l_samenv')
|
||||
pip()
|
||||
if reload_systemd:
|
||||
do_cmd('systemctl daemon-reload')
|
||||
for service, action in to_start.items():
|
||||
for as_root in reload_systemd:
|
||||
if as_root:
|
||||
do_cmd('systemctl daemon-reload', sudo=True)
|
||||
else:
|
||||
do_cmd('systemctl --user daemon-reload', sudo=False)
|
||||
for service, (action, _) in to_start.items():
|
||||
show.dirty = True
|
||||
if action == 'if_restart':
|
||||
do_cmd(f'ifdown {service}')
|
||||
for service, action in to_start.items():
|
||||
for service, (action, as_root) in to_start.items():
|
||||
if action == 'if_restart':
|
||||
do_cmd(f'ifup {service}')
|
||||
else:
|
||||
if action == 'restart':
|
||||
do_cmd(f'systemctl restart {service}')
|
||||
do_cmd(f'systemctl enable {service}')
|
||||
if action == 'restart': # else 'enable'
|
||||
do_cmd('systemctl', 'restart', service, sudo=as_root)
|
||||
if not as_root:
|
||||
do_cmd(f'loginctl enable-linger l_samenv')
|
||||
do_cmd('systemctl', 'enable', service, sudo=as_root)
|
||||
|
||||
if box.change_if_names:
|
||||
print('interface name system has to be changed from enp*s0 to eth*')
|
||||
@ -674,7 +722,6 @@ def handle_config():
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
more_info = False
|
||||
doit = False
|
||||
print(' ')
|
||||
show = Show()
|
||||
|
21
utils.py
21
utils.py
@ -202,17 +202,22 @@ class MainIf:
|
||||
|
||||
def unix_cmd(cmd, *args, execute=None, stdout=PIPE, sudo=True):
|
||||
command = cmd.split() + list(args)
|
||||
sudo = ['sudo'] if sudo else []
|
||||
if command[0] == 'systemctl' and not sudo:
|
||||
command.insert(1, '--user')
|
||||
if sudo:
|
||||
command.insert(0, 'sudo')
|
||||
# sudo = ['sudo'] if sudo else []
|
||||
if execute is not False: # None or True
|
||||
if execute:
|
||||
print('$', *command)
|
||||
result = Popen(sudo + command, stdout=stdout).communicate()[0]
|
||||
# result = Popen(sudo + command, stdout=stdout).communicate()[0]
|
||||
result = Popen(command, stdout=stdout).communicate()[0]
|
||||
return (result or b'').decode()
|
||||
else:
|
||||
print('>', *command)
|
||||
|
||||
|
||||
def check_service(service, set_on=None, execute=None):
|
||||
def check_service(service, set_on=None, execute=None, as_root=True):
|
||||
"""check or set state of systemd service
|
||||
|
||||
set_on is None or not given: query only
|
||||
@ -221,7 +226,7 @@ def check_service(service, set_on=None, execute=None):
|
||||
|
||||
sim: print out command instead of executing
|
||||
"""
|
||||
result = unix_cmd(f'systemctl show -p WantedBy -p ActiveState {service}')
|
||||
result = unix_cmd(f'systemctl show -p WantedBy -p ActiveState {service}', sudo=False)
|
||||
enabled = False
|
||||
active = False
|
||||
for line in result.split('\n'):
|
||||
@ -231,14 +236,14 @@ def check_service(service, set_on=None, execute=None):
|
||||
active = True
|
||||
if set_on:
|
||||
if not active:
|
||||
unix_cmd(f'systemctl start {service}', execute=execute)
|
||||
unix_cmd('systemctl', 'start', service, execute=execute, sudo=as_root)
|
||||
if not enabled:
|
||||
unix_cmd(f'systemctl enable {service}', execute=execute)
|
||||
unix_cmd('systemctl', 'enable', service, execute=execute, sudo=as_root)
|
||||
elif set_on is not None:
|
||||
if active:
|
||||
unix_cmd(f'systemctl stop {service}', execute=execute)
|
||||
unix_cmd('systemctl', 'stop', service, execute=execute, sudo=as_root)
|
||||
if enabled:
|
||||
unix_cmd(f'systemctl disable {service}', execute=execute)
|
||||
unix_cmd('systemctl', 'disable', service, execute=execute, sudo=as_root)
|
||||
return active, enabled
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user