From e344108e518eba54fb12c0bb6ad0b8616eb635e9 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 4 Apr 2025 15:36:03 +0200 Subject: [PATCH] make install work also for rpi boxes - in addition to to_system, we have now to_ with every type of box - install is not needed to run under sudo anymore --- README.md | 30 ++- install.py | 206 ++++++++++++------ to_dual-eth-rpi/boot/config.txt | 51 +++++ to_dual-eth-rpi/etc/udev/rules.d/__delete__ | 2 + to_ionopi/boot/config.txt | 51 +++++ to_ionopi/etc/udev/rules.d/99-ionopi.rules | 1 + to_ionopi/etc/udev/rules.d/__delete__ | 1 + to_ionopimax/boot/config.txt | 51 +++++ .../etc/udev/rules.d/99-ionopimax.rules | 1 + to_ionopimax/etc/udev/rules.d/__delete__ | 1 + utils.py | 38 ++-- 11 files changed, 343 insertions(+), 90 deletions(-) create mode 100755 to_dual-eth-rpi/boot/config.txt create mode 100644 to_dual-eth-rpi/etc/udev/rules.d/__delete__ create mode 100755 to_ionopi/boot/config.txt create mode 100644 to_ionopi/etc/udev/rules.d/99-ionopi.rules create mode 100644 to_ionopi/etc/udev/rules.d/__delete__ create mode 100755 to_ionopimax/boot/config.txt create mode 100644 to_ionopimax/etc/udev/rules.d/99-ionopimax.rules create mode 100644 to_ionopimax/etc/udev/rules.d/__delete__ diff --git a/README.md b/README.md index f0991d2..410b6de 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # Installation tools for APUs and Raspberry Pi boxes -The APU is a Linux box to be used as a control box at LIN sample environment +Supports teh following boxes used at LIN sample environment: + +- bare-apu: Linux box +- controlbox: from FRM2 +- ionopimax: PLC from Sfera Labs with compute module 3 +- ionopi: PLC from Sfrea labs with Raspberry Pi 3 +- dual-eth-rpi: dual ethernet Raspberry Pi with compute module 4 `cfg/` contains the configuration files for all boxes `to_system/` contains the files to be installed in the system -We have also the Raspberry based PLC Iono Pi Max -and other Raspberry Pi based boxes. +`to_/` system files specific for a box (box names as in above list) + ## config files @@ -16,9 +22,12 @@ filename: cfg/(hostname)_(hexdigits).cfg * (hostname) the hostname to be given on startup * (hexdigits) the last six digits of the ethernet adress of the first interface on the APU (leftmost plug) -content of the config files: +### example content of a config files for a control box: ``` +[BOX] +type=controlbox + [NETWORK] eth0=192.168.127.254 # leftmost socket: connect here a moxa with factory settings eth1=192.168.2.2 # connected device will get this ip via DHCP @@ -38,6 +47,19 @@ port=5000 # the port for the frappy server startup_text=startup...|HOST|ADDR # startup text, 3 lines separated with | ``` +the [BOX] section is optional for controlbox and bare-apu + + +### example content of a config files for a control box: + +``` +[BOX] +type=ionopi + +[NETWORK] +eth0=wan +``` + ## network configuration The example above fits the most cases. diff --git a/install.py b/install.py index a03870c..551827a 100755 --- a/install.py +++ b/install.py @@ -12,37 +12,36 @@ if bytes == str: import sys import os import filecmp -import shutil import re import types import socket +from pathlib import Path from subprocess import Popen, PIPE from ipaddress import IPv4Interface -from os.path import join, getmtime, exists, basename +from os.path import getmtime -if os.geteuid() != 0: - exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") def exit(): print('please restart sudo ./install.py again') sys.exit() + try: import serial except ImportError: if 'yes'.startswith(input('install pyserial? [y]')): - os.system('pip3 install --break-system-packages pyserial') + os.system('sudo pip3 install --break-system-packages pyserial') serial = None try: - from utils import BoxInfo, check_service, unix_cmd, change_firewall + from utils import BoxInfo, check_service, unix_cmd, change_firewall, UndefinedConfigFile except ImportError: if 'yes'.startswith(input('install netifaces? [y]')): - os.system('pip3 install --break-system-packages netifaces') + os.system('sudo pip3 install --break-system-packages netifaces') serial = None if serial is None: - print('please restart sudo ./install.py again') + print('please restart ./install.py again') exit() @@ -51,36 +50,62 @@ TOOLS = BoxInfo.TOOLS more_info = False DEL = '__to_delete__' -STARTUP_TEXT = f'{TOOLS}/startup_display.txt' +STARTUP_TEXT = TOOLS / 'startup_display.txt' COMMENT = "; please refer to README.md for help" -CONFIG_TEMPLATE = f"""[NETWORK] -{COMMENT} +TEMPLATES = {} +TEMPLATES['bare-apu'] = f"""{COMMENT} + +[BOX] +type=bare-apu + +[NETWORK] eth0=wan eth1=192.168.2.2 eth2=192.168.3.3 eth3=192.168.127.254 [ROUTER] -{COMMENT} 3001=192.168.127.254:3001 """ -CONTROLBOX_TEMPLATE = f"""[NETWORK] -{COMMENT} -eth0=dhcp +TEMPLATES['controlbox'] = f"""{COMMENT} + +[BOX] +type=controlbox + +[NETWORK] +eth0=wan eth1=192.168.1.1 eth2=192.168.2.2 eth3=192.168.3.3 [DISPLAY] -{COMMENT} line0=startup... line1=HOST line2=ADDR """ +TEMPLATES['dual-eth-rpi'] = f"""{COMMENT} + +[BOX] +type=dual-eth-rpi + +[NETWORK] +eth0=wan +eth1=192.168.1.1 +""" + +GENERIC_TEMPLATE = f"""{COMMENT} + +[BOX] +type=%s + +[NETWORK] +eth0=wan +""" + DHCP_HEADER = """ default-lease-time 600; max-lease-time 7200; @@ -133,14 +158,22 @@ box = BoxInfo() box.hostname_changed = False dhcp_server_cfg = [] -TO_SYSTEM = [f'{TOOLS}/to_system', f'{TOOLS}/{box.typ}_system'] +TO_SYSTEM = [TOOLS / 'to_system', TOOLS / f'{box.typ}_system'] -def do_cmd(command): - unix_cmd(command, doit) +def do_cmd(*command): + unix_cmd(*command, execute=doit) # with sudo show.dirty = True +def copyfiles(srcdir, dstdir, files): + unix_cmd('cp', *(str(srcdir / f) for f in files), str(dstdir)) + + +def deletefiles(srcdir, files): + unix_cmd('rm', '-f', *(str(srcdir / f) for f in files)) + + def frappy(cfg=None, port=None, requirements='', **kwds): if not cfg: return None @@ -173,8 +206,8 @@ def router(firewall=False, **opts): if not opts: return None try: - os.remove(join(TO_SYSTEM[0], 'etc/nftables.conf')) - with open(f'{TOOLS}/requirements.txt') as f: + os.remove(TO_SYSTEM[0] / 'etc/nftables.conf') + with open(TOOLS / 'requirements.txt') as f: pip_requirements['root']['tools'] = f.read() except FileNotFoundError: pass @@ -202,18 +235,21 @@ def display(**opts): def pip(): for user, requirements in pip_requirements.items(): if user == 'root': - tmpname = join('/root', 'pip_requirements.tmp') - pipcmd = 'pip3 install -r %s' % tmpname + tmpname = '/root/pip_requirements.tmp' + pipcmd = ['sudo pip3 install -r', tmpname] else: - tmpname = join('/home', user, 'pip_requirements.tmp') - pipcmd = 'sudo --user %s pip3 install --user --break-system-packages -r %s' % (user, tmpname) + tmpname = f'/home/{user}/pip_requirements.tmp' + pipcmd = ['pip3 install --user --break-system-packages -r', tmpname] filename = tmpname.replace('.tmp', '.txt') content = ''.join('# --- for %s ---\n%s\n' % kv for kv in requirements.items()) - if write_when_new(filename, content, True): - if doit: - os.rename(filename, tmpname) - if os.system(pipcmd) == 0: - os.rename(tmpname, filename) + if content: + if write_when_new(filename, content, True): + if doit: + os.rename(filename, tmpname) + if os.system(' '.join(pipcmd)) == 0: + os.rename(tmpname, filename) + else: + os.remove(tmpname) else: os.remove(tmpname) else: @@ -225,7 +261,7 @@ def pip(): SERVICES = dict(router=router, frappy=frappy, display=display) -def write_when_new(filename, content, ignore_reduction=False): +def write_when_new(filename, content, as_root=False, ignore_reduction=False): if content is None: lines = [] else: @@ -237,6 +273,9 @@ def write_when_new(filename, content, ignore_reduction=False): old = fil.read() except FileNotFoundError: old = None + #except Exception as e: + # print('can not read', filename, '-> do not check') + # return False if old == content: return False if ignore_reduction: @@ -249,10 +288,12 @@ def write_when_new(filename, content, ignore_reduction=False): return False if doit: if lines: - with open(filename, 'w') as fil: + with tempfile.NamedTemporaryFile('w') as fil: fil.write(content) + fil.flush() + unix_cmd('cp', fil.name, filename, sudo=as_root) else: - os.remove(filename) + unix_cmd('rm', '-f', filename, sudo=as_root) elif more_info: print('.' * 80) print('changes in', filename) @@ -314,7 +355,7 @@ def replace_in_file(filename, pat, repl): if not success: raise ValueError(f'{pat} not in {filename}') - write_when_new(filename, newcontent) + write_when_new(filename, newcontent, as_root=True) return success[0] @@ -356,17 +397,17 @@ def create_if(name, cfg): def walk(action): for rootpath in TO_SYSTEM: - if not exists(rootpath): + if not rootpath.exists(): continue os.chdir(rootpath) for dirpath, _, files in os.walk('.'): - syspath = dirpath[1:] # remove leading '.' - action.dirpath = dirpath + syspath = Path(dirpath[1:]) # remove leading '.' -> absolute path + action.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(join(syspath, f)) > getmtime(join(dirpath, f))] + newer = [f for f in mismatch if getmtime(syspath / f) > getmtime(dirpath / f)] if newer: action.newer(newer) if len(newer) < len(mismatch): @@ -375,12 +416,12 @@ def walk(action): if missing: if DEL in missing: missing.remove(DEL) - with open(join(dirpath, DEL)) as fil: + with open(dirpath / DEL) as fil: to_delete = [] for fname in fil: fname = fname.strip() - if fname and exists(join(syspath, fname)): - if exists(join(dirpath, fname)): + 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) @@ -390,9 +431,8 @@ def walk(action): class Walker: - def __init__(self): - self.dirpath = None - self.syspath = None + dirpath = None + syspath = None class Show(Walker): @@ -403,7 +443,7 @@ class Show(Walker): if more_info: for f in files: if f.endswith(more_info): - print('diff %s %s' % (join(self.dirpath, f), join(self.syspath, f))) + print('diff %s %s' % (self.dirpath / f, self.syspath / f)) print('.' * 80) else: print('%s %s:\n %s' % (title, self.syspath, ' '.join(files))) @@ -412,7 +452,7 @@ class Show(Walker): self.dirty = True if more_info: for f in files: - print('cat %s' % join(dirpath, f)) + print('cat %s' % (dirpath / f)) print('.' * 80) else: print('%s %s:\n %s' % (title, self.syspath, ' '.join(files))) @@ -432,41 +472,65 @@ class Show(Walker): class Do(Walker): def newer(self, files): - for file in files: - shutil.copy(join(self.syspath, file), join(self.dirpath, file)) + copyfiles(self.syspath, self.dirpath, files) - def older(self, files): - for file in files: - shutil.copy(join(self.dirpath, file), join(self.syspath, file)) + older = newer - def missing(self, files): - self.older(files) + missing = newer def delete(self, files): - for file in files: - os.remove(join(self.syspath, file)) + deletefiles(self.syspath, files) def handle_config(): dhcp_server_cfg.clear() - config = box.read_config() + try: + config = box.read_config() + except UndefinedConfigFile as e: + print(e) cfgfile = box.cfgfile newhostname = box.hostname - if not cfgfile: - template = CONFIG_TEMPLATE + if cfgfile: + if box.hwtype != 'apu': + try: + typ = config.get('BOX', {}).get('type') + if typ: + box.typ = typ + except ValueError: + pass + else: + if box.typ == 'cm4': + print('WARNING: missing type in BOX section') + print('This is a cm4 so guess its a dual-eth-rpi') + box.typ = 'dual-eth-rpi' + elif box.typ == 'cm3': + print('WARNING: missing type in BOX section') + print('This is a cm3 so guess its a ionopimax') + box.typ = 'ionopimax' + else: + raise ValueError(f'unknown type={typ} defined in BOX section') + else: if box.typ == 'apu': # determine if display is present disp = serial.Serial('/dev/ttyS1', baudrate=115200, timeout=1) disp.write(b'\x1b\x1b\x01\xf3') display_available = disp.read(8)[0:4] == b'\x1b\x1b\x05\xf3' if display_available: - typ = 'controlbox' - template = CONTROLBOX_TEMPLATE + box.typ = 'controlbox' else: - typ = 'bare apu' - else: - typ = 'rpi' - print('no cfg file found for this', typ, + box.typ = 'bare-apu' + elif box.typ == 'cm4': + box.typ = 'dual-eth-rpi' + print('This is a cm4, so guess its a dual-eth-rpi - please check type in cfg file') + elif box.typ == 'cm3': + print('This is a cm3 so guess its a ionopimax - please check type in cfg file') + box.typ = 'ionopimax' + template = TEMPLATES.get(box.typ) + if template is None: + box.typ = box.typ or 'unknown-box' + template = GENERIC_TEMPLATE % box.typ + + print('no cfg file found for this', box.typ, f'with id {box.id:06x} (hostname={box.hostname})') newhostname = input('enter host name: ') if not newhostname: @@ -483,7 +547,7 @@ def handle_config(): newhostname = basename(cfgfile).rpartition('_')[0] if doit: print('bash sethostname.sh') - os.system(f'bash {TOOLS}/sethostname.sh') + unix_cmd('bash', f'{TOOLS}/sethostname.sh') else: if cfgfile: print('replace host name %r by %r' % (box.hostname, newhostname)) @@ -498,12 +562,14 @@ def handle_config(): netcfg = config['NETWORK'] for name in netcfg: if name not in box.macaddr: + print(name) + print(box.macaddr) print(f'{name} is not a valid network interface name') raise RuntimeError('network interface name system does not match') for ifname in box.macaddr: content = create_if(ifname, netcfg.get(ifname, 'off')) # content = '\n'.join('%s=%s' % kv for kv in content.items()) - todo = write_when_new(f'/etc/network/interfaces.d/{ifname}', content) + todo = write_when_new(f'/etc/network/interfaces.d/{ifname}', content, as_root=True) if todo and not more_info: print('change', ifname) show.dirty = True @@ -522,7 +588,7 @@ def handle_config(): content.append(' range %s %s;\n' % rng) content.append('}\n') content = ''.join(content) - todo = write_when_new('/etc/dhcp/dhcpd.conf', content) + todo = write_when_new('/etc/dhcp/dhcpd.conf', content, as_root=True) if todo: print('change dhcpd.conf') to_start['isc-dhcp-server'] = 'restart' @@ -532,7 +598,7 @@ def handle_config(): r'INTERFACESv4="(.*)"', ' '.join([n for n in box.macaddr if n != box.main_if])) except FileNotFoundError: if 'yes'.startswith(input('install isc-dhcp-server? [y]')): - os.system('apt-get install isc-dhcp-server') + unix_cmd('apt-get install isc-dhcp-server') exit() elif doit: unix_cmd('systemctl stop isc-dhcp-server') @@ -560,7 +626,7 @@ def handle_config(): to_start[service] = 'enable' elif not active: to_start[service] = 'restart' - if write_when_new(f'/etc/systemd/system/{service}.service', servicecfg): + if write_when_new(f'/etc/systemd/system/{service}.service', servicecfg, as_root=True): show.dirty = True reload_systemd = True if servicecfg and to_start.get('service') is None: @@ -618,7 +684,7 @@ else: doit = True handle_config() walk(Do()) - with open(f'{TOOLS}/current', 'w') as f: + with open(TOOLS / 'current', 'w') as f: f.write(result) elif 'more'.startswith(answer.lower()): more_info = True diff --git a/to_dual-eth-rpi/boot/config.txt b/to_dual-eth-rpi/boot/config.txt new file mode 100755 index 0000000..a777e40 --- /dev/null +++ b/to_dual-eth-rpi/boot/config.txt @@ -0,0 +1,51 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +dtparam=i2c_arm=off +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] +# runtime clock +dtoverlay=mcp7941x + diff --git a/to_dual-eth-rpi/etc/udev/rules.d/__delete__ b/to_dual-eth-rpi/etc/udev/rules.d/__delete__ new file mode 100644 index 0000000..cb77997 --- /dev/null +++ b/to_dual-eth-rpi/etc/udev/rules.d/__delete__ @@ -0,0 +1,2 @@ +99-ionopimax.rules +99-ionopi.rules \ No newline at end of file diff --git a/to_ionopi/boot/config.txt b/to_ionopi/boot/config.txt new file mode 100755 index 0000000..40b2cda --- /dev/null +++ b/to_ionopi/boot/config.txt @@ -0,0 +1,51 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +dtparam=i2c_arm=off +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] +dtoverlay=ionopi +# runtime clock +dtoverlay=i2c-rtc,mcp7941x diff --git a/to_ionopi/etc/udev/rules.d/99-ionopi.rules b/to_ionopi/etc/udev/rules.d/99-ionopi.rules new file mode 100644 index 0000000..99d5520 --- /dev/null +++ b/to_ionopi/etc/udev/rules.d/99-ionopi.rules @@ -0,0 +1 @@ +SUBSYSTEM=="ionopi", PROGRAM="/bin/sh -c 'find -L /sys/class/ionopi/ -maxdepth 2 -exec chown root:ionopi {} \; || true'" \ No newline at end of file diff --git a/to_ionopi/etc/udev/rules.d/__delete__ b/to_ionopi/etc/udev/rules.d/__delete__ new file mode 100644 index 0000000..5ff0ff1 --- /dev/null +++ b/to_ionopi/etc/udev/rules.d/__delete__ @@ -0,0 +1 @@ +99-ionopimax.rules \ No newline at end of file diff --git a/to_ionopimax/boot/config.txt b/to_ionopimax/boot/config.txt new file mode 100755 index 0000000..0a6b8f0 --- /dev/null +++ b/to_ionopimax/boot/config.txt @@ -0,0 +1,51 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +dtparam=i2c_arm=off +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] +dtoverlay=ionopimax +# runtime clock +dtoverlay=i2c-rtc,mcp7941x diff --git a/to_ionopimax/etc/udev/rules.d/99-ionopimax.rules b/to_ionopimax/etc/udev/rules.d/99-ionopimax.rules new file mode 100644 index 0000000..61d2641 --- /dev/null +++ b/to_ionopimax/etc/udev/rules.d/99-ionopimax.rules @@ -0,0 +1 @@ +SUBSYSTEM=="ionopimax", PROGRAM="/bin/sh -c 'find -L /sys/class/ionopimax/ -maxdepth 2 -exec chown root:ionopimax {} \; || true'" \ No newline at end of file diff --git a/to_ionopimax/etc/udev/rules.d/__delete__ b/to_ionopimax/etc/udev/rules.d/__delete__ new file mode 100644 index 0000000..f992ea9 --- /dev/null +++ b/to_ionopimax/etc/udev/rules.d/__delete__ @@ -0,0 +1 @@ +99-ionopi.rules diff --git a/utils.py b/utils.py index 0c154fc..82e63bd 100644 --- a/utils.py +++ b/utils.py @@ -3,6 +3,7 @@ import socket import threading import re from glob import glob +from pathlib import Path from configparser import ConfigParser from netifaces import interfaces, ifaddresses, gateways, AF_INET, AF_LINK from subprocess import Popen, PIPE @@ -19,13 +20,18 @@ else: os.system(cmd) +class UndefinedConfigFile(Exception): + """config file not found or ambiguous""" + + class BoxInfo: - TOOLS = '/home/l_samenv/boxtools' - CFGPATH = f'{TOOLS}/cfg/%s_%06x.cfg' + TOOLS = Path('/home/l_samenv/boxtools') + CFGPATH = str(TOOLS / 'cfg' / '%s_%06x.cfg') BOX_TYPES = { '00:0d:b9': 'apu', # bare apu or control box - 'b8:27:eb': 'cm3', # iono pi - 'd8:3a:dd': 'cm4', # dual-eth-rpi + 'b8:27:eb': 'cm3', # guess iono pi max + 'e4:5f:01': 'ionopi', # guess iono pi + 'd8:3a:dd': 'cm4', # guess dual-eth-rpi } def __init__(self): @@ -50,6 +56,7 @@ class BoxInfo: self.id = int(''.join(addr.split(':')[-3:]), 16) & 0xffffff self.typ = self.BOX_TYPES.get(addr[:8]) self.main_if = ifname + self.hwtype = self.typ # this is one of the values in BOX_TYPE and will not change def get_macaddr(self): return self.macaddr.get(self.main_if) @@ -57,9 +64,9 @@ class BoxInfo: def read_config(self, section=None): cfgfiles = glob(self.CFGPATH % ('*', self.id)) if len(cfgfiles) > 1: - raise ValueError('ambiguous cfgfile: %r' % cfgfiles) + raise AmbiguousConfigFile('ambiguous cfgfile: %r' % cfgfiles) if section and not cfgfiles: - raise ValueError('no cfg file found for %s' % self.id) + raise UndefinedConfigFile('no cfg file found for %s' % self.id) if cfgfiles: self.cfgfile = cfgfiles[0] else: @@ -141,14 +148,16 @@ class MainIf: return self.carrier, self.ip, self.hostnameresult[0], self.gateway -def unix_cmd(command, execute=None, stdout=PIPE): - if execute != False: # None or True +def unix_cmd(cmd, *args, execute=None, stdout=PIPE, sudo=True): + command = cmd.split() + list(args) + sudo = ['sudo'] if sudo else [] + if execute is not False: # None or True if execute: - print('$ %s' % command) - result = Popen(command.split(), stdout=stdout).communicate()[0] + print('$', *command) + result = Popen(sudo + command, stdout=stdout).communicate()[0] return (result or b'').decode() else: - print('> %s' % command) + print('>', *command) def check_service(service, set_on=None, execute=None): @@ -185,15 +194,12 @@ def change_firewall(set_on, ports, execute=None): ports.add(22) # always add ssh active, enabled = check_service('nftables') if not set_on: - if os.geteuid() == 0: - check_service('nftables', False, execute) - else: - print('need sudo rights to modify firewall') + check_service('nftables', False, execute) return active or enabled pattern = re.compile('(tcp dport ({.*}) ct state new accept)', re.MULTILINE | re.DOTALL) - for filename in FIREWALL_CONF, os.path.join(BoxInfo.TOOLS, 'nftables.conf'): + for filename in FIREWALL_CONF, BoxInfo.TOOLS / 'nftables.conf': with open(filename) as f: content = f.read()