Merge branch 'master' of gitlab.psi.ch:samenv/aputools
This commit is contained in:
111
README.md
111
README.md
@ -2,23 +2,23 @@
|
|||||||
|
|
||||||
The APU is a Linux box to be used as a control box at LIN sample environment
|
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
|
## config files
|
||||||
|
|
||||||
filename: servercfg/(hostname)_(hexdigits).cfg
|
filename: servercfg/(hostname)_(hexdigits).cfg
|
||||||
|
|
||||||
* (hostname) the hostname to be given on startup
|
* (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:
|
content of the config files:
|
||||||
|
|
||||||
```
|
```
|
||||||
[NETWORK]
|
[NETWORK]
|
||||||
enp1s0=192.168.127.254 # leftmost socket: connect here a moxa with factory settings
|
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
|
enp3s0=192.168.3.3 # or must be configured static to this IP
|
||||||
enp4s0=dhcp # rightmost socket is the uplink
|
enp4s0=dhcp # rightmost socket is the uplink
|
||||||
|
|
||||||
@ -30,17 +30,24 @@ enp4s0=dhcp # rightmost socket is the uplink
|
|||||||
[FRAPPY]
|
[FRAPPY]
|
||||||
cfg=uniax # the cfg file for the frappy server
|
cfg=uniax # the cfg file for the frappy server
|
||||||
port=5000 # the port 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
|
## 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
|
### fixed IP
|
||||||
```
|
```
|
||||||
enp2s0=192.168.3.5
|
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,
|
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).
|
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
|
### 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
|
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
|
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
309
display.py
Normal 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()
|
||||||
|
|
262
install.py
262
install.py
@ -12,31 +12,49 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import filecmp
|
import filecmp
|
||||||
import shutil
|
import shutil
|
||||||
|
import serial
|
||||||
|
import types
|
||||||
|
import socket
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from ipaddress import IPv4Interface
|
from ipaddress import IPv4Interface
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from os.path import join, getmtime, exists, basename
|
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')
|
os.chdir('/root/aputools/to_system')
|
||||||
|
|
||||||
DEL = '__to_delete__'
|
DEL = '__to_delete__'
|
||||||
CFGPATH = '/root/aputools/servercfg/%s_%6.6x.cfg'
|
CFGPATH = '/root/aputools/servercfg/%s_%6.6x.cfg'
|
||||||
|
STARTUP_TEXT = '/root/aputools/startup_display.txt'
|
||||||
|
|
||||||
COMMENT = "; please refer to README.md for help"
|
COMMENT = "; please refer to README.md for help"
|
||||||
|
|
||||||
CONFIG_TEMPLATE = """[NETWORK]
|
CONFIG_TEMPLATE = f"""[NETWORK]
|
||||||
%s
|
{COMMENT}
|
||||||
enp1s0=192.168.127.254
|
enp1s0=192.168.127.254
|
||||||
enp2s0=192.168.2.2
|
enp2s0=192.168.2.2
|
||||||
enp3s0=192.168.3.3
|
enp3s0=192.168.3.3
|
||||||
enp4s0=dhcp
|
enp4s0=dhcp
|
||||||
|
|
||||||
[ROUTER]
|
[ROUTER]
|
||||||
%s
|
{COMMENT}
|
||||||
3001=192.168.127.254:3001
|
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 = """
|
DHCP_HEADER = """
|
||||||
default-lease-time 600;
|
default-lease-time 600;
|
||||||
@ -66,12 +84,51 @@ ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/secop-server %s
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy = multi-user.target
|
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 = {
|
pip_requirements = {
|
||||||
'l_samenv': {},
|
'l_samenv': {},
|
||||||
'root': {}
|
'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):
|
def frappy(cfg=None, port=None, requirements='', **kwds):
|
||||||
if not cfg:
|
if not cfg:
|
||||||
return None
|
return None
|
||||||
@ -95,6 +152,24 @@ def router(**opts):
|
|||||||
return ROUTER_TEMPLATE
|
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():
|
def pip():
|
||||||
for user, requirements in pip_requirements.items():
|
for user, requirements in pip_requirements.items():
|
||||||
if user == 'root':
|
if user == 'root':
|
||||||
@ -118,7 +193,7 @@ def pip():
|
|||||||
show.dirty = True
|
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):
|
def unix_cmd(command, always=False, stdout=PIPE):
|
||||||
@ -159,7 +234,8 @@ def write_when_new(filename, content, ignore_reduction=False):
|
|||||||
fil.write(content)
|
fil.write(content)
|
||||||
else:
|
else:
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
elif filename.endswith(more_info):
|
elif more_info:
|
||||||
|
print('.' * 80)
|
||||||
print('changes in', filename)
|
print('changes in', filename)
|
||||||
old = [] if old is None else old.split('\n')
|
old = [] if old is None else old.split('\n')
|
||||||
top_lines = 0 # in case of empty loop
|
top_lines = 0 # in case of empty loop
|
||||||
@ -183,10 +259,11 @@ def write_when_new(filename, content, ignore_reduction=False):
|
|||||||
print('>>>')
|
print('>>>')
|
||||||
print('\n'.join(old[-bottom_lines:-1]))
|
print('\n'.join(old[-bottom_lines:-1]))
|
||||||
print("===")
|
print("===")
|
||||||
return True
|
print('.' * 80)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
def create_if(name, cfg, mac, dhcp_server_cfg):
|
def create_if(name, cfg):
|
||||||
result = dict(
|
result = dict(
|
||||||
TYPE='Ethernet',
|
TYPE='Ethernet',
|
||||||
NAME=name,
|
NAME=name,
|
||||||
@ -200,6 +277,10 @@ def create_if(name, cfg, mac, dhcp_server_cfg):
|
|||||||
if cfg == 'off':
|
if cfg == 'off':
|
||||||
result['ONBOOT'] = 'no'
|
result['ONBOOT'] = 'no'
|
||||||
elif cfg.startswith('wan') or cfg == 'dhcp':
|
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'
|
result['BOOTPROTO'] = 'dhcp'
|
||||||
# default: all <= 192.0.0.0
|
# default: all <= 192.0.0.0
|
||||||
# others have to be added explicitly
|
# others have to be added explicitly
|
||||||
@ -246,37 +327,44 @@ def walk(action):
|
|||||||
missing.remove(DEL)
|
missing.remove(DEL)
|
||||||
with open(join(dirpath, DEL)) as fil:
|
with open(join(dirpath, DEL)) as fil:
|
||||||
to_delete = []
|
to_delete = []
|
||||||
for f in fil:
|
for fname in fil:
|
||||||
f = f.strip()
|
fname = fname.strip()
|
||||||
if f and exists(join(syspath, f)):
|
if fname and exists(join(syspath, fname)):
|
||||||
if exists(join(dirpath, f)):
|
if exists(join(dirpath, fname)):
|
||||||
print('ERROR: %s in %s, but also in repo -> ignored' % (f, DEL))
|
print('ERROR: %s in %s, but also in repo -> ignored' % (fname, DEL))
|
||||||
else:
|
else:
|
||||||
to_delete.append(f)
|
to_delete.append(fname)
|
||||||
action.delete(to_delete)
|
action.delete(to_delete)
|
||||||
if missing:
|
if missing:
|
||||||
action.missing(missing)
|
action.missing(missing)
|
||||||
|
|
||||||
|
|
||||||
class Show:
|
class Walker:
|
||||||
|
def __init__(self):
|
||||||
|
self.dirpath = None
|
||||||
|
self.syspath = None
|
||||||
|
|
||||||
|
|
||||||
|
class Show(Walker):
|
||||||
dirty = False
|
dirty = False
|
||||||
|
|
||||||
def diff(self, title, files):
|
def diff(self, title, files):
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
if more_info == 'NONE':
|
if more_info:
|
||||||
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
|
|
||||||
else:
|
|
||||||
for f in files:
|
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('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):
|
def show(self, title, dirpath, files):
|
||||||
if more_info == 'NONE':
|
if more_info:
|
||||||
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
|
|
||||||
else:
|
|
||||||
for f in files:
|
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):
|
def newer(self, files):
|
||||||
self.show('get from', self.dirpath, files)
|
self.show('get from', self.dirpath, files)
|
||||||
@ -291,7 +379,7 @@ class Show:
|
|||||||
self.show('remove from', self.syspath, files)
|
self.show('remove from', self.syspath, files)
|
||||||
|
|
||||||
|
|
||||||
class Do:
|
class Do(Walker):
|
||||||
def newer(self, files):
|
def newer(self, files):
|
||||||
for file in files:
|
for file in files:
|
||||||
shutil.copy(join(self.syspath, file), join(self.dirpath, file))
|
shutil.copy(join(self.syspath, file), join(self.dirpath, file))
|
||||||
@ -308,57 +396,69 @@ class Do:
|
|||||||
os.remove(join(self.syspath, file))
|
os.remove(join(self.syspath, file))
|
||||||
|
|
||||||
|
|
||||||
IFNAMES = ['enp%ds0' % i for i in range(1,5)]
|
|
||||||
|
|
||||||
|
|
||||||
def handle_config():
|
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
|
cfgfile = None
|
||||||
cfgfiles = []
|
cfgfiles = []
|
||||||
for i in range(4):
|
dhcp_server_cfg.clear()
|
||||||
# goodie: look for mac addresses of all 4 ports
|
for file in glob(CFGPATH % ('*', apuid)):
|
||||||
cfgfiles += glob(CFGPATH % ('*', apuid + i))
|
cfgfiles.append(file)
|
||||||
with open('/etc/hostname') as f:
|
for i in [1, 2, 3]:
|
||||||
hostname = f.read().strip()
|
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:
|
if not cfgfiles:
|
||||||
print('no cfg file found for %s' % hostname)
|
# determine if display is present
|
||||||
newname = input('enter host name: ')
|
disp = serial.Serial('/dev/ttyS1', baudrate=115200, timeout=1)
|
||||||
if not newname:
|
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')
|
print('no hostname given')
|
||||||
return False
|
return False
|
||||||
cfgfile = CFGPATH % (newname, apuid)
|
cfgfile = CFGPATH % (newhostname, apuid)
|
||||||
with open(cfgfile, 'w') as f:
|
with open(cfgfile, 'w') as f:
|
||||||
f.write(CONFIG_TEMPLATE)
|
f.write(template)
|
||||||
elif len(cfgfiles) > 1:
|
elif len(cfgfiles) > 1:
|
||||||
print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles))
|
print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles))
|
||||||
else:
|
else:
|
||||||
cfgfile = cfgfiles[0]
|
cfgfile = cfgfiles[0]
|
||||||
if cfgfile != CFGPATH % (hostname, apuid):
|
if cfgfile != CFGPATH % (newhostname, apuid):
|
||||||
|
if cfgfile:
|
||||||
|
newhostname = basename(cfgfile).rpartition('_')[0]
|
||||||
if doit:
|
if doit:
|
||||||
os.system('sh ../sethostname.sh')
|
os.system('sh sethostname.sh')
|
||||||
else:
|
else:
|
||||||
if cfgfile:
|
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
|
show.dirty = True
|
||||||
|
main_info['hostname'] = newhostname
|
||||||
if cfgfile is None:
|
if cfgfile is None:
|
||||||
return False
|
return False
|
||||||
to_start = {}
|
to_start = {}
|
||||||
ifname = ''
|
ifname = ''
|
||||||
parser = ConfigParser()
|
parser = ConfigParser()
|
||||||
dhcp_server_cfg = []
|
|
||||||
try:
|
try:
|
||||||
parser.read(cfgfile)
|
parser.read(cfgfile)
|
||||||
network = dict(parser['NETWORK'])
|
network = {ifname_mapping.get(k, k): v for k, v in parser['NETWORK'].items()}
|
||||||
for ifname in IFNAMES:
|
for ifname in net_addr:
|
||||||
content = create_if(ifname, network.pop(ifname, 'off'), netaddr_dict[ifname], dhcp_server_cfg)
|
content = create_if(ifname, network.pop(ifname, 'off'))
|
||||||
content = '\n'.join('%s=%s' % kv for kv in content.items())
|
content = '\n'.join('%s=%s' % kv for kv in content.items())
|
||||||
todo = write_when_new('/etc/sysconfig/network-scripts/ifcfg-%s' % ifname, content)
|
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)
|
print('change', ifname)
|
||||||
show.dirty = True
|
show.dirty = True
|
||||||
to_start[ifname] = 'if_restart'
|
to_start[ifname] = 'if_restart'
|
||||||
@ -380,7 +480,7 @@ def handle_config():
|
|||||||
if todo:
|
if todo:
|
||||||
print('change dhcpd.conf')
|
print('change dhcpd.conf')
|
||||||
to_start['dhcpd'] = 'restart'
|
to_start['dhcpd'] = 'restart'
|
||||||
show.dirty = True
|
show.dirty = True
|
||||||
elif doit:
|
elif doit:
|
||||||
unix_cmd('systemctl stop dhcpd')
|
unix_cmd('systemctl stop dhcpd')
|
||||||
unix_cmd('systemctl disable dhcpd')
|
unix_cmd('systemctl disable dhcpd')
|
||||||
@ -391,7 +491,11 @@ def handle_config():
|
|||||||
dirty = True
|
dirty = True
|
||||||
content.append('[%s]' % section)
|
content.append('[%s]' % section)
|
||||||
content.append(COMMENT)
|
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('%s=%s' % (key, value))
|
||||||
content.append('')
|
content.append('')
|
||||||
if dirty:
|
if dirty:
|
||||||
@ -403,12 +507,12 @@ def handle_config():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
reload_systemd = False
|
reload_systemd = False
|
||||||
for service, template_func in SERVICES.items():
|
for service, service_func in SERVICES.items():
|
||||||
section = service.upper()
|
section = service.upper()
|
||||||
if parser.has_section(section):
|
if parser.has_section(section):
|
||||||
template = template_func(**dict(parser[section]))
|
servicecfg = service_func(**dict(parser[section]))
|
||||||
else:
|
else:
|
||||||
template = None
|
servicecfg = None
|
||||||
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
|
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
|
||||||
active = False
|
active = False
|
||||||
enabled = False
|
enabled = False
|
||||||
@ -417,7 +521,7 @@ def handle_config():
|
|||||||
enabled = True
|
enabled = True
|
||||||
elif line.strip() == 'ActiveState=active':
|
elif line.strip() == 'ActiveState=active':
|
||||||
active = True
|
active = True
|
||||||
if template is None:
|
if servicecfg is None:
|
||||||
if active:
|
if active:
|
||||||
unix_cmd('systemctl stop %s' % service)
|
unix_cmd('systemctl stop %s' % service)
|
||||||
show.dirty = True
|
show.dirty = True
|
||||||
@ -429,10 +533,10 @@ def handle_config():
|
|||||||
to_start[service] = 'enable'
|
to_start[service] = 'enable'
|
||||||
elif not active:
|
elif not active:
|
||||||
to_start[service] = 'restart'
|
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
|
show.dirty = True
|
||||||
reload_systemd = 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'
|
to_start[service] = 'restart'
|
||||||
|
|
||||||
pip()
|
pip()
|
||||||
@ -447,23 +551,39 @@ def handle_config():
|
|||||||
if action == 'restart':
|
if action == 'restart':
|
||||||
unix_cmd('systemctl restart %s' % service)
|
unix_cmd('systemctl restart %s' % service)
|
||||||
unix_cmd('systemctl enable %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
|
doit = False
|
||||||
print('---')
|
print(' ')
|
||||||
show = Show()
|
show = Show()
|
||||||
walk(show)
|
walk(show)
|
||||||
result = handle_config()
|
result = handle_config()
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
print('fix first above errors')
|
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:
|
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
59
mkusb.sh
Normal 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
|
||||||
|
|
73
router.py
73
router.py
@ -18,6 +18,7 @@ iptables -A INPUT -i lo -j ACCEPT
|
|||||||
|
|
||||||
sim = False
|
sim = False
|
||||||
|
|
||||||
|
|
||||||
def unix_cmd(command):
|
def unix_cmd(command):
|
||||||
if sim:
|
if sim:
|
||||||
print('> %r' % command)
|
print('> %r' % command)
|
||||||
@ -29,10 +30,15 @@ def unix_cmd(command):
|
|||||||
class IoHandler:
|
class IoHandler:
|
||||||
client = None
|
client = None
|
||||||
handler = None
|
handler = None
|
||||||
|
port = None
|
||||||
|
|
||||||
def __init__(self, client, handler):
|
def __init__(self, client, handler):
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.client = client
|
self.client = client
|
||||||
|
self.sentchunks = 0
|
||||||
|
self.sentbytes = 0
|
||||||
|
self.rcvdchunks = 0
|
||||||
|
self.rcvdbytes = 0
|
||||||
|
|
||||||
def request(self):
|
def request(self):
|
||||||
try:
|
try:
|
||||||
@ -40,6 +46,8 @@ class IoHandler:
|
|||||||
if data:
|
if data:
|
||||||
# print('<', data)
|
# print('<', data)
|
||||||
self.write(data)
|
self.write(data)
|
||||||
|
self.sentbytes += len(data)
|
||||||
|
self.sentchunks += 1
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('ERROR in request: %s' % e)
|
print('ERROR in request: %s' % e)
|
||||||
@ -51,6 +59,8 @@ class IoHandler:
|
|||||||
data = self.read()
|
data = self.read()
|
||||||
# print('>', data)
|
# print('>', data)
|
||||||
self.client.sendall(data)
|
self.client.sendall(data)
|
||||||
|
self.rcvdbytes += len(data)
|
||||||
|
self.rcvdchunks += 1
|
||||||
return
|
return
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
pass
|
pass
|
||||||
@ -99,6 +109,44 @@ class SerialHandler(IoHandler):
|
|||||||
self.serial.close()
|
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:
|
class AcceptHandler:
|
||||||
"""handler for routing
|
"""handler for routing
|
||||||
|
|
||||||
@ -112,6 +160,8 @@ class AcceptHandler:
|
|||||||
reused: in this case maxcount has to be increased ...
|
reused: in this case maxcount has to be increased ...
|
||||||
"""
|
"""
|
||||||
readers = {}
|
readers = {}
|
||||||
|
handlers = {}
|
||||||
|
routes = {}
|
||||||
|
|
||||||
def __init__(self, port, addr, iocls, maxcount=1):
|
def __init__(self, port, addr, iocls, maxcount=1):
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
@ -125,16 +175,18 @@ class AcceptHandler:
|
|||||||
self.port = port
|
self.port = port
|
||||||
self.available = maxcount
|
self.available = maxcount
|
||||||
self.pending = 0
|
self.pending = 0
|
||||||
print('listening at port %d for %s(%r)' % (port, iocls.__name__, addr) )
|
|
||||||
|
|
||||||
def close_client(self, iohandler):
|
def close_client(self, iohandler):
|
||||||
self.readers.pop(iohandler.fno, None)
|
self.readers.pop(iohandler.fno, None)
|
||||||
|
client = iohandler.client
|
||||||
|
fno = client.fileno()
|
||||||
try:
|
try:
|
||||||
client = iohandler.client
|
self.readers.pop(fno)
|
||||||
self.readers.pop(client.fileno())
|
|
||||||
client.close()
|
client.close()
|
||||||
|
InfoHandler.log(f'closed connection from port {self.port} fno {fno}')
|
||||||
except Exception as e:
|
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.client = None
|
||||||
iohandler.fno = None
|
iohandler.fno = None
|
||||||
self.available += 1
|
self.available += 1
|
||||||
@ -148,18 +200,23 @@ class AcceptHandler:
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
client, addr = self.socket.accept()
|
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)
|
handler = self.iocls(client, self)
|
||||||
except Exception as e:
|
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()
|
client.close()
|
||||||
return
|
return
|
||||||
self.readers[client.fileno()] = handler.request
|
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
|
self.available -= 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls, routes, restrict=None):
|
def run(cls, routes, restrict=None):
|
||||||
|
cls.routes = dict(routes)
|
||||||
if restrict is not None:
|
if restrict is not None:
|
||||||
lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT')
|
lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT')
|
||||||
unix_cmd('iptables -F')
|
unix_cmd('iptables -F')
|
||||||
@ -169,6 +226,7 @@ class AcceptHandler:
|
|||||||
if restrict:
|
if restrict:
|
||||||
unix_cmd(FILTER % 22)
|
unix_cmd(FILTER % 22)
|
||||||
|
|
||||||
|
AcceptHandler(1111, None, InfoHandler, 5)
|
||||||
for port, dest in routes.items():
|
for port, dest in routes.items():
|
||||||
port=int(port)
|
port=int(port)
|
||||||
if restrict:
|
if restrict:
|
||||||
@ -196,7 +254,6 @@ class AcceptHandler:
|
|||||||
cls.readers[fno]()
|
cls.readers[fno]()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = ConfigParser()
|
parser = ConfigParser()
|
||||||
cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname())
|
cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname())
|
||||||
|
10
servercfg/linse-box1_5b265c.cfg
Normal file
10
servercfg/linse-box1_5b265c.cfg
Normal 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
|
11
servercfg/linseapu6_5fa5cc.cfg
Normal file
11
servercfg/linseapu6_5fa5cc.cfg
Normal 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
|
@ -1,9 +1,11 @@
|
|||||||
echo "Welcome to $HOSTNAME $(hostname -I)"
|
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
|
export EDITOR=nano
|
||||||
function service_status () {
|
function service_status () {
|
||||||
for name in $@; do \
|
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\|$\)'
|
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"
|
||||||
|
Reference in New Issue
Block a user