Merge branch 'master' of gitlab.psi.ch:samenv/aputools

This commit is contained in:
2024-02-20 11:20:58 +01:00
8 changed files with 754 additions and 89 deletions

111
README.md
View File

@ -2,23 +2,23 @@
The APU is a Linux box to be used as a control box at LIN sample environment
servercfg/ contains the configuration files for all boxes
`servercfg/` contains the configuration files for all boxes
to_system/ contains the files to be installed in the system
`to_system/` contains the files to be installed in the system
## config files
filename: servercfg/(hostname)_(hexdigits).cfg
* (hostname) the hostname to be given on startup
* (hexdigits) the last six digits of one of the ethernet adresses of the interfaces on the APU
* (hexdigits) the last six digits of the ethernet adress of the first interface on the APU (leftmost plug)
content of the config files:
```
[NETWORK]
enp1s0=192.168.127.254 # leftmost socket: connect here a moxa with factory settings
enp2s0=192.168.2.2 # connected device will get this ip via DHCP
enp2s0=192.168.2.2 # connected device will get this ip via DHCP
enp3s0=192.168.3.3 # or must be configured static to this IP
enp4s0=dhcp # rightmost socket is the uplink
@ -30,17 +30,24 @@ enp4s0=dhcp # rightmost socket is the uplink
[FRAPPY]
cfg=uniax # the cfg file for the frappy server
port=5000 # the port for the frappy server
[DISPLAY]
startup_text=startup...|HOST|ADDR # startup text, 3 lines separated with |
```
## network configuration
The example above fits the most cases. Here a detailed description of possible settings:
The example above fits the most cases.
Remark: as the control boxes (MLZ type) are labelled with 'eth0' etc., the names might be given in this form.
Internally the more modern names like 'enp1s0' are used.
Here a detailed description of possible settings:
### fixed IP
```
enp2s0=192.168.3.5
```
Configure the IP address of the connected device to get the specified IP via DHCP or
Configure the IP address of the connected device to get the specified IP via DHCP or
configure the device to with this static IP. The last number must be within 1..254,
the APU port itself will get 1 (or 2, if the specified IP is 1).
@ -56,7 +63,7 @@ Probably needed rarely.
### uplink via DHCP
```
enp4s0=wan # or enp4s0=dhcp
enp4s0=wan # or enp4s0=dhcp
```
Uplink configured for DHCP in any network n.x.y.z with n < 192
@ -72,5 +79,95 @@ Must not overlap networks specified for other ports!
enp3s0=off # disabled
```
## display
available on control boxes (MLZ type) only. must therefore not be present on bare apu boxes.
## Installation of a Fresh Control Box or bare APU
## install image by means of the USB stick BOOT_TINY
The stick has TinyLinux on it and some additional scripts
If you do not have one, you may create it from another box
### a) create a USB stick with TinyLinux (omit if you have the stick already)
log in as root/FrappyLinse to an apu box
```
apu> cd aputools
apu> bash mkusb.sh
```
You are asked to give the device name from a list (typically sdb)
before it writes to the stick.
### b) boot with TinyLinux from USB stick
Connect a Mac with an USB to serial adapter (and null-modem!).
```
mac> screen /dev/tty.usbserial-130 115200
```
Do not yet conenct to LAN, if the box is not yet registered to PacketFence.
Plug USB stick and start the box, wait for prompt 'tc@box:' and cd into BOOT_TINY
```
cd /media/BOOT_TINY
```
### c) Determine address/name for registering in PacketFence
```
sh reg
```
enter new hostname (HWaddr will be on the output)
register to PacketFence and set role to sinq-samenv
connect LAN to rightmost socket on a bare APU or the leftmost socket on a control box
### d) Copy Image to APU SSD
```
sh write_to_ssd
```
some random art images are shown ...
```
images from l_samenv@samenv:boxes/images:
apumaster_2022-11-09.lz4
apumaster_2024-01-18.lz4
which image?
```
* Enter the image you want to write (typically the last one).
* It will take around 10 mins to write the image.
* remove the stick, power off/on (or do `sudo reboot now`)
### e) Install Services
login with root/FrappyLinse
```
> cd aputools
> git pull
> python3 install.py
...
enter host name:
...
> reboot now
```
DONE!
### f) Cloning an Image from an Existing Box
Use (b) above to boot from the BOOT_TINY USB stick
```
$ sh clone
```
You are asked

309
display.py Normal file
View File

@ -0,0 +1,309 @@
import sys
import os
import time
import serial
import socket
import threading
# display tty device
tty = '/dev/ttyS1'
STARTUP_TEXT = '/root/aputools/startup_display.txt'
ESC = 0x1b
ESCESC = bytes((ESC, ESC))
MODE_GRAPH = 0x20
MODE_CONSOLE = 0x21
SET_POS = 0x30
SET_FONT = 0x31
SET_COLOR = 0x32
CLEAR = 0x40
LINES = 0x41
RECT = 0x42
ICON = 0x43
TEXT = 0x44
COPYRECT = 0x45
PLOT = 0x46
TOUCH = 0x50
TOUCH_MODE = 0x51
SAVE_ATTRS = 0xa0
SEL_ATTRS = 0xc0
SET_STARTUP = 0xf2
IDENT = 0xf3
FONT_GEO = [(6, 8), (8, 16), (20, 40), (38,64), (16, 16), (12, 24)]
TOPGAP = 8 # upper part of display is not useable
HEIGHT = 120
WIDTH = 480
MAGIC = b'\xcb\xef\x20\x18'
def xy(x, y):
x = min(480, int(x))
return bytes([(min(127, y + TOPGAP) << 1) + (x >> 8), x % 256])
class Display:
fg = 15
bg = 0
touch = 0
bar = None
blink = False
menu = None
storage = None
def __init__(self, dev, timeout=0.5, daemon=False):
self.event = threading.Event()
self.term = serial.Serial(dev, baudrate=115200, timeout=timeout)
self.storage = bytearray()
if daemon:
todo_file = STARTUP_TEXT + '.todo'
try:
with open(todo_file) as f:
text = f.read()
print('new startup text:')
print(text)
except FileNotFoundError:
text = None
if text:
self.reset()
self.show(*text.split('\n')[0:3])
self.set_startup()
os.rename(todo_file, STARTUP_TEXT)
else:
self.storage = None
else:
os.system('systemctl stop display')
self.gethost(False)
self.reset()
if daemon:
threading.Thread(target=self.gethostthread, daemon=True).start()
self.event.wait(1) # wait for thread to be started
self.net_display(None)
def reset(self):
self.send(MODE_GRAPH)
self.font(2)
self.send(CLEAR, self.bg)
def color(self, fg=15, bg=0):
self.fg = fg
self.bg = bg
self.send(SET_COLOR, bg, bg, fg, fg)
def send(self, code, *args):
out = bytearray([code])
for arg in args:
if isinstance(arg, str):
out.extend(arg.encode('latin-1'))
elif hasattr(arg, '__len__'):
out.extend(arg)
else:
out.append(arg)
cmd = bytearray([ESC,ESC,len(out)]) + out
if self.storage is not None:
self.storage.extend(cmd)
self.term.write(cmd)
def set_startup(self):
if self.storage is None:
print('storage off')
return
data = self.storage
self.storage = None
self.send(SET_STARTUP, data)
def version(self):
self.term.write(bytes([ESC,ESC,1,0xf3]))
reply = self.term.read(4)
assert reply[0:2] == ESCESC
return self.term.read(reply[2]-1)
def font(self, size):
self.fontsize = size
self.send(SET_FONT, size)
self.colwid, self.rowhei = FONT_GEO[self.fontsize]
self.nrows = HEIGHT // self.rowhei
self.ncols = WIDTH // self.colwid
self.textbuffer = [" " * self.ncols] * self.nrows
self.color()
def text(self, text, row=0, left=0, right=None, font=2):
if font != self.fontsize:
self.font(font)
if right is None:
right = self.ncols
if right < 0:
right += self.ncols
if left < 0:
left += self.ncols
for line in text.split('\n'):
padded = line + " " * (right - left - len(line))
self.send(SET_POS, xy(left * self.colwid, TOPGAP + row * self.rowhei))
self.send(TEXT, padded.encode('latin-1'))
def show(self, *args):
self.send(CLEAR, self.bg)
if len(args) == 1:
self.text(args[0], 1)
else:
for row, line in enumerate(args):
if row < 3:
self.text(line, row)
def gethost(self, truehostname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('8.8.8.8', 80)) # 8.8.8.8: google DNS
self.hostip = s.getsockname()[0]
except Exception as e:
self.hostip = ''
hostname = None
if self.hostip and truehostname:
try:
hostname = socket.gethostbyaddr(self.hostip)[0]
except Exception:
pass
self.hostname = hostname or socket.gethostname()
def gethostthread(self):
self.gethost(True)
self.event.set()
time.sleep(1)
while True:
self.event.clear()
self.gethost(True)
self.event.wait(1)
def set_touch(self, on):
self.send(TOUCH_MODE, on)
self.touch = on
def gettouch(self):
if not self.touch:
self.set_touch(1)
while True:
ch2 = self.term.read(2)
while ch2 != ESCESC:
if len(ch2) < 2:
return
ch2 = ch2[1:2] + self.term.read(1)
length = self.term.read(1)
if not length:
return
data = self.term.read(length[0])
if len(data) < length[0]:
return
if data[0] == TOUCH and len(data) == 3:
x = (data[1] % 2) * 256 + data[2]
return x
print('skipped', data)
def menu_reboot(self, x):
if x is None:
return
if x < 120:
self.show('reboot ...')
os.system('reboot now')
else:
self.std_display()
def menu_shutdown(self, x):
if x is None:
return
if x < 120:
self.show('shutdown ...')
os.system('shutdown now')
else:
self.std_display()
def menu_main(self, x):
if x is None:
return
print(x)
if x < 160:
self.std_display()
elif x < 320:
self.menu = self.menu_reboot
self.show('reboot?', '( OK ) (cancel)')
else:
self.menu = self.menu_shutdown
self.show('shutdown?', '( OK ) (cancel)')
def std_display(self):
self.menu = self.net_display
self.net_display(None)
def net_display(self, x):
if x is None:
hostip = self.hostip or 'no network'
dotpos = hostip.find('.')
if dotpos < 0:
dotpos = len(hostip)
self.text(hostip.replace('.', ' ', 1), 0, 0, 15)
self.send(SET_COLOR, 0, 0, 0, 11 + self.blink) # yellow / blue
self.text('.', 0, dotpos, dotpos+1)
self.color()
self.text(self.hostname, 1)
self.event.set()
self.blink = not self.blink
else:
self.menu = self.menu_main
self.show('(cancel)(reboot)(shdown)')
def refresh(self):
func = self.menu or self.net_display
func(self.gettouch())
def bootmode(self):
self.send(0xf0, 0xcb, 0xef, 0x20, 0x18)
def pretty_version(v):
return '%c v%d.%d%s' % (v[0], v[2], v[3], ' test' if v[1] else '')
daemon = False
firmware = None
for arg in sys.argv[1:]:
if arg == '-d':
daemon = True
elif arg.endswith('.bin'):
firmware = arg
elif tty.startswith('/dev/'):
tty = arg
else:
raise ValueError(f'do not know how to tread argument: {arg}')
d = Display(tty, daemon=daemon and not firmware)
if firmware:
with open(firmware, 'rb') as f:
tag = f.read()[-8:]
if tag[:4] != MAGIC:
raise ValueError(f'{firmware} is not a valid firmware file')
hwversion = d.version()
if tag[4:] == hwversion:
# print('firmware is already', pretty_version(hwversion))
print('display version:', pretty_version(hwversion))
print('binfile version:', pretty_version(tag[4:]))
result = input('flash this (takes 1 min)? ').lower()
if result in ('y', 'yes'):
print('\ndo NOT interrupt')
d.bootmode()
d.term.close()
time.sleep(1.)
os.system(f'../stm32flash-0.7/stm32flash -R -v -b 115200 -w {firmware} {tty}')
if daemon:
while True:
d.refresh()

View File

@ -12,31 +12,49 @@ import sys
import os
import filecmp
import shutil
import serial
import types
import socket
from subprocess import Popen, PIPE
from glob import glob
from ipaddress import IPv4Interface
from configparser import ConfigParser
from os.path import join, getmtime, exists, basename
more_info = sys.argv[1] if len(sys.argv) > 1 else 'NONE'
more_info = False
os.chdir('/root/aputools/to_system')
DEL = '__to_delete__'
CFGPATH = '/root/aputools/servercfg/%s_%6.6x.cfg'
STARTUP_TEXT = '/root/aputools/startup_display.txt'
COMMENT = "; please refer to README.md for help"
CONFIG_TEMPLATE = """[NETWORK]
%s
CONFIG_TEMPLATE = f"""[NETWORK]
{COMMENT}
enp1s0=192.168.127.254
enp2s0=192.168.2.2
enp3s0=192.168.3.3
enp4s0=dhcp
[ROUTER]
%s
{COMMENT}
3001=192.168.127.254:3001
""" % (COMMENT, COMMENT)
"""
CONTROLBOX_TEMPLATE = f"""[NETWORK]
{COMMENT}
enp1s0=dhcp
enp2s0=192.168.1.1
enp3s0=192.168.2.2
enp4s0=192.168.3.3
[DISPLAY]
{COMMENT}
line0=startup...
line1=HOST
line2=ADDR
"""
DHCP_HEADER = """
default-lease-time 600;
@ -66,12 +84,51 @@ ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/secop-server %s
[Install]
WantedBy = multi-user.target
"""
DISPLAY_TEMPLATE = """[Unit]
Description = status display
After = network.target
[Service]
User = root
ExecStart = /usr/bin/python3 /root/aputools/display.py -d
[Install]
WantedBy = multi-user.target
"""
ifname_mapping = {
'eth0': 'enp1s0',
'eth1': 'enp2s0',
'eth2': 'enp3s0',
'eth3': 'enp4s0',
}
pip_requirements = {
'l_samenv': {},
'root': {}
}
net_addr = {} # dict <if name> of <ethernet address>
dhcp_server_cfg = [] # configuration for dhcp
main_info = { # info to be determined depending on cfg
'ifname': '', # name of interface of wan (dhcp) port
'addr': '', # addr of wan port
'current': None,
'hostname': socket.gethostname() # effective or given host name
}
for netif in os.scandir('/sys/class/net'):
if netif.name != 'lo': # do not consider loopback interface
with open(os.path.join(netif.path, 'address')) as f:
addr = f.read().strip().lower()
net_addr[netif.name] = addr
sorted_if = sorted(net_addr)
apuid = int(''.join(net_addr[sorted_if[0]].split(':')[-3:]), 16) & 0xfffffc
def frappy(cfg=None, port=None, requirements='', **kwds):
if not cfg:
return None
@ -95,6 +152,24 @@ def router(**opts):
return ROUTER_TEMPLATE
def display_update(cfg):
text = '\n'.join(cfg.get('startup_text', '').split('|')[:3])
text = text.replace('HOST', main_info['hostname']) \
.replace('ADDR', main_info['addr']) + '\n'
if write_when_new(STARTUP_TEXT, text):
print('change startup text')
if doit:
os.rename(STARTUP_TEXT, STARTUP_TEXT + '.todo')
return True
return False
def display(**opts):
if not opts:
return None
return DISPLAY_TEMPLATE
def pip():
for user, requirements in pip_requirements.items():
if user == 'root':
@ -118,7 +193,7 @@ def pip():
show.dirty = True
SERVICES = dict(router=router, frappy=frappy)
SERVICES = dict(router=router, frappy=frappy, display=display)
def unix_cmd(command, always=False, stdout=PIPE):
@ -159,7 +234,8 @@ def write_when_new(filename, content, ignore_reduction=False):
fil.write(content)
else:
os.remove(filename)
elif filename.endswith(more_info):
elif more_info:
print('.' * 80)
print('changes in', filename)
old = [] if old is None else old.split('\n')
top_lines = 0 # in case of empty loop
@ -183,10 +259,11 @@ def write_when_new(filename, content, ignore_reduction=False):
print('>>>')
print('\n'.join(old[-bottom_lines:-1]))
print("===")
return True
print('.' * 80)
return content
def create_if(name, cfg, mac, dhcp_server_cfg):
def create_if(name, cfg):
result = dict(
TYPE='Ethernet',
NAME=name,
@ -200,6 +277,10 @@ def create_if(name, cfg, mac, dhcp_server_cfg):
if cfg == 'off':
result['ONBOOT'] = 'no'
elif cfg.startswith('wan') or cfg == 'dhcp':
if main_info.get('mainif', name) != name:
raise ValueError('can not have more than one WAN/DHCP port')
main_info['mainif'] = name
main_info['addr'] = net_addr[name]
result['BOOTPROTO'] = 'dhcp'
# default: all <= 192.0.0.0
# others have to be added explicitly
@ -246,37 +327,44 @@ def walk(action):
missing.remove(DEL)
with open(join(dirpath, DEL)) as fil:
to_delete = []
for f in fil:
f = f.strip()
if f and exists(join(syspath, f)):
if exists(join(dirpath, f)):
print('ERROR: %s in %s, but also in repo -> ignored' % (f, DEL))
for fname in fil:
fname = fname.strip()
if fname and exists(join(syspath, fname)):
if exists(join(dirpath, fname)):
print('ERROR: %s in %s, but also in repo -> ignored' % (fname, DEL))
else:
to_delete.append(f)
to_delete.append(fname)
action.delete(to_delete)
if missing:
action.missing(missing)
class Show:
class Walker:
def __init__(self):
self.dirpath = None
self.syspath = None
class Show(Walker):
dirty = False
def diff(self, title, files):
self.dirty = True
if more_info == 'NONE':
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
else:
if more_info:
for f in files:
if f.endswith(MORE_INFO):
if f.endswith(more_info):
print('diff %s %s' % (join(self.dirpath, f), join(self.syspath, f)))
print('.' * 80)
else:
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
def show(self, title, dirpath, files):
if more_info == 'NONE':
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
else:
if more_info:
for f in files:
if f.endswith(MORE_INFO):
print('cat %s' % join(dirpath, f))
print('cat %s' % join(dirpath, f))
print('.' * 80)
else:
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
def newer(self, files):
self.show('get from', self.dirpath, files)
@ -291,7 +379,7 @@ class Show:
self.show('remove from', self.syspath, files)
class Do:
class Do(Walker):
def newer(self, files):
for file in files:
shutil.copy(join(self.syspath, file), join(self.dirpath, file))
@ -308,57 +396,69 @@ class Do:
os.remove(join(self.syspath, file))
IFNAMES = ['enp%ds0' % i for i in range(1,5)]
def handle_config():
netaddr_dict = {}
for ifname in IFNAMES:
with open('/sys/class/net/%s/address' % ifname) as f:
netaddr_dict[ifname] = f.read().strip().lower()
netaddr = netaddr_dict[IFNAMES[0]]
apuid = int(''.join(netaddr.split(':')[-3:]), 16) & 0xfffffc
cfgfile = None
cfgfiles = []
for i in range(4):
# goodie: look for mac addresses of all 4 ports
cfgfiles += glob(CFGPATH % ('*', apuid + i))
with open('/etc/hostname') as f:
hostname = f.read().strip()
dhcp_server_cfg.clear()
for file in glob(CFGPATH % ('*', apuid)):
cfgfiles.append(file)
for i in [1, 2, 3]:
bad = glob(CFGPATH % ('*', apuid+i))
if bad:
print('cfg files found with bad apu id (use net addr of leftmost plug)')
print(bad)
return False
newhostname = main_info['hostname']
if not cfgfiles:
print('no cfg file found for %s' % hostname)
newname = input('enter host name: ')
if not newname:
# 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:
# leftmost interface labelled 'eth0'
main['mainif'] = sorted_if[0]
typ = 'controlbox'
template = CONTROLBOX_TEMPLATE
else:
# rightmost interface
main['mainif'] = sorted_if[-1]
typ = 'bare apu'
template = CONFIG_TEMPLATE
print('no cfg file found for this', typ, f'with id {apuid:%6.6x} (hostname={hostname})')
newhostname = input('enter host name: ')
if not newhostname:
print('no hostname given')
return False
cfgfile = CFGPATH % (newname, apuid)
cfgfile = CFGPATH % (newhostname, apuid)
with open(cfgfile, 'w') as f:
f.write(CONFIG_TEMPLATE)
f.write(template)
elif len(cfgfiles) > 1:
print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles))
else:
cfgfile = cfgfiles[0]
if cfgfile != CFGPATH % (hostname, apuid):
if cfgfile != CFGPATH % (newhostname, apuid):
if cfgfile:
newhostname = basename(cfgfile).rpartition('_')[0]
if doit:
os.system('sh ../sethostname.sh')
os.system('sh sethostname.sh')
else:
if cfgfile:
print('replace host name %r by %r' % (hostname, basename(cfgfile).rpartition('_')[0]))
print('replace host name %r by %r' % (hostname, newhostname))
show.dirty = True
main_info['hostname'] = newhostname
if cfgfile is None:
return False
to_start = {}
ifname = ''
parser = ConfigParser()
dhcp_server_cfg = []
try:
parser.read(cfgfile)
network = dict(parser['NETWORK'])
for ifname in IFNAMES:
content = create_if(ifname, network.pop(ifname, 'off'), netaddr_dict[ifname], dhcp_server_cfg)
network = {ifname_mapping.get(k, k): v for k, v in parser['NETWORK'].items()}
for ifname in net_addr:
content = create_if(ifname, network.pop(ifname, 'off'))
content = '\n'.join('%s=%s' % kv for kv in content.items())
todo = write_when_new('/etc/sysconfig/network-scripts/ifcfg-%s' % ifname, content)
if todo and more_info == 'NONE':
if todo and not more_info:
print('change', ifname)
show.dirty = True
to_start[ifname] = 'if_restart'
@ -380,7 +480,7 @@ def handle_config():
if todo:
print('change dhcpd.conf')
to_start['dhcpd'] = 'restart'
show.dirty = True
show.dirty = True
elif doit:
unix_cmd('systemctl stop dhcpd')
unix_cmd('systemctl disable dhcpd')
@ -391,7 +491,11 @@ def handle_config():
dirty = True
content.append('[%s]' % section)
content.append(COMMENT)
for key, value in parser[section].items():
section_dict = dict(parser[section].items())
if section == 'DISPLAY':
if display_update(section_dict):
to_start['display'] = 'restart'
for key, value in section_dict.items():
content.append('%s=%s' % (key, value))
content.append('')
if dirty:
@ -403,12 +507,12 @@ def handle_config():
return False
reload_systemd = False
for service, template_func in SERVICES.items():
for service, service_func in SERVICES.items():
section = service.upper()
if parser.has_section(section):
template = template_func(**dict(parser[section]))
servicecfg = service_func(**dict(parser[section]))
else:
template = None
servicecfg = None
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
active = False
enabled = False
@ -417,7 +521,7 @@ def handle_config():
enabled = True
elif line.strip() == 'ActiveState=active':
active = True
if template is None:
if servicecfg is None:
if active:
unix_cmd('systemctl stop %s' % service)
show.dirty = True
@ -429,10 +533,10 @@ def handle_config():
to_start[service] = 'enable'
elif not active:
to_start[service] = 'restart'
if write_when_new('/etc/systemd/system/%s.service' % service, template):
if write_when_new('/etc/systemd/system/%s.service' % service, servicecfg):
show.dirty = True
reload_systemd = True
if template and to_start.get('service') is None:
if servicecfg and to_start.get('service') is None:
to_start[service] = 'restart'
pip()
@ -447,23 +551,39 @@ def handle_config():
if action == 'restart':
unix_cmd('systemctl restart %s' % service)
unix_cmd('systemctl enable %s' % service)
return True
result = [f'config file:\n {cfgfile}']
for section in parser.sections():
result.append(section)
for key, value in parser.items(section):
result.append(f' {key} = {value}')
result.append('')
return '\n'.join(result)
more_info = False
doit = False
print('---')
print(' ')
show = Show()
walk(show)
result = handle_config()
if not result:
print('fix first above errors')
elif show.dirty and more_info == 'NONE':
print('---')
answer = input('do above? ')
doit = True
if answer.lower().startswith('y'):
handle_config()
walk(Do())
else:
print('nothing to do')
if show.dirty:
print('---')
answer = input('enter "y(es)" to do above or "m(ore)" for more info: ')
if not answer:
print('cancel')
elif 'yes'.startswith(answer.lower()):
doit = True
handle_config()
walk(Do())
with open('/root/aputools/current', 'w') as f:
f.write(result)
elif 'more'.startswith(answer.lower()):
more_info = True
walk(show)
result = handle_config()
else:
print('nothing to do')

59
mkusb.sh Normal file
View File

@ -0,0 +1,59 @@
# name of the USB stick to be created
NAME=BOOT_TINY
# list removeable disks
DISKS=()
while read LINE
do
ARR=($LINE)
if [[ ${ARR[@]:0:2} == "1 disk" ]]; then
DISKS+=(${ARR[@]:2:1})
printf "%-7s %7s " ${ARR[@]:2:2}
echo ${ARR[@]:4}
elif [[ ${ARR[@]:0:2} == "1 part" ]]; then
printf " %7s " ${ARR[@]:3:1}
echo ${ARR[@]:4}
fi
done < <(lsblk -l --output RM,TYPE,NAME,SIZE,LABEL,VENDOR,MODEL)
echo "which device? should be one of: ${DISKS[@]}"
read DEVICE
if [ -z $DEVICE ] ; then
exit
fi
if [[ " ${DISKS[@]} " =~ " $DEVICE " ]]; then
echo "create TinyLinux"
if [ ! -d boot_tiny ] ; then
git clone --depth 1 git@gitlab.psi.ch:samenv/boot_tiny.git
fi
if [[ $(cat /sys/block/$DEVICE/removable) != "1" ]]; then
echo "/dev/$DEVICE is not a removable disk"
exit
fi
DEVICE=/dev/$DEVICE
umount ${DEVICE}1
dd if=/dev/zero of=${DEVICE} count=1 conv=notrunc
echo -e "o\nn\np\n1\n\n\nw" | fdisk ${DEVICE}
mkfs.vfat -n $NAME -I ${DEVICE}1
syslinux -i ${DEVICE}1
dd conv=notrunc bs=440 count=1 if=boot_tiny/mbr.bin of=${DEVICE}
parted ${DEVICE} set 1 boot on
mkdir -p /mnt/apusb
mount ${DEVICE}1 /mnt/apusb
echo "copy files ..."
cp -r boot_tiny/* /mnt/apusb/
umount /mnt/apusb
rm -rf boot_tiny
echo "done."
else
echo "/dev/$DEVICE is not a removeable disk"
fi

View File

@ -18,6 +18,7 @@ iptables -A INPUT -i lo -j ACCEPT
sim = False
def unix_cmd(command):
if sim:
print('> %r' % command)
@ -29,10 +30,15 @@ def unix_cmd(command):
class IoHandler:
client = None
handler = None
port = None
def __init__(self, client, handler):
self.handler = handler
self.client = client
self.sentchunks = 0
self.sentbytes = 0
self.rcvdchunks = 0
self.rcvdbytes = 0
def request(self):
try:
@ -40,6 +46,8 @@ class IoHandler:
if data:
# print('<', data)
self.write(data)
self.sentbytes += len(data)
self.sentchunks += 1
return
except Exception as e:
print('ERROR in request: %s' % e)
@ -51,6 +59,8 @@ class IoHandler:
data = self.read()
# print('>', data)
self.client.sendall(data)
self.rcvdbytes += len(data)
self.rcvdchunks += 1
return
except ConnectionResetError:
pass
@ -99,6 +109,44 @@ class SerialHandler(IoHandler):
self.serial.close()
class InfoHandler(IoHandler):
clients = {}
def __init__(self, client, handler):
super().__init__(client, handler)
info = [f'{k} -> {v}' for k, v in AcceptHandler.routes.items()]
if AcceptHandler.handlers:
info.append('\nactive routings, statistics bytes/chunks')
info.append('fno port sent received')
for fno, h in AcceptHandler.handlers.items():
info.append(f'{fno} {h.port} {h.sentbytes:d}/{h.sentchunks:d} {h.rcvdbytes:d}/{h.rcvdchunks:d}')
info.append('')
self.client.sendall('\n'.join(info).encode('utf-8'))
self.clients[client.fileno()] = self
self.fno = None
def read(self):
return b''
def write(self, data):
pass
def close(self):
self.clients.pop(self.client.fileno())
@classmethod
def log(cls, line):
if cls.clients:
for c in cls.clients.values():
try:
c.client.sendall(line.encode('utf-8'))
c.client.sendall(b'\n')
except TimeoutError:
pass
else:
print(line)
class AcceptHandler:
"""handler for routing
@ -112,6 +160,8 @@ class AcceptHandler:
reused: in this case maxcount has to be increased ...
"""
readers = {}
handlers = {}
routes = {}
def __init__(self, port, addr, iocls, maxcount=1):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -125,16 +175,18 @@ class AcceptHandler:
self.port = port
self.available = maxcount
self.pending = 0
print('listening at port %d for %s(%r)' % (port, iocls.__name__, addr) )
def close_client(self, iohandler):
self.readers.pop(iohandler.fno, None)
client = iohandler.client
fno = client.fileno()
try:
client = iohandler.client
self.readers.pop(client.fileno())
self.readers.pop(fno)
client.close()
InfoHandler.log(f'closed connection from port {self.port} fno {fno}')
except Exception as e:
print('ERROR in close_client: %s' % e)
InfoHandler.log(f'{e!r} in close_client')
self.handlers.pop(fno, None)
iohandler.client = None
iohandler.fno = None
self.available += 1
@ -148,18 +200,23 @@ class AcceptHandler:
return
try:
client, addr = self.socket.accept()
print('accepted', addr, 'on', self.port)
InfoHandler.log(f'accepted {addr} on {self.port} fno {client.fileno()}')
handler = self.iocls(client, self)
except Exception as e:
print('ERROR creating %s(%r)' % (self.iocls.__name__, self.addr))
InfoHandler.log(f'{e!r} creating {self.iocls.__name__}({self.addr})')
client.close()
return
self.readers[client.fileno()] = handler.request
self.readers[handler.fno] = handler.reply
if handler.fno is not None:
self.readers[handler.fno] = handler.reply
# statistics: number of chunks sent / received
handler.port = self.port
self.handlers[client.fileno()] = handler
self.available -= 1
@classmethod
def run(cls, routes, restrict=None):
cls.routes = dict(routes)
if restrict is not None:
lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT')
unix_cmd('iptables -F')
@ -169,6 +226,7 @@ class AcceptHandler:
if restrict:
unix_cmd(FILTER % 22)
AcceptHandler(1111, None, InfoHandler, 5)
for port, dest in routes.items():
port=int(port)
if restrict:
@ -196,7 +254,6 @@ class AcceptHandler:
cls.readers[fno]()
if __name__ == '__main__':
parser = ConfigParser()
cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname())

View File

@ -0,0 +1,10 @@
[NETWORK]
; please refer to README.md for help
eth0=dhcp
eth1=192.168.1.1
eth2=192.168.2.2
eth3=192.168.3.3
[DISPLAY]
; please refer to README.md for help
startup_text=startup...|HOST|ADDR

View File

@ -0,0 +1,11 @@
[NETWORK]
; please refer to README.md for help
enp1s0=192.168.127.254
enp2s0=192.168.12.2
enp3s0=192.168.13.3
enp4s0=wan,192.168.1.0/24
[ROUTER]
; please refer to README.md for help
3001=192.168.127.254:3001
8080=192.168.127.254:80

View File

@ -1,9 +1,11 @@
echo "Welcome to $HOSTNAME $(hostname -I)"
echo "$(cat /sys/class/net/enp4s0/address)"
echo "$(cat /sys/class/net/enp1s0/address) .. $(cat /sys/class/net/enp4s0/address)"
export EDITOR=nano
function service_status () {
for name in $@; do \
echo ${name} $(systemctl is-active ${name}) $(systemctl is-enabled ${name}); \
echo ${name} $(systemctl is-active ${name}) $(systemctl is-enabled ${name} 2> /dev/null); \
done | column -t | grep --color=always '\(disabled\|inactive\|$\)'
}
service_status router frappy
alias current='cat /root/aputools/current'
service_status router frappy display
echo "> current # show currently installed state"