changeset 289:65e28d2e0cd8

move static templates to files/ ; use inventory tags for selecting hosts+features ; other refactors
author drewp@bigasterisk.com
date Sun, 21 Apr 2024 17:07:23 -0700
parents 3af02e24eaf9
children 828d3f4da54b
files apt.py dns.py files/dnsmasq/dnsmasq-mtail.service files/dnsmasq/metrics.mtail files/dnsmasq/run_mtail.sh files/docker_daemon.json files/fstab/dash files/fstab/dot files/fstab/slash files/net/bang_10.2.network files/net/ditto-netplan.yaml files/net/house_net.service files/net/pipe_10.2.network files/net/pipe_isp.network files/net/prime.network files/net/singlenic.network files/pigpiod.service home.py inventory.py kube.py multikube.py net.py package_lists.py packages.py pipe.py ssh.py sync.py system.py templates/boot_config.txt.j2 templates/dnsmasq/dnsmasq-mtail.service.j2 templates/dnsmasq/metrics.mtail.j2 templates/dnsmasq/run_mtail.sh templates/hosts.j2 templates/net/bang_10.2.network.j2 templates/net/ditto-netplan.yaml.j2 templates/net/house_net.service.j2 templates/net/pipe_10.2.network.j2 templates/net/pipe_isp.network.j2 templates/net/prime.network.j2 templates/net/singlenic.network.j2 templates/pigpiod.service.j2 templates/sources.list.j2 templates/wireguard/wg0.conf.j2 users.py wireguard.py
diffstat 45 files changed, 342 insertions(+), 486 deletions(-) [+]
line wrap: on
line diff
--- a/apt.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/apt.py	Sun Apr 21 17:07:23 2024 -0700
@@ -9,8 +9,6 @@
 
 TZ = 'America/Los_Angeles'
 
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
-
 
 def pkg_keys():
     files.directory(path='/etc/apt/keyrings/')  # for raspi
@@ -39,7 +37,7 @@
             ('https://nvidia.github.io/libnvidia-container/gpgkey', 'nvidia.gpg'),
         ]
     ])
-    if is_pi or host.name == 'bang':
+    if 'pi' in host.groups or host.name == 'bang':
         # this contaminates the apt-update
         files.file(path="/etc/apt/trusted.gpg.d/podman.asc", present=False)
 
@@ -98,7 +96,7 @@
     files.put(src=io.StringIO("nameserver 10.2.0.3\n"), dest='/etc/resolv.conf')
 
 
-if is_pi:
+if 'pi' in host.groups:
     correct_dns()
 pkg_keys()
 apt_sources()
--- a/dns.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/dns.py	Sun Apr 21 17:07:23 2024 -0700
@@ -5,9 +5,6 @@
 import pyinfra
 from pyinfra import host
 from pyinfra.operations import files, systemd, server
