Commit Graph
35 Commits
Author SHA1 Message Date
Andreas WredeandClaude Fable 5 fe9e6d3b30 failover: resolve MBIM/QMI modems to their wwan netdev
NM reports a gsm connection's device as the control port (cdc-wdm0), but
IP + routes live on the wwan netdev (wwu1u2i4 on the Quectel EC25-AF), so
probes and route enforcement silently saw nothing (cellular stuck "down",
no metric). Map connection-keyed WANs through GENERAL.IP-IFACE, and let
the gateway fallback query the connection since the netdev isn't an NM
device.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:28:12 -04:00
Andreas WredeandClaude Fable 5 7c36757d35 sysctl: correct the use_tempaddr comment with the real root cause
The rotating privacy address was not itself the packet-loss mechanism:
the 2026-07-13 discrimination experiment (4 flows, dual-ended capture)
showed the loss keyed to hbc's exact 5-tuple — upstream per-flow state
poisoned when the flow was created during boot/apply address churn, kept
alive forever by the 10s heartbeat cadence. Fresh flows to the same
host/port were clean; restarting hbc fixed it instantly. use_tempaddr=0
stays: it removes one source of the churn that poisons newborn flows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuPg8vz6FmDUj4SgEJ525C
2026-07-13 13:13:08 -04:00
Andreas WredeandClaude Sonnet 5 09259eb6cc sysctl: force use_tempaddr=0 to beat Ubuntu's 55-ipv6-privacy.conf default
Root cause of the flow-selective IPv6 heartbeat loss investigated
2026-07-12/13: Ubuntu ships /usr/lib/sysctl.d/55-ipv6-privacy.conf with
use_tempaddr=2 system-wide. Nothing on wlan0 overrode it before this
morning's netplan fix, so the interface carried a rotating privacy
address alongside the stable one. hbc's long-lived flow eventually
straddled a temp-address deprecation event mid-flight, which looked
exactly like random upstream packet loss (in-transit, IPv6-only,
flow-selective) and cost a day of packet captures before the cause
turned out to be this default fighting the WAN config instead.

The netplan/NM ipv6.ip6-privacy=false fix already forces this per
connection, but that's a timing-dependent override racing a package
default. This makes it explicit and permanent: 99- loads after (and
wins over) 55- the next time sysctl --system runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 12:06:33 -04:00
Andreas WredeandClaude Sonnet 5 daa545f15c deploy.sh: warn on netplan drift between repo and /etc/netplan
The 07-12 IPv6 fix sat in ap/50-van-wan.yaml for a day without ever being
copied to /etc/netplan/ or applied — deploy.sh deliberately skips netplan
(applying it flaps the uplinks) so nothing caught the gap. Now it diffs
the two and prints the manual deploy commands when they disagree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 11:04:19 -04:00
andreas 4aae62372e deument last change 2026-07-12 12:22:45 -04:00
Andreas WredeandClaude Fable 5 776f020af3 cockpit: unified Clients card (Wi-Fi + wired via bridge FDB)
New "Clients" card lists everything on the van LAN in one table — hostname,
IP, MAC, connection (5GHz / 2.4GHz / LAN eth0 / LAN USB), signal, TX rate.
Wi-Fi rows come from the hostapd station dumps as before; wired rows from
learned bridge-FDB entries on the LAN ports (the port's own MAC is
"permanent", and FDB duplicates entries per vlan — both filtered), enriched
with IP/hostname from dnsmasq leases + neighbor table.

The per-band station tables move out of the Access Points card (which keeps
its status line + client count), and the WAN/Uplinks table now hides
unmanaged devices so the networkd-owned LAN ports don't show up as ghost
WAN rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:44:57 -04:00
Andreas WredeandClaude Fable 5 a36b64c7c4 lan: repurpose onboard eth0 as an internal LAN port on br0
With wifi + Starlink + cellular as uplinks, the onboard GbE earns its keep
as a wired LAN port instead of the metric-150 ethernet WAN: eth0 leaves
netplan/NM (added to van-ap-unmanaged.conf) and joins br0 via networkd
(23-van-lan-eth0.network), so wired clients get 10.42.0.x DHCP/DNS and NAT
exactly like Wi-Fi clients. The eth WAN is gone from van-failover's config.

