diff --git a/install.py b/install.py index c137613..5c6b015 100755 --- a/install.py +++ b/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 of , 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() diff --git a/to_system/home/l_samenv/.bash_profile b/to_home/.bash_profile similarity index 100% rename from to_system/home/l_samenv/.bash_profile rename to to_home/.bash_profile diff --git a/to_system/home/l_samenv/.config/linse_profile b/to_home/.config/linse_profile similarity index 100% rename from to_system/home/l_samenv/.config/linse_profile rename to to_home/.config/linse_profile diff --git a/to_system/home/l_samenv/.config/linse_setuser b/to_home/.config/linse_setuser similarity index 100% rename from to_system/home/l_samenv/.config/linse_setuser rename to to_home/.config/linse_setuser diff --git a/utils.py b/utils.py index 1794c83..47f7b21 100644 --- a/utils.py +++ b/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