-from pyinfra.facts.server import Arch, LinuxDistribution
-
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
 
 
 def dnsmasq_instance(net_name,
@@ -41,7 +38,7 @@
 
 def standard_host_dns():
     files.template(src='templates/hosts.j2', dest='/etc/hosts')
-    if is_pi:
+    if 'pi' in host.groups:
         files.put(dest='/etc/resolv.conf',
                   src=StringIO('''
 # written by pyinfra
@@ -91,8 +88,8 @@
                      listen_address='unused')  # only works after wireguard is up
 elif host.name == 'ditto':
     rpi_iscsi_volumes()  # move out of this file- it's not dns
-elif host.name == 'pipe':
 # move out of this file- it's not dns
+if host.name == 'pipe':
     rpi_net_boot()
     files.directory(path='/opt/dnsmasq')
     dnsmasq_instance('10.2',
@@ -102,10 +99,10 @@
                      dhcp_hosts_filename='templates/dnsmasq/dhcp_hosts.j2')
     out = '/opt/dnsmasq/10.2'
     # This mtail is for dhcp command counts and errors. Another monitor in lanscape/ reads the leases file.
-    files.template(src='templates/dnsmasq/metrics.mtail.j2', dest=f'{out}/metrics.mtail')
-    files.template(src='templates/dnsmasq/run_mtail.sh', dest=f'{out}/run_mtail.sh')
+    files.put(src='files/dnsmasq/metrics.mtail', dest=f'{out}/metrics.mtail')
+    files.put(src='files/dnsmasq/run_mtail.sh', dest=f'{out}/run_mtail.sh')
 
-    files.template(src='templates/dnsmasq/dnsmasq-mtail.service.j2', dest='/etc/systemd/system/dnsmasq-mtail.service')
+    files.put(src='files/dnsmasq/dnsmasq-mtail.service', dest='/etc/systemd/system/dnsmasq-mtail.service')
     systemd.service(service='dnsmasq-mtail', enabled=True, restarted=True, daemon_reload=True)
 
     # Serve another dns, no dhcp, and include the dynamic-blocking file written by net_routes.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/dnsmasq/dnsmasq-mtail.service	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,13 @@
+# written by pyinfra
+
+[Unit]
+Description=dnsmasq-mtail for 10.2 network
+After=dnsmasq_10.2.service
+
+[Service]
+Type=simple
+
+ExecStart=zsh /opt/dnsmasq/10.2/run_mtail.sh
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/dnsmasq/metrics.mtail	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,37 @@
+counter dnsmasq_no_addr_errors
+/no address available/ {
+    dnsmasq_no_addr_errors++
+}
+
+counter dnsmasq_dhcp_requests
+/DHCPREQUEST/ {
+    dnsmasq_dhcp_requests++
+}
+
+counter dnsmasq_dhcp_acks
+/DHCPACK/ {
+    dnsmasq_dhcp_acks++
+}
+
+counter dnsmasq_dhcp_discovers
+/DHCPDISCOVER/ {
+    dnsmasq_dhcp_discovers++
+}
+
+counter dnsmasq_dhcp_offers
+/DHCPOFFER/ {
+    dnsmasq_dhcp_offers++
+}
+
+gauge dnsmasq_dns_queries_answered_locally
+gauge dnsmasq_dns_queries_forwarded by server
+gauge dnsmasq_dns_queries_retried_or_failed by server
+
+/queries forwarded (?P<fwd>\d+), queries answered locally (?P<loc>\d+)/ {
+    dnsmasq_dns_queries_answered_locally = $loc
+}
+
+/server (?P<svr>\S+)#53: queries sent (?P<sent>\d+), retried or failed (?P<fail>\d+)/ {
+    dnsmasq_dns_queries_forwarded[$svr] = $sent
+    dnsmasq_dns_queries_retried_or_failed[$svr] = $fail
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/dnsmasq/run_mtail.sh	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,11 @@
+#!/bin/zsh
+STATS_PERIOD=2m
+while (true) { pkill --signal USR1 --oldest --full /usr/sbin/dnsmasq; sleep ${STATS_PERIOD} } &
+
+rm -f /tmp/dnsmasq_log_pipe
+mkfifo /tmp/dnsmasq_log_pipe
+
+{ journalctl -fu dnsmasq_10.2.service > /tmp/dnsmasq_log_pipe } &
+
+mtail -port 9991 -logtostderr -logs /tmp/dnsmasq_log_pipe  -progs /opt/dnsmasq/10.2
+#-disable_fsnotify -poll_interval ${STATS_PERIOD}
\ No newline at end of file
--- a/files/docker_daemon.json	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-{ "experimental":true, "insecure-registries":["bang5:5000", "reg:5000"] }
--- a/files/fstab/dash	Sun Apr 21 17:01:13 2024 -0700
+++ b/files/fstab/dash	Sun Apr 21 17:07:23 2024 -0700
@@ -4,19 +4,8 @@
 /dev/disk/by-uuid/d8d23ff1-7c37-4a7d-9fc4-55fc61f912a0 / ext4 defaults 0 1
 /dev/disk/by-uuid/CB55-821E /boot/efi vfat defaults 0 1
 
-
-#UUID=b88f75cd-9022-4af9-a11b-5a5a1fbd3132 /d2  ext4  defaults 0 0
-#UUID=3b6780e0-ec86-43be-8d09-e462dbad762e /d3  ext4  defaults 0 0
-#UUID=73bcd201-5f77-4f68-9fba-47835c3c1692 /d4  ext4  defaults 0 0
-
-
-
 UUID=73bcd201-5f77-4f68-9fba-47835c3c1692 /d2  ext4  defaults 0 0
 UUID=6cae1c30-3c91-4aa7-9e9f-fcbd7ff706fe /d3  ext4  defaults 0 0
 UUID=3b6780e0-ec86-43be-8d09-e462dbad762e /d4  ext4  defaults 0 0
 
-
-
-#/swap.img	none	swap	sw	0	0
-
-ditto5:/my                           /my       nfs  rw,noatime          0       0
+ditto5:/my                                /my  nfs   rw,noatime 0 0
--- a/files/fstab/dot	Sun Apr 21 17:01:13 2024 -0700
+++ b/files/fstab/dot	Sun Apr 21 17:07:23 2024 -0700
@@ -8,5 +8,3 @@
 
 /dev/mapper/ubuntu--vg-ubuntu--lv                      /d2 ext4 defaults 0 1
 /dev/disk/by-uuid/5a6ce8db-cde0-4c26-b6a4-08faef2e01a2 /d3 ext4 defaults 0 1
-
-#/dev/disk/by-uuid/a3891b94-5ddd-428d-ab6f-8e39f3d8fbde /d3 ext4 defaults 0 1
\ No newline at end of file
--- a/files/fstab/slash	Sun Apr 21 17:01:13 2024 -0700
+++ b/files/fstab/slash	Sun Apr 21 17:07:23 2024 -0700
@@ -4,4 +4,4 @@
 UUID=df079890-9431-4e17-940c-d9ed8ce4e149 /         ext4 errors=remount-ro 0       1
 UUID=1CFA-995B                            /boot/efi vfat umask=0077        0       1
 
-ditto5:/my                           /my       nfs  rw,noatime          0       0
+ditto5:/my                                /my       nfs  rw,noatime        0       0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/bang_10.2.network	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,10 @@
+# written by pyinfra
+
+[Match]
+MACAddress=60:e3:27:04:4a:85
+
+[Network]
+DHCP=no
+Address=10.2.0.1/16
+DNS=10.2.0.3
+Gateway=10.2.0.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/ditto-netplan.yaml	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,22 @@
+# written by pyinfra
+network:
+  ethernets:
+    eno1:
+      addresses:
+        # name=ditto
+        - 10.2.0.133/16
+        # name=mqtt1 etc
+        - 10.2.0.11/16
+        - 10.2.0.12/16
+        - 10.2.0.13/16
+        - 10.2.0.14/16
+      routes:
+        - to: default
+          via: 10.2.0.3
+      nameservers:
+        addresses: [10.2.0.3]
+    enp5s0:
+      dhcp4: true
+    ens3:
+      dhcp4: true
+  version: 2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/house_net.service	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,14 @@
+# written by pyinfra
+
+[Unit]
+After=network-online.target nss-lookup.target
+Wants=network-online.target nss-lookup.target
+
+[Service]
+Type=oneshot
+ExecStart=sh -c "sysctl net.ipv4.ip_forward=1 && /usr/sbin/iptables -A POSTROUTING --table nat --out-interface eth0 --jump MASQUERADE"
+RemainAfterExit=yes
+
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/pipe_10.2.network	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,14 @@
+# written by pyinfra
+
+[Match]
+# usb dongle
+MACAddress=00:05:1b:33:3e:81
+
+[Network]
+DHCP=no
+Address=10.2.0.3/16
+# vip for the filtered dns server we give clients who are to have sometimes-filtered domains
+Address=10.2.0.4/16
+DNS=10.2.0.3
+Domains=bigasterisk.com
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/pipe_isp.network	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,12 @@
+# written by pyinfra
+
+[Match]
+# onboard eth
+MACAddress=00:1e:06:43:20:d0
+
+[Network]
+DHCP=no
+Address=192.168.42.3/24
+Gateway=192.168.42.1
+DNS=10.2.0.1
+Domains=bigasterisk.com
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/prime.network	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,14 @@
+# written by pyinfra
+
+# see systemd.network(5)
+
+[Match]
+MACAddress=04:01:09:7f:89:01
+
+[Network]
+Address=162.243.138.136/24
+Gateway=162.243.138.1
+DNS=10.5.0.1%wg0
+DNS=8.8.8.8
+DNS=8.8.4.4
+Domains=bigasterisk.com
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/net/singlenic.network	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,10 @@
+# written by pyinfra
+
+[Match]
+Name=*
+
+[Network]
+DHCP=yes
+# this sauce may or may not help with k3s
+LinkLocalAddressing=yes
+IPForward=yes
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/pigpiod.service	Sun Apr 21 17:07:23 2024 -0700
@@ -0,0 +1,11 @@
+# written by pyinfra
+
+[Unit]
+Description=Daemon required to control GPIO pins via pigpio
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/pigpiod -g -p 8888
+
+[Install]
+WantedBy=multi-user.target
--- a/home.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/home.py	Sun Apr 21 17:07:23 2024 -0700
@@ -1,12 +1,7 @@
 from pyinfra import host
 from pyinfra.operations import files, server
 
-if host.name in [
-    'dash',
-    'slash',
-    'bang',
-    'ditto',
-    ]:
+if host.data.get('drewp_home'):
     # maybe bring sync.py in here too
 
     server.shell(commands=['chsh -s /bin/zsh drewp'])
--- a/inventory.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/inventory.py	Sun Apr 21 17:07:23 2024 -0700
@@ -1,25 +1,28 @@
 big = [
-    ('bang',   { 'wireguard_address': '10.5.0.1',                                                               }),
-    ('dash',   { 'wireguard_address': '10.5.0.5',  'ssh_hostname': 'dash',                                      }),
-    ('slash',  { 'wireguard_address': '10.5.0.6',  'ssh_hostname': 'slash',                                     }),
-    ('dot',    { 'wireguard_address': '10.5.0.30', 'ssh_hostname': 'dot',                                       }),
-    ('ditto',  { 'wireguard_address': '10.5.0.7',                                                               }),
-    # ('squib',  {                                                                                                }),
+    ('bang'      , {                'drewp_gid': 1000,                     'drewp_uid': 501,                    'k8s_admin': True  ,                                     'syncthing': True,                    'wireguard_address': '10.5.0.1' ,  }),
+    ('dash'      , {                'drewp_gid': 1000, 'drewp_home': True, 'drewp_uid': 501, 'gpu': True      , 'k8s_admin': True ,   'ssh_hostname': 'dash',            'syncthing': True,                    'wireguard_address': '10.5.0.5',   }),
+    ('slash'     , {                'drewp_gid': 1000, 'drewp_home': True, 'drewp_uid': 501,                    'k8s_admin': True   , 'ssh_hostname': 'slash',           'syncthing': True,                    'wireguard_address': '10.5.0.6'  , }),
+    ('dot'       , {                'drewp_gid': 1000,                     'drewp_uid': 501,                                          'ssh_hostname': 'dot'        ,     'syncthing': True,                    'wireguard_address': '10.5.0.30',  }),
+    ('ditto'     , { 'coral': True, 'drewp_gid': 1000, 'drewp_home': True, 'drewp_uid': 501, 'gpu': True ,      'k8s_admin': True,                                       'syncthing': True,                    'wireguard_address': '10.5.0.7',   }),
+    ('squib'     , {                'drewp_gid': 1000,                     'drewp_uid': 501,                                                                                                                                                      }),
 ]
 
 small = [
-    ('pipe',   { 'wireguard_address': '10.5.0.3',  'ssh_hostname': '10.2.0.3',                                  }),
+    ('pipe'      , {                'drewp_gid': 501 ,                     'drewp_uid': 1001,                                         'ssh_hostname': '10.2.0.3'   ,                                           'wireguard_address': '10.5.0.3'  , }),
 ]
 
 pi = [
-    # ('garage', { 'wireguard_address': '10.5.0.14', 'ssh_hostname': 'garage',         }),
-    ('ws-printer', { 'wireguard_address': '10.5.0.31', 'ssh_hostname': 'ws-printer',   }),
-    ('gn-music',   { 'wireguard_address': '10.5.0.32', 'ssh_hostname': 'gn-music',     }),
-    ('li-drums',   { 'wireguard_address': '10.5.0.33', 'ssh_hostname': 'li-drums',     }),
+    ('garage'    , {                'drewp_gid': 501 ,                     'drewp_uid': 1000,                                         'ssh_hostname': 'garage'     ,                                           'wireguard_address': '10.5.0.14' , }),
+    ('ws-printer', {                'drewp_gid': 501 ,                     'drewp_uid': 1000,                                         'ssh_hostname': 'ws-printer5',                                           'wireguard_address': '10.5.0.31' , }),
+    ('li-drums'  , {                'drewp_gid': 501 ,                     'drewp_uid': 1000,                                         'ssh_hostname': 'li-drums5'  ,                                           'wireguard_address': '10.5.0.33' , }),
+    ('gn-music'  , {                'drewp_gid': 501 ,                     'drewp_uid': 1000,                                         'ssh_hostname': 'gn-music'   ,                                           'wireguard_address': '10.5.0.32' , }),
 ]
 
-remote = [
-    ('prime',  { 'wireguard_address': '10.5.0.2',  'ssh_hostname': '162.243.138.136','mac': '04:01:09:7f:89:01',}),
-    ('plus',   { 'wireguard_address': '10.5.0.110','ssh_hostname': '10.2.0.35',                                      }),
-    ('pillow',  { 'wireguard_address': '10.5.0.111','ssh_hostname':'10.5.0.111',}),
+hosted = [
+    ('prime'     , {                'drewp_gid': 1000,                     'drewp_uid': 501,                                          'ssh_hostname': '162.243.138.136',                                       'wireguard_address': '10.5.0.2' ,  }),
 ]
+
+laptop = [
+    ('plus'      , {                'drewp_gid': 1000, 'drewp_home': True, 'drewp_uid': 501,                                          'ssh_hostname': '10.2.0.35'      , 'syncthing': True, 'wg_roamer': True, 'wireguard_address': '10.5.0.110', }),
+    ('pillow'    , {                'drewp_gid': 1000,                     'drewp_uid': 1000 ,                                        'ssh_hostname': '10.5.0.111' ,     'syncthing': True, 'wg_roamer': True, 'wireguard_address': '10.5.0.111', }),
+]
--- a/kube.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/kube.py	Sun Apr 21 17:07:23 2024 -0700
@@ -5,8 +5,6 @@
 from pyinfra.facts.server import Arch, LinuxDistribution
 from pyinfra.operations import files, server, systemd
 
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
-
 # https://github.com/GoogleContainerTools/skaffold/releases
 skaffold_version = 'v2.10.1'
 
@@ -34,21 +32,7 @@
                    mode='755',
                    cache_time=1000)
     # one time; writes to $HOME
-    server.shell("skaffold config set --global insecure-registries reg:5000")
-
-
-def pi_cgroup_setup():
-    '''
-    fixes this:
-
-    Mar 29 23:47:11 ws-printer k3s[5999]: time="2024-03-29T23:47:11-07:00" level=fatal msg="failed to find memory cgroup (v2)"
-    '''
-    return 'cmdline.txt lives on pipe now, not on the pi host'
-    old_cmdline = host.get_fact(FindInFile, path='/boot/cmdline.txt', pattern=r'.*')[0]
-    if 'cgroup' not in old_cmdline:
-        cmdline = old_cmdline + ' cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory'
-        files.line(path='/boot/cmdline.txt', line='.*', replace=cmdline)
-        # pi needs reboot now
+    server.shell(commands="skaffold config set --global insecure-registries reg:5000")
 
 
 def host_prep():
@@ -61,9 +45,6 @@
     none, strict, loose = 0, 1, 2
     server.sysctl(key='net.ipv4.conf.default.rp_filter', value=loose, persist=True)
 
-    if is_pi:
-        pi_cgroup_setup()
-
 
 # don't try to get aufs-dkms on rpi-- https://github.com/docker/for-linux/issues/709
 def podman_insecure_registry(reg):
@@ -100,7 +81,7 @@
         dest=f'/etc/systemd/system/{service_name}',
         role=role,
     )
-    if host.name in ['bang', 'garage']:
+    if not host.data.get('gpu'):
         # no supported gpu
         '''
             kubectl label --overwrite node bang nvidia.com/gpu.deploy.gpu-feature-discovery=false
@@ -135,7 +116,6 @@
     server_ip,
     server_node,
     nodes,
-    admin_from,
     # https://github.com/k3s-io/k3s/releases
     # 1.23.6 per https://github.com/cilium/cilium/issues/20331
     k3s_version,
@@ -151,7 +131,7 @@
         # also note that podman dropped the default `docker.io/` prefix on image names (see https://unix.stackexchange.com/a/701785/419418)
         config_and_run_service(k3s_version, server_node, server_ip)
 
-    if host.name in admin_from:
+    if host.data.get('k8s_admin'):
         podman_insecure_registry(reg='reg:5000')
         files.directory(path='/etc/rancher/k3s')
         install_skaffold()
--- a/multikube.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/multikube.py	Sun Apr 21 17:07:23 2024 -0700
@@ -47,14 +47,6 @@
     #skaffold config set --global insecure-registries bang5:5000
 
 
-def pi_cgroup_setup():
-    old_cmdline = host.get_fact(FindInFile, path='/boot/cmdline.txt', pattern=r'.*')[0]
-    if 'cgroup' not in old_cmdline:
-        cmdline = old_cmdline + ' cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory'
-        files.line(path='/boot/cmdline.txt', line='.*', replace=cmdline)
-        # pi needs reboot now
-
-
 def host_prep():
     server.sysctl(key='net.ipv4.ip_forward', value="1", persist=True)
     server.sysctl(key='net.ipv6.conf.all.forwarding', value="1", persist=True)
@@ -66,12 +58,11 @@
     #none, strict, loose = 0, 1, 2
     #server.sysctl(key='net.ipv4.conf.default.rp_filter', value=loose, persist=True)
 
-    if is_pi:
-        pi_cgroup_setup()
 
 def service_name():
     return 'k3s.service' if host.name == server_node else 'k3s-node.service'
 
+
 def config_and_run_service():
     role = 'server' if host.name == server_node else 'agent'
     which_conf = 'config-server.yaml.j2' if host.name == server_node else 'config-agent.yaml.j2'
--- a/net.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/net.py	Sun Apr 21 17:07:23 2024 -0700
@@ -1,9 +1,5 @@
 from pyinfra import host
 from pyinfra.operations import apt, files, server, systemd
-from pyinfra.facts.server import Arch, LinuxDistribution
-
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
-is_wifi = False
 
 
 def cleanup():
@@ -23,7 +19,7 @@
             delete=True,
         )
 
-    # On bang (now pipe):
+    # On pipe:
     #   Now using a HW router for this firewall. No incoming connections.
     #   test connections from the outside:
     #   http://www.t1shopper.com/tools/port-scanner/
@@ -33,74 +29,50 @@
     apt.packages(packages=['ufw'], present=False)
 
 
-# https://github.com/k3s-io/k3s/issues/1812 unclear, but more importantly, this has to be set
-# on pipe in a way that works with the commands in house_net.service (and net_routes)
-server.shell(commands=[
-    'update-alternatives --set iptables /usr/sbin/iptables-legacy',
-    'update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy',
-])
-# needs reboot if this changed
+def iptables_version():
+    # https://github.com/k3s-io/k3s/issues/1812 unclear, but more importantly, this has to be set
+    # on pipe in a way that works with the commands in house_net.service (and net_routes)
+    server.shell(commands=[
+        'update-alternatives --set iptables /usr/sbin/iptables-legacy',
+        'update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy',
+    ])
+    # needs reboot if this changed
 
-if host.name in ['prime', 'bang', 'pipe', 'ditto']:
-    server.sysctl(key='net.ipv6.conf.all.disable_ipv6', value=1, persist=True)
-
-    # if is_wifi_pi:
-    #     files.put(dest="/etc/network/interfaces.d/wlan0", src="files/pi_wlan0_powersave")
-    #     ssh.command(host.name, "iw wlan0 set power_save off")
 
-    files.directory('/etc/systemd/network')
-    if host.name == 'prime':
-        cleanup()
+iptables_version()
+server.sysctl(key='net.ipv6.conf.all.disable_ipv6', value=1, persist=True)
+
+if host.name == 'prime':
+    cleanup()
 
-        files.template(
-            src="templates/net/prime.network.j2",
-            dest="/etc/systemd/network/99-prime.network",
-            mac=host.host_data['mac'],
-        )
-
-    elif host.name == 'bang':
-        cleanup()
-
-        files.template(src="templates/net/bang_10.2.network.j2", dest="/etc/systemd/network/20-10.2.network")
-        apt.packages(packages=['network-manager'], present=False)
+    files.template(
+        src="files/net/prime.network",
+        dest="/etc/systemd/network/99-prime.network",
+    )
+    systemd.service(service='systemd-networkd.service', enabled=True, running=True, restarted=True)
 
-    elif host.name == 'plus':
-        apt.packages(packages=['network-manager'], present=True)
-
-    elif host.name == 'pipe':
-        cleanup()
+if host.name == 'bang':
+    cleanup()
 
-        files.template(src="templates/net/pipe_10.2.network.j2", dest="/etc/systemd/network/99-10.2.network")
-        files.template(src="templates/net/pipe_isp.network.j2", dest="/etc/systemd/network/99-isp.network")
-        server.sysctl(key='net.ipv4.ip_forward', value=1, persist=True)
-        files.template(src="templates/net/house_net.service.j2",
-                       dest="/etc/systemd/system/house_net.service",
-                       out_interface='eth0')
-        systemd.service(service='house_net.service', daemon_reload=True, enabled=True, running=True, restarted=True)
+    files.template(src="files/net/bang_10.2.network", dest="/etc/systemd/network/20-10.2.network")
+    apt.packages(packages=['network-manager'], present=False)
+    systemd.service(service='systemd-networkd.service', enabled=True, running=True, restarted=True)
+
+if host.name == 'pipe':
+    cleanup()
 
-    elif host.name == 'ditto':
-        files.template(
-            src="templates/net/ditto-netplan.yaml.j2",
-            dest="/etc/netplan/00-installer-config.yaml",
-            create_remote_dir=True,
-        )
-
-    else:
-        cleanup()
+    files.template(src="files/net/pipe_10.2.network", dest="/etc/systemd/network/99-10.2.network")
+    files.template(src="files/net/pipe_isp.network", dest="/etc/systemd/network/99-isp.network")
+    server.sysctl(key='net.ipv4.ip_forward', value=1, persist=True)
+    files.template(src="files/net/house_net.service", dest="/etc/systemd/system/house_net.service", out_interface='eth0')
+    systemd.service(service='house_net.service', daemon_reload=True, enabled=True, running=True, restarted=True)
+    systemd.service(service='systemd-networkd.service', enabled=True, running=True, restarted=True)
 
-        if is_wifi:
-            files.put(src="secrets/wpa_supplicant.conf", dest="/etc/wpa_supplicant/wpa_supplicant.conf")
-
-        files.template(
-            src="templates/net/singlenic.network.j2",
-            dest="/etc/systemd/network/20-bigasterisk.network",
-            create_remote_dir=True,
-        )
-        apt.packages(packages=['network-manager'], present=False)
+if host.name == 'ditto':
+    files.template(
+        src="files/net/ditto-netplan.yaml",
+        dest="/etc/netplan/00-installer-config.yaml",
+        create_remote_dir=True,
+    )
 
     systemd.service(service='systemd-networkd.service', enabled=True, running=True, restarted=True)
-
-    # delete?
-    # # TODO this breaks wireguard wg on garage, i think. workaround:
-    # if host.name == 'garage':
-    #     server.shell('ip -4 address add 10.5.0.14/24 dev wg0')
--- a/package_lists.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/package_lists.py	Sun Apr 21 17:07:23 2024 -0700
@@ -91,7 +91,7 @@
     'wakeonlan',
 ]
 
-for_bang_ditto = [
+for_ditto = [
     'dnsmasq',
     'nfs-common',
     'openntpd',
--- a/packages.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/packages.py	Sun Apr 21 17:07:23 2024 -0700
@@ -1,12 +1,10 @@
 from io import StringIO
+
 from pyinfra import host
-from pyinfra.facts.server import LinuxDistribution
 from pyinfra.operations import apt, files, server, systemd
 
 import package_lists
 
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
-
 
 def kitty():
     vers = '0.31.0'  # see https://github.com/kovidgoyal/kitty/releases
@@ -52,12 +50,12 @@
 
 def proper_locate():
     apt.packages(packages='mlocate', present=False)
-    if not is_pi and host.name not in ['prime', 'pipe']:
+    if 'pi' not in host.groups and host.name not in ['prime', 'pipe']:
         apt.packages(packages='plocate')
 
 
 def proper_man():
-    if host.name in ['pipe', 'prime'] or is_pi:
+    if 'small' in host.groups or 'pi' in host.groups:
         apt.packages(packages=['mandb'], present=False)
 
 
@@ -65,14 +63,12 @@
     systemd.service(service='nginx', enabled=False, running=False)
 
 
-kw = dict(present=True, latest=True)
-
-apt.packages(packages=package_lists.setup, **kw)
+apt.packages(packages=package_lists.setup, latest=True)
 
 
 def roblox():
-    server.shell('flatpak install -y org.freedesktop.Platform/x86_64/23.08')
-    server.shell('flatpak install -y flathub org.vinegarhq.Vinegar')  # (roblox runner)
+    server.shell(commands='flatpak install -y org.freedesktop.Platform/x86_64/23.08')
+    server.shell(commands='flatpak install -y flathub org.vinegarhq.Vinegar')  # (roblox runner)
     files.put(
         src=StringIO(
             #"#!/bin/sh\nexec flatpak run org.vinegarhq.Vinegar player run 'roblox-player:1'\n"
@@ -97,35 +93,28 @@
     ])
 
 
-if not is_pi:
-    if host.name != 'pipe':
-        apt.packages(packages=['reptyr'])
-    kitty()
-else:
-    apt.packages(packages=package_lists.pi_setup)
-
 proper_locate()
 proper_man()
 
-apt.packages(packages=package_lists.general, **kw)
-apt.packages(packages=package_lists.debug, **kw)
-
-if host.name in ["bang", 'ditto']:
-    apt.packages(packages=package_lists.for_bang_ditto, **kw)
+apt.packages(packages=package_lists.general, latest=True)
+apt.packages(packages=package_lists.debug, latest=True)
 
 if host.name == "pipe":
-    apt.packages(packages=package_lists.for_pipe, **kw)
+    apt.packages(packages=package_lists.for_pipe, latest=True)
+
+if host.name != 'pipe':
+    apt.packages(packages=['reptyr'])
 
 if host.name == "prime":
-    apt.packages(packages=package_lists.for_prime, **kw)
+    apt.packages(packages=package_lists.for_prime, latest=True)
 
 if host.name == 'plus':
-    apt.packages(packages=package_lists.laptop, **kw)
+    apt.packages(packages=package_lists.laptop, latest=True)
 
-if host.name in ['dash', 'slash', 'ditto', 'dot']:
-    apt.packages(packages=package_lists.k8s_node_with_nvidia_gpu(host.name))  # no kw, or apt will remove nvidia-utils-VERS (!)
+if host.data.get('gpu'):
+    apt.packages(packages=package_lists.k8s_node_with_nvidia_gpu(host.name))
 
-if host.name in ['dash', 'slash', 'ditto']:
+if host.data.get('k8s_admin'):
     podman()
 
 is_kube_node = host.name in ['dash', 'slash', 'ditto', 'ws-printer', 'li-drums']
@@ -133,19 +122,20 @@
     kube_node()
 
 if host.name == 'ditto':
+    apt.packages(packages=package_lists.for_ditto, latest=True)
     # should have happened in the previous step, but it gets reverted.
     apt.packages(packages=['nvidia-utils-535-server'])
 
-if not is_pi:
-    apt.packages(packages=package_lists.non_pi, **kw)
-else:
-    # move to another file?
-    files.template(src="templates/pigpiod.service.j2", dest="/etc/systemd/system/pigpiod.service")
-    systemd.service(service='pigpiod', daemon_reload=True, enabled=True)
+if 'pi' not in host.groups:
+    kitty()
+    apt.packages(packages=package_lists.non_pi, latest=True)
+
+if 'pi' in host.groups:
+    apt.packages(packages=package_lists.pi_setup)
 
 desktop_env = host.name in ['dash', 'slash', 'plus', 'dot', 'squib', 'pillow']
 if desktop_env:
-    apt.packages(packages=package_lists.xorg + package_lists.desktop, **kw)
+    apt.packages(packages=package_lists.xorg + package_lists.desktop, latest=True)
     roblox()
 if desktop_env or host.name in ['bang', 'ditto']:
     pdm()
--- a/pipe.py	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-from pyinfra.operations import apt, files, git, server, systemd
-
--- a/ssh.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/ssh.py	Sun Apr 21 17:07:23 2024 -0700
@@ -2,7 +2,6 @@
 from pyinfra.facts.server import LinuxDistribution
 from pyinfra.operations import files, systemd
 
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
 
 systemd.service(
     service='ssh',
@@ -12,7 +11,7 @@
 
 files.line(path='/etc/ssh/ssh_config', line="HashKnownHosts", replace="HashKnownHosts no")
 
-if not is_pi:
+if 'pi' not in host.groups:
     files.line(path='/etc/ssh/sshd_config', line="^UseDNS\b", replace="UseDNS no")
     # MAYBE plus needs this fix: adding ListenAddress 0.0.0.0 to /etc/ssh/sshd_config
     systemd.service(service='sshd', reloaded=True)
--- a/sync.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/sync.py	Sun Apr 21 17:07:23 2024 -0700
@@ -41,7 +41,7 @@
 
 # primary instance is in k8s (/my/serv/filesync/syncthing); the rest are run with systemd.
 # Configs are in ~/.config/syncthing/ on each box
-if host.name in ['dash', 'dot', 'slash', 'plus', 'bang', 'ditto', 'pillow']:
+if host.data.get('syncthing'):
     apt.packages(packages=['syncthing'], present=False)
     user = 'ari' if host.name == 'dot' else 'drewp'
 
--- a/system.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/system.py	Sun Apr 21 17:07:23 2024 -0700
@@ -1,10 +1,11 @@
 import os
+from io import StringIO
+from typing import cast
 
+import pyinfra
 from pyinfra import host
-from pyinfra.facts.server import LinuxDistribution
 from pyinfra.operations import apt, files, server, systemd
 
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
 TZ = 'America/Los_Angeles'
 
 
@@ -18,6 +19,7 @@
     if os.path.exists(fstab_file):
         files.put(src=fstab_file, dest='/etc/fstab')
 
+
 def pi_tmpfs():
     for line in [
             'tmpfs /var/log tmpfs defaults,noatime,mode=0755 0 0',
@@ -57,6 +59,7 @@
                        fam='tcp')
         systemd.service(service=svc, enabled=True, restarted=True)
 
+
 def minecraft_forward():
     port = 25765
     for fam in ['tcp', 'udp']:
@@ -69,14 +72,41 @@
                        fam=fam)
         systemd.service(service=svc, enabled=True, restarted=True)
 
+
+def pigpiod():
+    files.put(src="files/pigpiod.service", dest="/etc/systemd/system/pigpiod.service")
+    systemd.service(service='pigpiod', daemon_reload=True, enabled=True)
+
+
+def rpi_iscsi_volumes():
+    iscsi_dir = '/d2/rpi-iscsi'
+    for pi_hostname in cast(list, pyinfra.inventory.get_group(name='pi')):
+        out = f'{iscsi_dir}/{pi_hostname}.disk'
+        files.directory(path=iscsi_dir)
+        server.shell(commands=f'dd if=/dev/zero of={out} count=0 bs=1 seek=10G conv=excl || true')
+        files.put(dest=f"/etc/tgt/conf.d/{pi_hostname}.conf",
+                  src=StringIO(f"""
+<target iqn.2024-03.com.bigasterisk:{pi_hostname}.target>
+    backing-store {out}
+    initiator-name iqn.2024-03.com.bigasterisk:{pi_hostname}.initiator
+</target> 
+                            """))
+    # restarting is disruptive to connected pis, and they might need to be
+    # visited:
+    #systemd.service(service='tgt.service', running=True, restarted=True)
+
+
 server.hostname(hostname=host.name)
 timezone()
 fstab()
 
-if not is_pi:
+if host.name == 'ditto':
+    rpi_iscsi_volumes()
+
+if 'pi' not in host.groups:
     files.line(path='/etc/update-manager/release-upgrades', line="^Prompt=", replace="Prompt=normal")
 
-if is_pi and host.name != 'pipe':
+if 'pi' in host.groups:
     pi_tmpfs()
 
 if host.name in ['bang', 'pipe', 'ditto']:
@@ -85,11 +115,16 @@
 if host.name in ['bang', 'ditto']:
     nfs_server()
 
+if host.name in ['prime', 'ditto']:
+    smaller_journals()
+
 if host.name == 'prime':
-    smaller_journals()
     web_forward()
     minecraft_forward()
 
+if 'pi' in host.groups:
+    pigpiod()
+
 # for space, consider:
 # k3s crictl rmi --prune
 # snap list --all | while read snapname ver rev trk pub notes; do if [[ $notes = *disabled* ]]; then snap remove "$snapname" --revision="$rev"; fi; done
--- a/templates/boot_config.txt.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-# written by pyinfra
-
-# For more options and information see
-# http://rpf.io/configtxt
-# Some settings may impact device functionality. See link above for details
-
-# uncomment if you get no picture on HDMI for a default "safe" mode
-#hdmi_safe=1
-
-# uncomment this if your display has a black border of unused pixels visible
-# and your display can output without overscan
-#disable_overscan=1
-
-# uncomment the following to adjust overscan. Use positive numbers if console
-# goes off screen, and negative if there is too much border
-#overscan_left=16
-#overscan_right=16
-#overscan_top=16
-#overscan_bottom=16
-
-# uncomment to force a console size. By default it will be display's size minus
-# overscan.
-#framebuffer_width=1280
-#framebuffer_height=720
-
-# uncomment if hdmi display is not detected and composite is being output
-#hdmi_force_hotplug=1
-
-# uncomment to force a specific HDMI mode (this will force VGA)
-#hdmi_group=1
-#hdmi_mode=1
-
-# uncomment to force a HDMI mode rather than DVI. This can make audio work in
-# DMT (computer monitor) modes
-#hdmi_drive=2
-
-# uncomment to increase signal to HDMI, if you have interference, blanking, or
-# no display
-#config_hdmi_boost=4
-
-# uncomment for composite PAL
-#sdtv_mode=2
-
-#uncomment to overclock the arm. 700 MHz is the default.
-#arm_freq=800
-
-# Uncomment some or all of these to enable the optional hardware interfaces
-#dtparam=i2c_arm=on
-#dtparam=i2s=on
-#dtparam=spi=on
-
-# Uncomment this to enable infrared communication.
-#dtoverlay=gpio-ir,gpio_pin=17
-#dtoverlay=gpio-ir-tx,gpio_pin=18
-
-# Additional overlays and parameters are documented /boot/overlays/README
-
-# Enable audio (loads snd_bcm2835)
-dtparam=audio=off
-
-[pi4]
-# Enable DRM VC4 V3D driver on top of the dispmanx display stack
-dtoverlay=vc4-fkms-v3d
-max_framebuffers=2
-
-[all]
-#dtoverlay=vc4-fkms-v3d
-dtoverlay=w1-gpio,gpiopin=17,pullup=y
-gpu_mem=16
-
-########################
-# unreviewed
-
-# for beacon
-#enable_uart=1
-#dtoverlay=pi3-miniuart-bt
-#core_freq=250
-
-# for tiny_screen
-#- lineinfile: dest=/boot/config.txt line="dtparam=spi=on" regexp="^dtparam=spi="
--- a/templates/dnsmasq/dnsmasq-mtail.service.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# written by pyinfra
-
-[Unit]
-Description=dnsmasq-mtail for 10.2 network
-After=dnsmasq_10.2.service
-
-[Service]
-Type=simple
-
-ExecStart=zsh /opt/dnsmasq/10.2/run_mtail.sh
-
-[Install]
-WantedBy=multi-user.target
--- a/templates/dnsmasq/metrics.mtail.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-counter dnsmasq_no_addr_errors
-/no address available/ {
-    dnsmasq_no_addr_errors++
-}
-
-counter dnsmasq_dhcp_requests
-/DHCPREQUEST/ {
-    dnsmasq_dhcp_requests++
-}
-
-counter dnsmasq_dhcp_acks
-/DHCPACK/ {
-    dnsmasq_dhcp_acks++
-}
-
-counter dnsmasq_dhcp_discovers
-/DHCPDISCOVER/ {
-    dnsmasq_dhcp_discovers++
-}
-
-counter dnsmasq_dhcp_offers
-/DHCPOFFER/ {
-    dnsmasq_dhcp_offers++
-}
-
-gauge dnsmasq_dns_queries_answered_locally
-gauge dnsmasq_dns_queries_forwarded by server
-gauge dnsmasq_dns_queries_retried_or_failed by server
-
-/queries forwarded (?P<fwd>\d+), queries answered locally (?P<loc>\d+)/ {
-    dnsmasq_dns_queries_answered_locally = $loc
-}
-
-/server (?P<svr>\S+)#53: queries sent (?P<sent>\d+), retried or failed (?P<fail>\d+)/ {
-    dnsmasq_dns_queries_forwarded[$svr] = $sent
-    dnsmasq_dns_queries_retried_or_failed[$svr] = $fail
-}
\ No newline at end of file
--- a/templates/dnsmasq/run_mtail.sh	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-#!/bin/zsh
-STATS_PERIOD=2m
-while (true) { pkill --signal USR1 --oldest --full /usr/sbin/dnsmasq; sleep ${STATS_PERIOD} } &
-
-rm -f /tmp/dnsmasq_log_pipe
-mkfifo /tmp/dnsmasq_log_pipe
-
-{ journalctl -fu dnsmasq_10.2.service > /tmp/dnsmasq_log_pipe } &
-
-mtail -port 9991 -logtostderr -logs /tmp/dnsmasq_log_pipe  -progs /opt/dnsmasq/10.2
-#-disable_fsnotify -poll_interval ${STATS_PERIOD}
\ No newline at end of file
--- a/templates/hosts.j2	Sun Apr 21 17:01:13 2024 -0700
+++ b/templates/hosts.j2	Sun Apr 21 17:07:23 2024 -0700
@@ -11,7 +11,7 @@
 ff02::2 ip6-allrouters
 
 
-{% if host.name in ['prime', 'plus'] %}
+{% if 'laptop' in host.groups or 'hosted' in host.groups %}
 10.5.0.1 bang bang.bigasterisk.com bang5 bang5.bigasterisk.com 
 10.5.0.7 ditto ditto.bigasterisk.com ditto5 ditto5.bigasterisk.com 
 10.5.0.5 dash
--- a/templates/net/bang_10.2.network.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# written by pyinfra
-
-[Match]
-MACAddress=60:e3:27:04:4a:85
-
-[Network]
-DHCP=no
-Address=10.2.0.1/16
-DNS=10.2.0.3
-Gateway=10.2.0.3
--- a/templates/net/ditto-netplan.yaml.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-# written by pyinfra
-network:
-  ethernets:
-    eno1:
-      addresses:
-        # name=ditto
-        - 10.2.0.133/16
-        # name=mqtt1 etc
-        - 10.2.0.11/16
-        - 10.2.0.12/16
-        - 10.2.0.13/16
-        - 10.2.0.14/16
-      routes:
-        - to: default
-          via: 10.2.0.3
-      nameservers:
-        addresses: [10.2.0.3]
-    enp5s0:
-      dhcp4: true
-    ens3:
-      dhcp4: true
-  version: 2
--- a/templates/net/house_net.service.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-# written by pyinfra
-
-[Unit]
-After=network-online.target nss-lookup.target
-Wants=network-online.target nss-lookup.target
-
-[Service]
-Type=oneshot
-ExecStart=sh -c "sysctl net.ipv4.ip_forward=1 && /usr/sbin/iptables -A POSTROUTING --table nat --out-interface eth0 --jump MASQUERADE"
-RemainAfterExit=yes
-
-
-[Install]
-WantedBy=multi-user.target
--- a/templates/net/pipe_10.2.network.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-# written by pyinfra
-
-[Match]
-# usb dongle
-MACAddress=00:05:1b:33:3e:81
-
-[Network]
-DHCP=no
-Address=10.2.0.3/16
-# vip for the filtered dns server we give clients who are to have sometimes-filtered domains
-Address=10.2.0.4/16
-DNS=10.2.0.3
-Domains=bigasterisk.com
-
--- a/templates/net/pipe_isp.network.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-# written by pyinfra
-
-[Match]
-# onboard eth
-MACAddress=00:1e:06:43:20:d0
-
-[Network]
-DHCP=no
-Address=192.168.42.3/24
-Gateway=192.168.42.1
-DNS=10.2.0.1
-Domains=bigasterisk.com
\ No newline at end of file
--- a/templates/net/prime.network.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-# written by pyinfra
-
-# see systemd.network(5)
-
-[Match]
-MACAddress={{ mac }}
-
-[Network]
-Address=162.243.138.136/24
-Gateway=162.243.138.1
-DNS=10.5.0.1%wg0
-DNS=8.8.8.8
-DNS=8.8.4.4
-Domains=bigasterisk.com
\ No newline at end of file
--- a/templates/net/singlenic.network.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# written by pyinfra
-
-[Match]
-Name=*
-
-[Network]
-DHCP=yes
-# this sauce may or may not help with k3s
-LinkLocalAddressing=yes
-IPForward=yes
\ No newline at end of file
--- a/templates/pigpiod.service.j2	Sun Apr 21 17:01:13 2024 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# written by pyinfra
-
-[Unit]
-Description=Daemon required to control GPIO pins via pigpio
-
-[Service]
-Type=simple
-ExecStart=/usr/bin/pigpiod -g -p 8888
-
-[Install]
-WantedBy=multi-user.target
--- a/templates/sources.list.j2	Sun Apr 21 17:01:13 2024 -0700
+++ b/templates/sources.list.j2	Sun Apr 21 17:07:23 2024 -0700
@@ -1,26 +1,22 @@
 # written by pyinfra
 
-{% if host.name in ['dash', 'squib', 'slash', 'dot', 'plus', 'ditto', 'pillow'] %}
+{% if 'big' in host.groups or 'laptop' in host.groups %}
 deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/ms.gpg] http://packages.microsoft.com/repos/code stable main
 deb [arch=amd64 signed-by=/etc/apt/keyrings/chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main
 deb [arch=amd64,i386 signed-by=/usr/share/keyrings/steam.gpg] https://repo.steampowered.com/steam/ stable steam
 deb [signed-by=/etc/apt/keyrings/unityhub.gpg] https://hub.unity3d.com/linux/repos/deb stable main
-{% endif %}
-
-{% if host.name in ['dash', 'squib', 'plus', 'bang', 'slash', 'dot', 'ditto', 'pillow'] %}
 deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main
 {% endif %}
 
-# k8s node with nvidia gpu
-{% if host.name in ['dash', 'ditto', 'slash', 'dot'] %}
+{% if host.data.get('gpu') %}
 deb [signed-by=/etc/apt/keyrings/nvidia.gpg] https://nvidia.github.io/libnvidia-container/stable/deb/$(ARCH) /
 {% endif %}
 
-{% if host.name in ['ditto'] %}
+{% if host.data.get('coral') %}
 deb [signed-by=/etc/apt/keyrings/coral.gpg] https://packages.cloud.google.com/apt coral-edgetpu-stable main
 {% endif %}
 
-{% if host.name in ['dash', 'squib', 'plus', 'bang', 'slash', 'dot', 'prime', 'ditto', 'pillow'] %}
+{% if 'big' in host.groups or 'laptop' in host.groups or 'hosted' in host.groups %}
 deb http://us.archive.ubuntu.com/ubuntu mantic main restricted
 deb http://us.archive.ubuntu.com/ubuntu mantic multiverse
 deb http://us.archive.ubuntu.com/ubuntu mantic universe
@@ -33,7 +29,7 @@
 deb http://us.archive.ubuntu.com/ubuntu mantic-updates universe
 {% endif %}
 
-{% if host.name in ['pipe'] %}
+{% if host.name == 'pipe' %}
 # seems stuck on jammy since http://deb.odroid.in/n2/ and https://wiki.odroid.com/odroid-n2/os_images/ubuntu don't have anything newer (2023-12-28)
 deb [signed-by=/etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg] http://archive.canonical.com/ubuntu jammy partner
 deb [signed-by=/etc/apt/trusted.gpg] http://deb.odroid.in/n2/ jammy main
@@ -51,7 +47,7 @@
 deb [signed-by=/etc/apt/trusted.gpg] http://ppa.launchpad.net/hardkernel/ppa/ubuntu jammy main
 {% endif %}
 
-{% if host.name in ['garage', 'ws-printer', 'gn-music', 'li-drums'] %}
+{% if 'pi' in host.groups %}
 deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
 deb http://deb.debian.org/debian-security/ bookworm-security main contrib non-free non-free-firmware
 deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
--- a/templates/wireguard/wg0.conf.j2	Sun Apr 21 17:01:13 2024 -0700
+++ b/templates/wireguard/wg0.conf.j2	Sun Apr 21 17:07:23 2024 -0700
@@ -21,14 +21,13 @@
     {{ peer_block('gn-music',    '10.5.0.32/32') }}
     {{ peer_block('li-drums',    '10.5.0.33/32') }}
 {% elif host.name == 'prime' %}
+# this list is wg_roamer & ditto & phone:
     {{ peer_block('ditto',       '10.5.0.0/24') }}
     {{ peer_block('drew-note10', '10.5.0.112/32') }}
     {{ peer_block('plus',        '10.5.0.110/32', 'public.bigasterisk.com:1195') }}
     {{ peer_block('pillow',      '10.5.0.111/32', 'public.bigasterisk.com:1195') }}
-
-{% elif host.name in ['plus','pillow'] %}
+{% elif host.data.get('wg_roamer') %}
     {{ peer_block('prime',       '10.5.0.0/24', 'public.bigasterisk.com:1195', 50) }}
-    {# {{ peer_block('ditto',        '10.5.0.0/24', 'ditto:1195', 50) }} #}
 {% else %}
 # note that hosts on filtered dns cannot currently look up the name 'ditto'
     {{ peer_block('ditto',        '10.5.0.0/24', '10.2.0.133:1195', 50) }}
--- a/users.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/users.py	Sun Apr 21 17:07:23 2024 -0700
@@ -2,16 +2,11 @@
 from pyinfra.operations import server
 from pyinfra.facts.server import LinuxDistribution
 
-is_pi = host.get_fact(LinuxDistribution)['name'] in ['Debian', 'Raspbian GNU/Linux']
 
 # raspbian took 1000 for 'pi' group, but drewp is rarely used on pi
 # setups so hopefully it won't matter much that drew group has a
 # different id.
-drewp_uid, drewp_gid = 501, 1000
-if host.name in ['pillow', ]:
-    drewp_uid, drewp_gid = 1000, 1000
-if host.name in ['pipe', 'garage', 'ws-printer', 'gn-music', 'li-drums']:
-    drewp_uid, drewp_gid = 1001, 501
+drewp_uid, drewp_gid = host.data.drewp_uid, host.data.drewp_gid
 drewp_groups = [
     'lp', 'adm', 'dialout', 'cdrom', 'sudo', 'audio', 'video', 'plugdev',
     'games', 'users', 'netdev', 'i2c', 'input', 'spi', 'gpio', 'fuse',
@@ -46,8 +41,7 @@
 server.user(user='drewp', uid=drewp_uid, group='drewp', groups=drewp_groups)
 
 
-
-if not is_pi:
+if 'pi' not in host.groups:
     server.group(group='adm', gid=4)
     server.group(group='cdrom', gid=24)
     server.group(group='dialout', gid=20)
@@ -89,12 +83,11 @@
     server.group(group='kelsi', gid=1008)
     server.user(user='kelsi', uid=1008, group='elastic')
 
-    if host.name != 'pipe':  # https://github.com/Fizzadar/pyinfra/issues/835
-        server.group(group='drewnote', gid=1009)
-        server.user(user='drewnote', uid=1009)
+    server.group(group='drewnote', gid=1009)
+    server.user(user='drewnote', uid=1009)
 
-        server.group(group='prometheus', gid=1010)
-        server.user(user='prometheus', uid=1010)
+    server.group(group='prometheus', gid=1010)
+    server.user(user='prometheus', uid=1010)
 
 # delete when garage is diskless
 if host.name == 'garage':
--- a/wireguard.py	Sun Apr 21 17:01:13 2024 -0700
+++ b/wireguard.py	Sun Apr 21 17:07:23 2024 -0700
@@ -14,9 +14,7 @@
 
 
 def peer_block(hostname, allowed_ips, endpoint=None, keepalive=None):
-    # if allowed_ips.startswith('10.5'):
-    #     # k3s nets also need to travel over wg
-    #     allowed_ips += ', 10.42.0.0/24, 10.43.0.0/24'
+    # allowed_ips should be determined mostly from host.data.wireguard_address
 
     public_key = wireguard_pubkey.pubkey[hostname]
     out = f'''\
@@ -33,6 +31,22 @@
     return out
 
 
+def get_priv_key(wireguard_interface) -> str:
+    priv_key_lines = host.get_fact(FindInFile, path=f'/etc/wireguard/{wireguard_interface}.conf', pattern=r'PrivateKey.*')
+    if not priv_key_lines:
+        priv_key = subprocess.check_output(['wg', 'genkey']).strip().decode('ascii')
+    else:
+        priv_key = priv_key_lines[0].split(' = ')[1]
+    return priv_key
+
+
+def compute_pub_key(priv_key: str) -> str:
+    pub_key = subprocess.check_output(['wg', 'pubkey'], input=priv_key.encode('ascii')).strip().decode('ascii')
+    # todo: if this was new, it should be added to a file of pubkeys that
+    # peer_block can refer to. meanwhile, edit the template.
+    return pub_key
+
+
 for wireguard_interface in ['wg0', 'bogasterisk']:
     if wireguard_interface == 'bogasterisk' and host.name != 'prime':
         continue
@@ -44,15 +58,10 @@
 
     # new pi may fail with 'Unable to access interface: Protocol not supported'. reboot fixes.
 
-    priv_key_lines = host.get_fact(FindInFile, path=f'/etc/wireguard/{wireguard_interface}.conf', pattern=r'PrivateKey.*')
-    if not priv_key_lines:
-        priv_key = subprocess.check_output(['wg', 'genkey']).strip().decode('ascii')
-    else:
-        priv_key = priv_key_lines[0].split(' = ')[1]
+    priv_key = get_priv_key(wireguard_interface)
 
-    pub_key = subprocess.check_output(['wg', 'pubkey'], input=priv_key.encode('ascii')).strip().decode('ascii')
-    # todo: if this was new, it should be added to a file of pubkeys that
-    # peer_block can refer to. meanwhile, edit the template.
+    # unused since I still hand-maintain wireguard_pubkey.py :(
+    # pub_key = compute_pub_key(priv_key)
 
     files.template(
         src=f'templates/wireguard/{wireguard_interface}.conf.j2',
@@ -67,9 +76,4 @@
     files.template(src='templates/wireguard/wg.service.j2',
                    dest=f'/etc/systemd/system/{svc}',
                    wireguard_interface=wireguard_interface)
-    systemd.service(service=svc, enabled=True, restarted=True, daemon_reload=True)
-
     systemd.service(service=svc, daemon_reload=True, restarted=True, enabled=True)
-
-# if host.name == 'bang':
-#     systemd.service(service=f'dnsmasq_10.5', enabled=True, restarted=True, daemon_reload=True)