Cutover notes: delete the old netplan-eth0 / stray eth0 NM profiles, then
nmcli general reload + networkctl reload/reconfigure eth0. NEVER cable this
port (or the USB LAN dongle) back into an upstream LAN — dnsmasq on br0
would serve rogue DHCP there; STP stays off, so don't cable both wired LAN
ports to the same switch either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:44:44 -04:00
Andreas WredeandClaude Fable 5 751facd511 starlink: dish route + status card on the Van Router page
The Starlink RTL8153 gets a declarative NM profile in 50-van-wan.yaml
(replaces the auto 'Wired connection 1') with a 192.168.100.1/32 link route,
so the dish's management address stays reachable from the router and — via
the existing !br0 masquerade — from the van LAN, regardless of which WAN
holds the default route.

New Cockpit "Starlink" card queries the dish's gRPC API (:9200, get_status
via grpcurl — not packaged in apt, deploy.sh warns when missing) inside the
existing single batched spawn: online/obstructed/outage pill, alerts,
uptime, sw version, PoP latency, down/up throughput, obstruction %.
Sentinels distinguish adapter-absent / no-grpcurl / dish-unreachable.

Gotcha captured while cutting over: a WAN profile without ipv4.route-metric
makes NM re-assert its DHCP default (metric ~101) against van-failover's
enforce_route pruning every cycle; the profile metric must match (failover's
set_profile_metric does this on health transitions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:06:43 -04:00
Andreas WredeandClaude Fable 5 2da97d26f4 thermal: watch NVMe/RP1 temps, fan stall, and undervoltage on the Pi 5
van-thermal grows sensor kinds beyond plain temperatures: "fan" alerts when
pwmfan is commanded on (pwm > 0) but reads 0 RPM (warn on first sample, crit
if it persists — 0 RPM with pwm 0 is just the firmware idling a cool SoC),
and "undervolt" goes crit on the live rpi_volt alarm plus a sticky warn off
the firmware's latched since-boot bit, so dips shorter than the 10s sample
interval still surface once. All kinds share the existing hysteresis /
journal / Pushover machinery (fan 🌀, undervolt ).

Config adds nvme Composite (65/70), rp1_adc (80/85 — the RP1 die drives all
USB/eth I/O), fan, and undervolt to the existing cpu sensor. The CSV logger
now derives per-kind columns and self-rotates when the header changes; the
Cockpit Temps card renders RPM + pwm duty and undervoltage state alongside
the temperatures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 06:41:17 -04:00
Andreas WredeandClaude Fable 5 6fe683000d thermal+ha: don't lose boot-time pages; stop HA gracefully
van-thermal fires its first sample seconds after start, and the Pi often
boots hot — order after network-online.target and retry network failures
in the Pushover send path (3 attempts, 15s apart) so that page survives
DNS not being up yet. Non-200 responses still don't retry.

The Quadlet gets StopTimeout=120: podman's default 10s window SIGKILLed
HA mid-flush and the recorder complained about an unclean sqlite shutdown
on every start. Verified clean after a full stop/start cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:06:16 -04:00
Andreas WredeandClaude Fable 5 3add42a0cb ha: replace the HAOS VM with a native Podman Quadlet container
The fixed 2 GiB ha_van allocation starved the 4 GB Pi. HA Container now
runs on the host network (http://10.42.0.1:8123): ha/homeassistant.container
installs to /etc/containers/systemd/, config in /srv/homeassistant, host
D-Bus mounted for onboard Bluetooth (needs apparmor=unconfined — Ubuntu's
dbus-daemon mediates per AppArmor label and denies AddMatch to BlueZ —
plus NET_ADMIN/NET_RAW for habluetooth adapter recovery).

Drop the VM-era plumbing: the 10.42.0.50 dhcp-host pin becomes a
host-record for 10.42.0.1, and the legacy-URL DNAT + hairpin masquerade
go away entirely. ha_van.xml retired to git history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:46:30 -04:00
Andreas WredeandClaude Fable 5 8ab5a4cbd8 ha: fresh HAOS install + VNC console for the HAOS CLI
The backup-restore from the x86 instance left Core stopped with the
Supervisor idle and healthy-looking — no restart, download, or disk
activity, and it survived a guest reboot. The old config was minimal,
so: fresh 18.1 image, configure from scratch (old disk kept aside as
*.old-restored). Add VNC graphics + virtio-gpu while the domain was
down: the HAOS CLI runs only on the graphical console (no serial
getty), so without this there is no way into a broken guest — view it
via Cockpit's Virtual Machines page (cockpit-machines now installed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:16:17 -04:00
Andreas WredeandClaude Fable 5 c40662361f ha: move the Home Assistant VM onto the Pi as an aarch64 guest
The x86 ha_van stayed behind on wayback at the port (9bf1420 dropped
ha/ and its DNAT/lease); with the hub moved and the Pi owning
10.42.0.0/24, HA follows. New domain XML: machine=virt + AAVMF EFI,
virtio-scsi HAOS 18.1 aarch64 image, 2 GiB (data restored from an HA
full backup — the x86 qcow2 can't cross architectures). Same MAC, so
the dnsmasq pin (10.42.0.50 / "homeassistant") and the legacy
10.42.0.1:8123 DNAT + br0 hairpin port back verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:19:56 -04:00
Andreas WredeandClaude Fable 5 7a92548114 cockpit: fix bridge EMFILE — batch status reads into one spawn, raise fd limit
The Python cockpit-bridge frees spawn-pipe fds only at GC time; the Van
Router page's ~12 cockpit.spawn calls every 5s saw-toothed the bridge to
its 1024-fd soft limit, so the polkit admin-escalation spawn failed with
"Too many files open" whenever it landed near a peak.

Two-sided fix:
- vanrouter.js gathers all read-only status in ONE `sh -c` spawn per
  refresh, sections delimited by @@vr:<name>@@ marker lines (~12x less
  pipe churn). Mutating actions unchanged.
- cockpit-session@.service drop-in raises LimitNOFILE to 65535
  (hard limit is 524288), installed by deploy.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 18:32:24 -04:00
Andreas WredeandClaude Fable 5 7066207333 deploy: tolerate absent AP radios (hub not yet plugged in)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:06:13 -04:00
Andreas WredeandClaude Fable 5 9bf142074a port to Pi 4 'wan': onboard eth0+wlan0 as NM WANs, AP stack verbatim
The USB hub (5GHz + 2.4GHz AP dongles, Starlink + LAN RTL8153s) moves over
from wayback; MAC-derived wlx*/enx* names travel with it, so hostapd/
networkd/cockpit configs are unchanged. Pi diffs only: failover WAN list
(wlan0 wifi 100, eth0 150, starlink USB 200, Koodo 300), cpu_thermal
sensor, bcm2835 watchdog 10s, no HA DNAT/lease, and deploy.sh drops
battery/lid/heartbeat/ZT-dns. Netplan reference in ap/50-van-wan.yaml.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:59:34 -04:00
Andreas WredeandClaude Fable 5 23448294a4 cockpit: show IP + hostname for AP clients
Join station MACs against dnsmasq leases (IP + hostname), with the
kernel's br0 neighbor table as a fallback IP source for static-IP
clients.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:08:03 -04:00
Andreas WredeandClaude Fable 5 db16964d5a cockpit: show both AP bands with per-band restart
vanrouter.js hardcoded the 5GHz iface; generalize to an APS list
(iface + hostapd unit + band label). The Access Points card now renders
a status line, client table, and Restart button per band, and the WAN
table excludes both AP ifaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:31:21 -04:00
Andreas WredeandClaude Fable 5 e652b0f7bd ap: parallel 2.4GHz AP (hostapd-2g) on the Linksys WUSB6300v2 dongle
Second radio (wlxd8ec5e2faa8c, RTL8822BU/rtw88), same VanLink SSID+PSK,
ch6 HT20, bridged into br0 next to the 5GHz AP. Runs as its own hostapd
instance + own watchdog so a USB wedge on one radio never darkens the
other; van-ap-watchdog now takes conf path + unit name as args (defaults
unchanged). rtw88.conf disables deep power-save (rtw89 gotcha analog)
and the driver's USB2->3 self-upgrade (USB3 noise in the 2.4GHz band —
note: dongle currently sits in a USB3 port, so it still enumerates
SuperSpeed; move to a USB2 port if 2.4GHz range disappoints).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:03:16 -04:00
Andreas WredeandClaude Fable 5 a7819edbc2 ha: bridge the Home Assistant VM onto br0 as a first-class LAN device
The VM lived on libvirt's default NAT net (192.168.122.50) behind a
/etc/libvirt/hooks/network hook that inserted FORWARD accepts above
libvirt's REJECT. libvirtd restarts re-inserted its chains on top
without firing the hook, silently cutting clients off from HA.

Now the VM's NIC (virtio, was e1000) bridges into br0: pinned lease
10.42.0.50 / name 'homeassistant' via van-ap dnsmasq, mDNS discovery
works, and no firewall hook can rot. Legacy http://10.42.0.1:8123
kept working via DNAT + same-subnet hairpin masquerade. Hook, libvirt
DHCP reservation, and iptables rules removed; domain XML in ha/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:38:53 -04:00
Andreas WredeandClaude Opus 4.8 4117802f69 ap: watchdog checks link ground-truth, not just hostapd state
After a USB re-enumeration the rtw89 radio can come back as a fresh DOWN
netdev, dropped from br0, while the still-running hostapd never notices and
never exits — so Restart=always never fires. hostapd_cli keeps answering
state=ENABLED off stale in-memory state, so the watchdog considered the AP
healthy and left it dark (observed 2026-07-05: AP down ~16:41 until manual
hostapd restart).

Add link_healthy(): require the netdev to be operstate=up and still a port of
its bridge, alongside the existing state=ENABLED check. That catches the wedged
case the control socket can't see.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:56:12 -04:00
Andreas WredeandClaude Opus 4.8 877fa14d0c ap: keep AP beaconing across USB re-enumeration
Starlink's USB ethernet shares a hub with the rtw89 Wi-Fi adapter; when Starlink
flaps, the hub re-enumerates and tears the radio down, so hostapd loses its
interface and exits. Two stock defaults left the AP dark: Restart=on-failure
misses clean exits, and the 5-in-10s start limit makes systemd give up during a
re-enumeration storm.

- hostapd-restart.conf drop-in: Restart=always, RestartSec=5, StartLimitIntervalSec=0
  so hostapd retries forever until the interface returns (verified: SIGKILL -> back
  to state=ENABLED in ~7s).
- van-ap-watchdog daemon: backstop for the case systemd can't see (hostapd running
  but radio wedged). Polls hostapd_cli status; restarts hostapd if the iface is
  present but not ENABLED, waits it out if the iface is mid-re-enumeration.
- hostapd.conf: add ctrl_interface so the watchdog can read the real AP state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:36:14 -04:00
Andreas WredeandClaude Opus 4.8 830ef52641 ap: bridge wired LAN port (enx00e04c331140) into br0 with the AP
Add a wired LAN port on the same 10.42.0.0/24 segment as VanLink WiFi by
bridging both into br0. The gateway IP, dnsmasq, and NAT move off the wlan name
onto br0; hostapd (not networkd) enslaves the wlan via bridge=br0, since a
station-mode wlan can't be bridged until it's in AP mode. NM leaves the wired
port unmanaged so networkd owns it. Failover/Starlink (enxd8ec5eeb3512)
untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:46:03 -04:00
Andreas WredeandClaude Opus 4.8 c93997c06d thermal: alert on recovery (crit→warn, warn→normal)
van-thermal already logged falling crossings to the journal but only paged
Pushover on rising ones. announce() now returns the crossing direction, and a
falling crossing pages an "all-clear" when leaving a level we'd have alerted on,
so the phone that got the rising alert also gets the recovery. thermal-config
gains the pushover_level + shared credentials_path keys. Ignore __pycache__.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:46:03 -04:00
Andreas WredeandClaude Opus 4.8 8fcc959551 robustness: hardware watchdog + hbc heartbeat client
Two TODO Tier-1 items:

- Hardware watchdog: power/10-vanlink-watchdog.conf drop-in sets
  RuntimeWatchdogSec=20s + RebootWatchdogSec=5min; deploy.sh applies it via
  daemon-reexec. PID1 pets intel_oc_wdt; a >20s systemd hang hard-resets the box.

- Heartbeat / dead-man's switch: vendored hbc.yaml + hbc.service report to
  hbd.wrede.pvt (UDP 50003) every 15s as user andreas, shipping cpu/mem/disk/
  network/zfs metrics. The hbc binary is installed once via the heartbeat
  project's installer (README §4); deploy.sh starts the service when present.

Auto-power-on-when-mains-returns: documented as a BIOS-only option (not
OS-controllable on this ZenBook) in README §3 + TODO, per decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:20:09 -04:00
Andreas WredeandClaude Opus 4.8 97309970ec docs: add TODO.md robustness backlog
Tiered list of candidate improvements toward a robust unattended van router,
with an "already in place" section so nothing gets redone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:03:44 -04:00
Andreas WredeandClaude Opus 4.8 473a75c729 cockpit: battery/power card; van-battery publishes thresholds in state
New "Battery / Power" card shows mains vs battery, charge % with a severity-
colored bar (only bites while on battery), the last alert level, and the
configured warn/shutdown thresholds. van-battery now includes warn_levels +
shutdown_level in /run/van-battery/state.json so the card stays self-describing
instead of hardcoding the numbers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:56:20 -04:00
Andreas WredeandClaude Opus 4.8 52e948654e battery: alert on mains<->battery transitions
Pushover notification when the router loses mains (now on battery) and when
mains is restored. One alert per real transition; first sample is skipped so a
restart while already on battery doesn't false-fire. Independent of the
low-charge 25/20/15/10% alerts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:52:49 -04:00
Andreas WredeandClaude Opus 4.8 b75b5b16c7 battery: van-battery daemon (low-charge Pushover alerts + safe shutdown)
While on battery (AC0/online=0), sends escalating Pushover alerts at 25/20/15%
and at 10% sends a final alert then `systemctl poweroff` (after shutdown_grace
so the alert flushes first). Edge-triggered per discharge episode; re-arms when
mains returns; plug-out already-low collapses to a single alert (no burst).

Pushover token+user live only in /etc/van-battery/pushover.json (0600), seeded
from pushover.json.example on first deploy and never committed. Missing/
placeholder creds disable sending but NOT the shutdown — running flat must
always power down safely. Publishes /run/van-battery/state.json. Stdlib only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:48:24 -04:00
Andreas WredeandClaude Opus 4.8 ef6c1ce182 thermal: van-thermal daemon (CPU+NVMe monitor, alert, log)
Single sysfs sample loop drives three jobs off one daemon (modeled on
van-failover):

- live state at /run/van-thermal/state.json; new Cockpit "Temperatures"
  card reads it in the existing 5s loop (no per-refresh `sensors` shell-out)
- journal alerts on ok<->warn<->crit crossings, with clear_margin hysteresis
- throttled, self-rotating CSV history at /var/log/van-thermal.csv

Sensors resolved by hwmon name+label (coretemp/Package id 0, nvme/Composite),
never by hwmonN index (not stable across boots). Defaults: CPU warn 80 /
crit 95, NVMe warn 65 / crit 70. deploy.sh installs + enables the service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:32:13 -04:00
Andreas WredeandClaude Opus 4.8 35466f0141 power: keep wayback awake lid-closed (never-sleep)
wayback runs as an always-on router living lid-closed in the van, but
stock logind HandleLidSwitch=suspend would sleep it on lid close (even
on AC). Add a logind drop-in that ignores the lid in all states and
mask every sleep target so idle/GUI/stray `systemctl suspend` can't
suspend it either.

- power/10-vanlink-nolid.conf -> /etc/systemd/logind.conf.d/
- deploy.sh: install drop-in, mask sleep/suspend/hibernate/hybrid-sleep,
  restart systemd-logind
- README: power/ layout row, never-sleep operation notes + reversal recipe

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:16:59 -04:00
Andreas WredeandClaude Opus 4.8 387375de1e failover/boot: mask networkd-wait-online instead of RequiredForOnline=no
The previous fix (RequiredForOnline=no on the ZT link) could not work and the
~2min boot stall returned. systemd-networkd-wait-online (systemd 255) only
declares the network online when at least one managed, online-eligible link
goes routable. networkd here manages only the AP (wlxc…, local-only) and the ZT
overlay — and with the ZT link also marked RequiredForOnline=no (on top of the
AP link that already was), wait-online had zero candidate links and could never
satisfy "online", burning its full 120s timeout every boot. Journal confirmed:
"Timeout occurred while waiting for network connectivity" at exactly +120s,
gating network-online.target → zerotier-one (the recovery path).

Mask systemd-networkd-wait-online entirely — neither networkd link is a real
uplink, and real uplink readiness is already covered by NetworkManager-wait-online.
Wire the mask into deploy.sh, delete the now-dead RequiredForOnline=no drop-in,
and correct README §3/§5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:57:21 -04:00
Andreas WredeandClaude Opus 4.8 118f2f2f5e failover/boot: stop ZeroTier link from deadlocking network-online.target
systemd-networkd-wait-online waits for every managed networkd link to go
routable. The ZT link ztuga7c2kh only appears once zerotier-one starts, but
zerotier-one is ordered After=network-online.target — so wait-online blocked
on the not-yet-existing link until its 120s timeout, adding ~2min to every
boot and delaying ZeroTier (the recovery path).

Add a RequiredForOnline=no drop-in so the overlay link is excluded from the
gate; real uplink readiness is already covered by NetworkManager-wait-online
(networkd owns only the AP + ZT links here, neither a true uplink). Wire it
into deploy.sh and document the loop in README §3/§5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:44:26 -04:00
Andreas Wrede cc25dee7e2 initial commit 2026-06-28 15:09:28 -04:00
Andreas Wrede bd865831d6 first commit 2026-06-28 15:08:04 -04:00