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