install.py: use class instead of global variables
This commit is contained in:
855
install.py
855
install.py
@ -61,277 +61,24 @@ max-lease-time 7200;
|
|||||||
authoritative;
|
authoritative;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ROUTER_TEMPLATE = """[Unit]
|
|
||||||
Description = Routing to locally connected hardware
|
|
||||||
After = network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart = /usr/bin/python3 /root/aputools/router.py
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy = multi-user.target
|
|
||||||
"""
|
|
||||||
|
|
||||||
FRAPPY_TEMPLATE = """[Unit]
|
|
||||||
Description = Running frappy server
|
|
||||||
After = network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User = l_samenv
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
pip_requirements = {
|
|
||||||
'l_samenv': {},
|
|
||||||
'root': {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
net_addr = {} # dict <if name> of <ethernet address>
|
|
||||||
main_info = {} # info about WAN interface
|
|
||||||
dhcp_server_cfg = [] # configuration for dhcp
|
|
||||||
|
|
||||||
|
|
||||||
def frappy(cfg=None, port=None, requirements='', **kwds):
|
|
||||||
if not cfg:
|
|
||||||
return None
|
|
||||||
req = pip_requirements['l_samenv']
|
|
||||||
req[cfg] = '\n'.join(requirements.split(','))
|
|
||||||
if port:
|
|
||||||
cfg = '-p %s %s' % (port, cfg)
|
|
||||||
with open('/home/l_samenv/frappy/requirements.txt') as f:
|
|
||||||
req['frappy main'] = f.read()
|
|
||||||
return FRAPPY_TEMPLATE % cfg
|
|
||||||
|
|
||||||
|
|
||||||
def router(**opts):
|
|
||||||
if not opts:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
with open('/root/aputools/requirements.txt') as f:
|
|
||||||
pip_requirements['root']['aputools'] = f.read()
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
return ROUTER_TEMPLATE
|
|
||||||
|
|
||||||
|
|
||||||
def display_update(cfg):
|
|
||||||
dirty = False
|
|
||||||
lines = []
|
|
||||||
for key in ['line0', 'line1', 'line2']:
|
|
||||||
value = cfg.get(key, '')
|
|
||||||
if value.startswith('HOST'):
|
|
||||||
line = main_info.get('hostname', '')
|
|
||||||
value = 'HOST: ' + line
|
|
||||||
elif value.startswith('ADDR'):
|
|
||||||
line = main_info.get('addr', '')
|
|
||||||
value = 'ADDR: ' + line
|
|
||||||
else:
|
|
||||||
line = value
|
|
||||||
lines.append(line)
|
|
||||||
if cfg.get(key) != value:
|
|
||||||
if doit:
|
|
||||||
cfg[key] = value
|
|
||||||
dirty = True
|
|
||||||
if dirty:
|
|
||||||
text = '\n'.join(lines)
|
|
||||||
if doit:
|
|
||||||
with open(STARTUP_TEXT, 'w') as f:
|
|
||||||
f.write(text)
|
|
||||||
else:
|
|
||||||
print('new startup text:')
|
|
||||||
print('\n'.join(lines))
|
|
||||||
|
|
||||||
|
|
||||||
def display(**opts):
|
|
||||||
if not opts:
|
|
||||||
return None
|
|
||||||
return DISPLAY_TEMPLATE
|
|
||||||
|
|
||||||
|
|
||||||
def pip():
|
|
||||||
for user, requirements in pip_requirements.items():
|
|
||||||
if user == 'root':
|
|
||||||
tmpname = join('/root', 'pip_requirements.tmp')
|
|
||||||
pipcmd = 'pip3 install -r %s' % tmpname
|
|
||||||
else:
|
|
||||||
tmpname = join('/home', user, 'pip_requirements.tmp')
|
|
||||||
pipcmd = 'sudo --user %s pip3 install --user -r %s' % (user, 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)
|
|
||||||
else:
|
|
||||||
os.remove(tmpname)
|
|
||||||
else:
|
|
||||||
print(pipcmd)
|
|
||||||
# unix_cmd(pipcmd, stdout=None)
|
|
||||||
show.dirty = True
|
|
||||||
|
|
||||||
|
|
||||||
SERVICES = dict(router=router, frappy=frappy, display=display)
|
SERVICES = dict(router=router, frappy=frappy, display=display)
|
||||||
|
|
||||||
|
|
||||||
def unix_cmd(command, always=False, stdout=PIPE):
|
net_addr = {} # dict <if name> of <ethernet address>
|
||||||
if doit or always:
|
for netif in os.scandir('/sys/class/net'):
|
||||||
if not always:
|
if netif.name != 'lo': # do not consider loopback interface
|
||||||
print('$ %s' % command)
|
with open(os.path.join(netif.path, 'address')) as f:
|
||||||
result = Popen(command.split(), stdout=stdout).communicate()[0]
|
addr = f.read().strip().lower()
|
||||||
return (result or b'').decode()
|
net_addr[netif.name] = addr
|
||||||
else:
|
|
||||||
print('> %s' % command)
|
|
||||||
|
|
||||||
|
|
||||||
def write_when_new(filename, content, ignore_reduction=False):
|
class Walker:
|
||||||
if content is None:
|
def __init__(self):
|
||||||
lines = []
|
self.dirpath = None
|
||||||
else:
|
self.syspath = None
|
||||||
if not content.endswith('\n'):
|
|
||||||
content += '\n'
|
|
||||||
lines = content.split('\n')
|
|
||||||
try:
|
|
||||||
with open(filename) as fil:
|
|
||||||
old = fil.read()
|
|
||||||
except FileNotFoundError:
|
|
||||||
old = None
|
|
||||||
if old == content:
|
|
||||||
return False
|
|
||||||
if ignore_reduction:
|
|
||||||
content_set = set(v for v in lines if not v.startswith('#'))
|
|
||||||
old_set = set(v for v in (old or '').split('\n') if not v.startswith('#'))
|
|
||||||
content_set -= old_set
|
|
||||||
if content_set:
|
|
||||||
print('missing packages: %s' % (', '.join(content_set - old_set)))
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
if doit:
|
|
||||||
if lines:
|
|
||||||
with open(filename, 'w') as fil:
|
|
||||||
fil.write(content)
|
|
||||||
else:
|
|
||||||
os.remove(filename)
|
|
||||||
elif filename.endswith(more_info):
|
|
||||||
print('changes in', filename)
|
|
||||||
old = [] if old is None else old.split('\n')
|
|
||||||
top_lines = 0 # in case of empty loop
|
|
||||||
for top_lines, (ol, ll) in enumerate(zip(old, lines)):
|
|
||||||
if ol != ll:
|
|
||||||
break
|
|
||||||
bottom_lines = 0
|
|
||||||
for bottom_lines, (ol, ll) in enumerate(zip(reversed(old[top_lines:]), reversed(lines[top_lines:]))):
|
|
||||||
if ol != ll:
|
|
||||||
break
|
|
||||||
if bottom_lines == 0:
|
|
||||||
old.append('')
|
|
||||||
lines.append('')
|
|
||||||
bottom_lines += 1
|
|
||||||
print("===")
|
|
||||||
print('\n'.join(old[:top_lines]))
|
|
||||||
print('<<<')
|
|
||||||
print('\n'.join(old[top_lines:-bottom_lines]))
|
|
||||||
print('---')
|
|
||||||
print('\n'.join(lines[top_lines:-bottom_lines]))
|
|
||||||
print('>>>')
|
|
||||||
print('\n'.join(old[-bottom_lines:-1]))
|
|
||||||
print("===")
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def create_if(name, cfg):
|
class Show(Walker):
|
||||||
result = dict(
|
|
||||||
TYPE='Ethernet',
|
|
||||||
NAME=name,
|
|
||||||
DEVICE=name,
|
|
||||||
BOOTPROTO='none',
|
|
||||||
ONBOOT='yes',
|
|
||||||
PROXY_METHOD='none',
|
|
||||||
BROWSER_ONLY='no',
|
|
||||||
IPV4_FAILURE_FATAL='yes',
|
|
||||||
IPV6INIT='no')
|
|
||||||
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
|
|
||||||
dhcp_server_cfg.append(('0.0.0.0/128.0.0.0', []))
|
|
||||||
dhcp_server_cfg.append(('128.0.0.0/192.0.0.0', []))
|
|
||||||
for nw in cfg.split(',')[1:]:
|
|
||||||
nw = IPv4Interface(nw).network
|
|
||||||
dhcp_server_cfg.append((nw.with_netmask, []))
|
|
||||||
else:
|
|
||||||
cfgip = IPv4Interface(cfg)
|
|
||||||
network = cfgip.network
|
|
||||||
if network.prefixlen == 32: # or no prefix specified
|
|
||||||
otherip = IPv4Interface('%s/24' % cfgip.ip)
|
|
||||||
network = otherip.network
|
|
||||||
if str(cfgip.ip).endswith('.1'):
|
|
||||||
cfgip = network.network_address + 2
|
|
||||||
else:
|
|
||||||
cfgip = network.network_address + 1
|
|
||||||
dhcp_server_cfg.append((network.with_netmask, [(str(otherip.ip), str(otherip.ip))]))
|
|
||||||
result['IPADDR'] = str(cfgip)
|
|
||||||
else: # subnet with multiple adresses -> static adresses only. dhcp range not yet implemented
|
|
||||||
result['IPADDR'] = str(cfgip.ip)
|
|
||||||
result['NETMASK'] = network.netmask
|
|
||||||
result['PREFIX'] = str(network.prefixlen)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def walk(action):
|
|
||||||
for dirpath, _, files in os.walk('.'):
|
|
||||||
syspath = dirpath[1:] # remove leading '.'
|
|
||||||
action.dirpath = 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))]
|
|
||||||
if newer:
|
|
||||||
action.newer(newer)
|
|
||||||
if len(newer) < len(mismatch):
|
|
||||||
newer = set(newer)
|
|
||||||
action.older([f for f in mismatch if f not in newer])
|
|
||||||
if missing:
|
|
||||||
if DEL in missing:
|
|
||||||
missing.remove(DEL)
|
|
||||||
with open(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))
|
|
||||||
else:
|
|
||||||
to_delete.append(f)
|
|
||||||
action.delete(to_delete)
|
|
||||||
if missing:
|
|
||||||
action.missing(missing)
|
|
||||||
|
|
||||||
|
|
||||||
class Show:
|
|
||||||
dirty = False
|
dirty = False
|
||||||
|
|
||||||
def diff(self, title, files):
|
def diff(self, title, files):
|
||||||
@ -364,7 +111,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))
|
||||||
@ -381,191 +128,435 @@ class Do:
|
|||||||
os.remove(join(self.syspath, file))
|
os.remove(join(self.syspath, file))
|
||||||
|
|
||||||
|
|
||||||
for netif in os.scandir('/sys/class/net'):
|
class Install:
|
||||||
if netif.name != 'lo': # do not consider loopback interface
|
ROUTER_TEMPLATE = """[Unit]
|
||||||
with open(os.path.join(netif.path, 'address')) as f:
|
Description = Routing to locally connected hardware
|
||||||
addr = f.read().strip().lower()
|
After = network.target
|
||||||
net_addr[netif.name] = addr
|
|
||||||
|
|
||||||
sorted_if = sorted(net_addr)
|
[Service]
|
||||||
|
ExecStart = /usr/bin/python3 /root/aputools/router.py
|
||||||
|
|
||||||
apuid = int(''.join(net_addr[sorted_if[0]].split(':')[-3:]), 16) & 0xffffff
|
[Install]
|
||||||
|
WantedBy = multi-user.target
|
||||||
|
"""
|
||||||
|
|
||||||
|
FRAPPY_TEMPLATE = """[Unit]
|
||||||
|
Description = Running frappy server
|
||||||
|
After = network.target
|
||||||
|
|
||||||
def handle_config():
|
[Service]
|
||||||
cfgfile = None
|
User = l_samenv
|
||||||
cfgfiles = []
|
ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/secop-server %s
|
||||||
for file in glob(CFGPATH % ('*', apuid)):
|
|
||||||
cfgfiles.append(file)
|
[Install]
|
||||||
with open('/etc/hostname') as f:
|
WantedBy = multi-user.target
|
||||||
hostname = f.read().strip()
|
"""
|
||||||
newhostname = hostname
|
|
||||||
if not cfgfiles:
|
DISPLAY_TEMPLATE = """[Unit]
|
||||||
disp = serial.Serial('/dev/ttyS1', baudrate=115200, timeout=1)
|
Description = status display
|
||||||
disp.write(b'\x1b\x1b\x01\xf3')
|
After = network.target
|
||||||
display_available = disp.read(8)[0:4] == b'\x1b\x1b\x05\xf3'
|
|
||||||
if display_available:
|
[Service]
|
||||||
# leftmost interface labelled 'eth0'
|
User = root
|
||||||
wan_if = sorted_if[0]
|
ExecStart = /usr/bin/python3 /root/aputools/display.py -d
|
||||||
typ = 'controlbox'
|
|
||||||
template = CONTROLBOX_TEMPLATE
|
[Install]
|
||||||
else:
|
WantedBy = multi-user.target
|
||||||
# rightmost interface
|
"""
|
||||||
wan_id = sorted_if[-1]
|
|
||||||
typ = 'bare apu'
|
main_if = ''
|
||||||
template = CONFIG_TEMPLATE
|
main_addr = ''
|
||||||
print('no cfg file found for this', typ, f'with id {apuid:%6.6x} (hostname={hostname})')
|
|
||||||
newhostname = input('enter host name: ')
|
def __init__(self, doit):
|
||||||
if not newhostname:
|
if not doit:
|
||||||
print('no hostname given')
|
self.to_system = Show()
|
||||||
return False
|
walk(self.to_system)
|
||||||
cfgfile = CFGPATH % (newhostname, apuid)
|
self.doit = doit
|
||||||
with open(cfgfile, 'w') as f:
|
self.current = None
|
||||||
f.write(template)
|
self.sorted_if = sorted(net_addr)
|
||||||
elif len(cfgfiles) > 1:
|
self.apuid = int(''.join(net_addr[sorted_if[0]].split(':')[-3:]), 16) & 0xfffffc
|
||||||
print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles))
|
self.dhcp_server_cfg = [] # configuration for dhcp
|
||||||
else:
|
self.pip_requirements = {
|
||||||
cfgfile = cfgfiles[0]
|
'l_samenv': {},
|
||||||
wan_if = None
|
'root': {}
|
||||||
if cfgfile != CFGPATH % (hostname, apuid):
|
}
|
||||||
if cfgfile:
|
cfgfile = None
|
||||||
newhostname = basename(cfgfile).rpartition('_')[0]
|
cfgfiles = []
|
||||||
if doit:
|
for file in glob(CFGPATH % ('*', apuid)):
|
||||||
os.system('sh sethostname.sh')
|
cfgfiles.append(file)
|
||||||
else:
|
with open('/etc/hostname') as f:
|
||||||
if cfgfile:
|
hostname = f.read().strip()
|
||||||
print('replace host name %r by %r' % (hostname, newhostname))
|
self.hostname = hostname
|
||||||
show.dirty = True
|
if not cfgfiles:
|
||||||
main_info['hostname'] = newhostname
|
disp = serial.Serial('/dev/ttyS1', baudrate=115200, timeout=1)
|
||||||
if cfgfile is None:
|
disp.write(b'\x1b\x1b\x01\xf3')
|
||||||
return False
|
display_available = disp.read(8)[0:4] == b'\x1b\x1b\x05\xf3'
|
||||||
to_start = {}
|
if display_available:
|
||||||
ifname = ''
|
# leftmost interface labelled 'eth0'
|
||||||
parser = ConfigParser()
|
typ = 'controlbox'
|
||||||
try:
|
template = CONTROLBOX_TEMPLATE
|
||||||
parser.read(cfgfile)
|
else:
|
||||||
network = dict(parser['NETWORK'])
|
# rightmost interface
|
||||||
for ifname in net_addr:
|
typ = 'bare apu'
|
||||||
content = create_if(ifname, network.pop(ifname, 'off'))
|
template = CONFIG_TEMPLATE
|
||||||
content = '\n'.join('%s=%s' % kv for kv in content.items())
|
print('no cfg file found for this', typ, f'with id {apuid:%6.6x} (hostname={hostname})')
|
||||||
todo = write_when_new('/etc/sysconfig/network-scripts/ifcfg-%s' % ifname, content)
|
self.hostname = input('enter host name: ')
|
||||||
if todo and more_info == 'NONE':
|
if not self.hostname:
|
||||||
print('change', ifname)
|
print('no hostname given')
|
||||||
show.dirty = True
|
return
|
||||||
to_start[ifname] = 'if_restart'
|
cfgfile = CFGPATH % (self.hostname, apuid)
|
||||||
if dhcp_server_cfg:
|
|
||||||
content = [DHCP_HEADER]
|
|
||||||
for subnet, rangelist in dhcp_server_cfg:
|
|
||||||
try:
|
|
||||||
adr, mask = subnet.split('/')
|
|
||||||
except Exception as e:
|
|
||||||
print(subnet, repr(e))
|
|
||||||
continue
|
|
||||||
content.append('subnet %s netmask %s {\n' % (adr, mask))
|
|
||||||
#content.append(' option netmask %s;\n' % mask)
|
|
||||||
for rng in rangelist:
|
|
||||||
content.append(' range %s %s;\n' % rng)
|
|
||||||
content.append('}\n')
|
|
||||||
content = ''.join(content)
|
|
||||||
todo = write_when_new('/etc/dhcp/dhcpd.conf', content)
|
|
||||||
if todo:
|
|
||||||
print('change dhcpd.conf')
|
|
||||||
to_start['dhcpd'] = 'restart'
|
|
||||||
show.dirty = True
|
|
||||||
elif doit:
|
|
||||||
unix_cmd('systemctl stop dhcpd')
|
|
||||||
unix_cmd('systemctl disable dhcpd')
|
|
||||||
content = []
|
|
||||||
dirty = False
|
|
||||||
for section in parser.sections():
|
|
||||||
if COMMENT not in parser[section]:
|
|
||||||
dirty = True
|
|
||||||
content.append('[%s]' % section)
|
|
||||||
content.append(COMMENT)
|
|
||||||
section_dict = dict(parser[section].items())
|
|
||||||
if section == 'DISPLAY':
|
|
||||||
display_update(section_dict)
|
|
||||||
for key, value in section_dict.items():
|
|
||||||
content.append('%s=%s' % (key, value))
|
|
||||||
content.append('')
|
|
||||||
if dirty:
|
|
||||||
with open(cfgfile, 'w') as f:
|
with open(cfgfile, 'w') as f:
|
||||||
f.write('\n'.join(content))
|
f.write(template)
|
||||||
except Exception as e:
|
elif len(cfgfiles) > 1:
|
||||||
print('ERROR: can not handle %s %s: %r' % (hostname, ifname, e))
|
print('ERROR: ambiguous cfg files: %s' % ', '.join(cfgfiles))
|
||||||
raise
|
|
||||||
return False
|
|
||||||
|
|
||||||
reload_systemd = False
|
|
||||||
for service, service_func in SERVICES.items():
|
|
||||||
section = service.upper()
|
|
||||||
if parser.has_section(section):
|
|
||||||
servicecfg = service_func(**dict(parser[section]))
|
|
||||||
else:
|
else:
|
||||||
servicecfg = None
|
cfgfile = cfgfiles[0]
|
||||||
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
|
if cfgfile != CFGPATH % (hostname, apuid):
|
||||||
active = False
|
if cfgfile:
|
||||||
enabled = False
|
self.hostname = basename(cfgfile).rpartition('_')[0]
|
||||||
for line in result.split('\n'):
|
if doit:
|
||||||
if line.startswith('WantedBy=m'):
|
os.system('sh sethostname.sh')
|
||||||
enabled = True
|
else:
|
||||||
elif line.strip() == 'ActiveState=active':
|
if cfgfile:
|
||||||
active = True
|
print('replace host name %r by %r' % (hostname, self.hostname))
|
||||||
if servicecfg is None:
|
self.to_system.dirty = True
|
||||||
if active:
|
if cfgfile is None:
|
||||||
unix_cmd('systemctl stop %s' % service)
|
raise ValueError('no config file')
|
||||||
show.dirty = True
|
to_start = {}
|
||||||
if enabled:
|
ifname = ''
|
||||||
unix_cmd('systemctl disable %s' % service)
|
parser = ConfigParser()
|
||||||
show.dirty = True
|
try:
|
||||||
|
parser.read(cfgfile)
|
||||||
|
network = dict(parser['NETWORK'])
|
||||||
|
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':
|
||||||
|
print('change', ifname)
|
||||||
|
self.to_system.dirty = True
|
||||||
|
to_start[ifname] = 'if_restart'
|
||||||
|
if self.dhcp_server_cfg:
|
||||||
|
content = [DHCP_HEADER]
|
||||||
|
for subnet, rangelist in self.dhcp_server_cfg:
|
||||||
|
try:
|
||||||
|
adr, mask = subnet.split('/')
|
||||||
|
except Exception as e:
|
||||||
|
print(subnet, repr(e))
|
||||||
|
continue
|
||||||
|
content.append('subnet %s netmask %s {\n' % (adr, mask))
|
||||||
|
# content.append(' option netmask %s;\n' % mask)
|
||||||
|
for rng in rangelist:
|
||||||
|
content.append(' range %s %s;\n' % rng)
|
||||||
|
content.append('}\n')
|
||||||
|
content = ''.join(content)
|
||||||
|
todo = write_when_new('/etc/dhcp/dhcpd.conf', content)
|
||||||
|
if todo:
|
||||||
|
print('change dhcpd.conf')
|
||||||
|
to_start['dhcpd'] = 'restart'
|
||||||
|
self.to_system.dirty = True
|
||||||
|
elif doit:
|
||||||
|
unix_cmd('systemctl stop dhcpd')
|
||||||
|
unix_cmd('systemctl disable dhcpd')
|
||||||
|
content = []
|
||||||
|
dirty = False
|
||||||
|
for section in parser.sections():
|
||||||
|
if COMMENT not in parser[section]:
|
||||||
|
dirty = True
|
||||||
|
content.append('[%s]' % section)
|
||||||
|
content.append(COMMENT)
|
||||||
|
section_dict = dict(parser[section].items())
|
||||||
|
if section == 'DISPLAY':
|
||||||
|
display_update(section_dict)
|
||||||
|
for key, value in section_dict.items():
|
||||||
|
content.append('%s=%s' % (key, value))
|
||||||
|
content.append('')
|
||||||
|
if dirty:
|
||||||
|
with open(cfgfile, 'w') as f:
|
||||||
|
f.write('\n'.join(content))
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR: can not handle %s %s: %r' % (hostname, ifname, e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
reload_systemd = False
|
||||||
|
for service, service_func in SERVICES.items():
|
||||||
|
section = service.upper()
|
||||||
|
if parser.has_section(section):
|
||||||
|
servicecfg = service_func(**dict(parser[section]))
|
||||||
|
else:
|
||||||
|
servicecfg = None
|
||||||
|
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
|
||||||
|
active = False
|
||||||
|
enabled = False
|
||||||
|
for line in result.split('\n'):
|
||||||
|
if line.startswith('WantedBy=m'):
|
||||||
|
enabled = True
|
||||||
|
elif line.strip() == 'ActiveState=active':
|
||||||
|
active = True
|
||||||
|
if servicecfg is None:
|
||||||
|
if active:
|
||||||
|
unix_cmd('systemctl stop %s' % service)
|
||||||
|
self.to_system.dirty = True
|
||||||
|
if enabled:
|
||||||
|
unix_cmd('systemctl disable %s' % service)
|
||||||
|
self.to_system.dirty = True
|
||||||
|
else:
|
||||||
|
if not enabled:
|
||||||
|
to_start[service] = 'enable'
|
||||||
|
elif not active:
|
||||||
|
to_start[service] = 'restart'
|
||||||
|
if write_when_new('/etc/systemd/system/%s.service' % service, servicecfg):
|
||||||
|
self.to_system.dirty = True
|
||||||
|
reload_systemd = True
|
||||||
|
if servicecfg and to_start.get('service') is None:
|
||||||
|
to_start[service] = 'restart'
|
||||||
|
|
||||||
|
pip()
|
||||||
|
if reload_systemd:
|
||||||
|
unix_cmd('systemctl daemon-reload')
|
||||||
|
for service, action in to_start.items():
|
||||||
|
self.to_system.dirty = True
|
||||||
|
if action == 'if_restart':
|
||||||
|
unix_cmd('ifdown %s' % service)
|
||||||
|
unix_cmd('ifup %s' % service)
|
||||||
|
else:
|
||||||
|
if action == 'restart':
|
||||||
|
unix_cmd('systemctl restart %s' % service)
|
||||||
|
unix_cmd('systemctl enable %s' % service)
|
||||||
|
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('')
|
||||||
|
self.current = '\n'.join(result)
|
||||||
|
|
||||||
|
def unix_cmd(self, command, always=False, stdout=PIPE):
|
||||||
|
if self.doit or always:
|
||||||
|
if not always:
|
||||||
|
print('$ %s' % command)
|
||||||
|
result = Popen(command.split(), stdout=stdout).communicate()[0]
|
||||||
|
return (result or b'').decode()
|
||||||
else:
|
else:
|
||||||
if not enabled:
|
print('> %s' % command)
|
||||||
to_start[service] = 'enable'
|
|
||||||
elif not active:
|
|
||||||
to_start[service] = 'restart'
|
|
||||||
if write_when_new('/etc/systemd/system/%s.service' % service, servicecfg):
|
|
||||||
show.dirty = True
|
|
||||||
reload_systemd = True
|
|
||||||
if servicecfg and to_start.get('service') is None:
|
|
||||||
to_start[service] = 'restart'
|
|
||||||
|
|
||||||
pip()
|
def write_when_new(self, filename, content, ignore_reduction=False):
|
||||||
if reload_systemd:
|
if content is None:
|
||||||
unix_cmd('systemctl daemon-reload')
|
lines = []
|
||||||
for service, action in to_start.items():
|
|
||||||
show.dirty = True
|
|
||||||
if action == 'if_restart':
|
|
||||||
unix_cmd('ifdown %s' % service)
|
|
||||||
unix_cmd('ifup %s' % service)
|
|
||||||
else:
|
else:
|
||||||
if action == 'restart':
|
if not content.endswith('\n'):
|
||||||
unix_cmd('systemctl restart %s' % service)
|
content += '\n'
|
||||||
unix_cmd('systemctl enable %s' % service)
|
lines = content.split('\n')
|
||||||
result = [f'config file:\n {cfgfile}']
|
try:
|
||||||
for section in parser.sections():
|
with open(filename) as f:
|
||||||
result.append(section)
|
old = f.read()
|
||||||
for key, value in parser.items(section):
|
except FileNotFoundError:
|
||||||
result.append(f' {key} = {value}')
|
old = None
|
||||||
result.append('')
|
if old == content:
|
||||||
return '\n'.join(result)
|
return False
|
||||||
|
if ignore_reduction:
|
||||||
|
content_set = set(v for v in lines if not v.startswith('#'))
|
||||||
|
old_set = set(v for v in (old or '').split('\n') if not v.startswith('#'))
|
||||||
|
content_set -= old_set
|
||||||
|
if content_set:
|
||||||
|
print('missing packages: %s' % (', '.join(content_set - old_set)))
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if self.doit:
|
||||||
|
if lines:
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
else:
|
||||||
|
os.remove(filename)
|
||||||
|
elif filename.endswith(more_info):
|
||||||
|
print('changes in', filename)
|
||||||
|
old = [] if old is None else old.split('\n')
|
||||||
|
top_lines = 0 # in case of empty loop
|
||||||
|
for top_lines, (ol, ll) in enumerate(zip(old, lines)):
|
||||||
|
if ol != ll:
|
||||||
|
break
|
||||||
|
bottom_lines = 0
|
||||||
|
for bottom_lines, (ol, ll) in enumerate(zip(reversed(old[top_lines:]), reversed(lines[top_lines:]))):
|
||||||
|
if ol != ll:
|
||||||
|
break
|
||||||
|
if bottom_lines == 0:
|
||||||
|
old.append('')
|
||||||
|
lines.append('')
|
||||||
|
bottom_lines += 1
|
||||||
|
print("===")
|
||||||
|
print('\n'.join(old[:top_lines]))
|
||||||
|
print('<<<')
|
||||||
|
print('\n'.join(old[top_lines:-bottom_lines]))
|
||||||
|
print('---')
|
||||||
|
print('\n'.join(lines[top_lines:-bottom_lines]))
|
||||||
|
print('>>>')
|
||||||
|
print('\n'.join(old[-bottom_lines:-1]))
|
||||||
|
print("===")
|
||||||
|
return content
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def frappy(cfg=None, port=None, requirements='', **kwds):
|
||||||
|
if not cfg:
|
||||||
|
return None
|
||||||
|
req = pip_requirements['l_samenv']
|
||||||
|
req[cfg] = '\n'.join(requirements.split(','))
|
||||||
|
if port:
|
||||||
|
cfg = '-p %s %s' % (port, cfg)
|
||||||
|
with open('/home/l_samenv/frappy/requirements.txt') as f:
|
||||||
|
req['frappy main'] = f.read()
|
||||||
|
return FRAPPY_TEMPLATE % cfg
|
||||||
|
|
||||||
|
def router(self, **opts):
|
||||||
|
if not opts:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open('/root/aputools/requirements.txt') as f:
|
||||||
|
pip_requirements['root']['aputools'] = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return self.ROUTER_TEMPLATE
|
||||||
|
|
||||||
|
def display_update(self, cfg):
|
||||||
|
dirty = False
|
||||||
|
lines = []
|
||||||
|
for key in ['line0', 'line1', 'line2']:
|
||||||
|
value = cfg.get(key, '')
|
||||||
|
if value.startswith('HOST'):
|
||||||
|
line = self.hostname
|
||||||
|
value = 'HOST: ' + line
|
||||||
|
elif value.startswith('ADDR'):
|
||||||
|
line = self.main_addr
|
||||||
|
value = 'ADDR: ' + line
|
||||||
|
else:
|
||||||
|
line = value
|
||||||
|
lines.append(line)
|
||||||
|
if cfg.get(key) != value:
|
||||||
|
if self.doit:
|
||||||
|
cfg[key] = value
|
||||||
|
dirty = True
|
||||||
|
if dirty:
|
||||||
|
text = '\n'.join(lines)
|
||||||
|
if self.doit:
|
||||||
|
with open(STARTUP_TEXT, 'w') as f:
|
||||||
|
f.write(text)
|
||||||
|
else:
|
||||||
|
print('new startup text:')
|
||||||
|
print('\n'.join(lines))
|
||||||
|
|
||||||
|
def display(self, **opts):
|
||||||
|
if not opts:
|
||||||
|
return None
|
||||||
|
return self.DISPLAY_TEMPLATE
|
||||||
|
|
||||||
|
def pip(self):
|
||||||
|
for user, requirements in pip_requirements.items():
|
||||||
|
if user == 'root':
|
||||||
|
tmpname = join('/root', 'pip_requirements.tmp')
|
||||||
|
pipcmd = 'pip3 install -r %s' % tmpname
|
||||||
|
else:
|
||||||
|
tmpname = join('/home', user, 'pip_requirements.tmp')
|
||||||
|
pipcmd = 'sudo --user %s pip3 install --user -r %s' % (user, 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 self.doit:
|
||||||
|
os.rename(filename, tmpname)
|
||||||
|
if os.system(pipcmd) == 0:
|
||||||
|
os.rename(tmpname, filename)
|
||||||
|
else:
|
||||||
|
os.remove(tmpname)
|
||||||
|
else:
|
||||||
|
print(pipcmd)
|
||||||
|
# unix_cmd(pipcmd, stdout=None)
|
||||||
|
self.to_system.dirty = True
|
||||||
|
|
||||||
|
def create_if(self, name, cfg):
|
||||||
|
result = dict(
|
||||||
|
TYPE='Ethernet',
|
||||||
|
NAME=name,
|
||||||
|
DEVICE=name,
|
||||||
|
BOOTPROTO='none',
|
||||||
|
ONBOOT='yes',
|
||||||
|
PROXY_METHOD='none',
|
||||||
|
BROWSER_ONLY='no',
|
||||||
|
IPV4_FAILURE_FATAL='yes',
|
||||||
|
IPV6INIT='no')
|
||||||
|
if cfg == 'off':
|
||||||
|
result['ONBOOT'] = 'no'
|
||||||
|
elif cfg.startswith('wan') or cfg == 'dhcp':
|
||||||
|
if name != self.main_if and self.main_if
|
||||||
|
raise ValueError('can not have more than one WAN/DHCP port')
|
||||||
|
self.main_if = name
|
||||||
|
self.main_addr = net_addr[name]
|
||||||
|
result['BOOTPROTO'] = 'dhcp'
|
||||||
|
# default: all <= 192.0.0.0
|
||||||
|
# others have to be added explicitly
|
||||||
|
self.dhcp_server_cfg.append(('0.0.0.0/128.0.0.0', []))
|
||||||
|
self.dhcp_server_cfg.append(('128.0.0.0/192.0.0.0', []))
|
||||||
|
for nw in cfg.split(',')[1:]:
|
||||||
|
nw = IPv4Interface(nw).network
|
||||||
|
self.dhcp_server_cfg.append((nw.with_netmask, []))
|
||||||
|
else:
|
||||||
|
cfgip = IPv4Interface(cfg)
|
||||||
|
network = cfgip.network
|
||||||
|
if network.prefixlen == 32: # or no prefix specified
|
||||||
|
otherip = IPv4Interface('%s/24' % cfgip.ip)
|
||||||
|
network = otherip.network
|
||||||
|
if str(cfgip.ip).endswith('.1'):
|
||||||
|
cfgip = network.network_address + 2
|
||||||
|
else:
|
||||||
|
cfgip = network.network_address + 1
|
||||||
|
self.dhcp_server_cfg.append((network.with_netmask, [(str(otherip.ip), str(otherip.ip))]))
|
||||||
|
result['IPADDR'] = str(cfgip)
|
||||||
|
else: # subnet with multiple adresses -> static adresses only. dhcp range not yet implemented
|
||||||
|
result['IPADDR'] = str(cfgip.ip)
|
||||||
|
result['NETMASK'] = network.netmask
|
||||||
|
result['PREFIX'] = str(network.prefixlen)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def walk(self, action):
|
||||||
|
for dirpath, _, files in os.walk('.'):
|
||||||
|
syspath = dirpath[1:] # remove leading '.'
|
||||||
|
action.dirpath = 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))]
|
||||||
|
if newer:
|
||||||
|
action.newer(newer)
|
||||||
|
if len(newer) < len(mismatch):
|
||||||
|
newer = set(newer)
|
||||||
|
action.older([f for f in mismatch if f not in newer])
|
||||||
|
if missing:
|
||||||
|
if DEL in missing:
|
||||||
|
missing.remove(DEL)
|
||||||
|
with open(join(dirpath, DEL)) as f:
|
||||||
|
to_delete = []
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if fline and exists(join(syspath, line)):
|
||||||
|
if exists(join(dirpath, line)):
|
||||||
|
print('ERROR: %s in %s, but also in repo -> ignored' % (line, DEL))
|
||||||
|
else:
|
||||||
|
to_delete.append(f)
|
||||||
|
action.delete(to_delete)
|
||||||
|
if missing:
|
||||||
|
action.missing(missing)
|
||||||
|
|
||||||
|
|
||||||
doit = False
|
|
||||||
print('---')
|
print('---')
|
||||||
show = Show()
|
to_install = Install(False)
|
||||||
walk(show)
|
|
||||||
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':
|
elif to_system.dirty and more_info == 'NONE':
|
||||||
print('---')
|
print('---')
|
||||||
answer = input('do above? ')
|
answer = input('do above? ')
|
||||||
doit = True
|
|
||||||
if answer.lower().startswith('y'):
|
if answer.lower().startswith('y'):
|
||||||
handle_config()
|
Install(True)
|
||||||
walk(Do())
|
walk(Do())
|
||||||
with open('/root/aputools/current', 'w') as f:
|
with open('/root/aputools/current', 'w') as fil:
|
||||||
f.write(result)
|
fil.write(to_install.current)
|
||||||
else:
|
else:
|
||||||
print('nothing to do')
|
print('nothing to do')
|
||||||
|
Reference in New Issue
Block a user