From d02c0f7b67f40479e99a254db12260f56d548cc0 Mon Sep 17 00:00:00 2001 From: Stefan Wintermeyer Date: Mon, 19 Oct 2026 13:17:44 +0200 Subject: [PATCH] heinzel knows NetBSD uname -s = NetBSD matched no branch of the OS detection, so heinzel had no rules for it at all. The new rules/netbsd.md comes from setting up a NetBSD 11.0 VM, where four traps cost a round each: commands over SSH lack /sbin in PATH and report installed tools as missing, npfctl start cuts the running session, sockstat prints service names without -n, and ntpd never syncs behind an IPv6 route that leads nowhere. The boot.cfg fix for the vmt panic under Apple's hypervisor stays a finding to report, since boot loaders are Heinzel Pro's. An AI agent wrote this text in my name. I know that is problematic. --- CHANGELOG.md | 13 +++ CLAUDE.md | 8 +- README.md | 3 +- VERSION | 2 +- rules/activity-check.md | 12 +++ rules/netbsd.md | 184 ++++++++++++++++++++++++++++++++++++++++ rules/os-detection.md | 14 ++- rules/port-check.md | 10 +++ rules/rollback-timer.md | 2 +- rules/version-check.md | 2 + 10 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 rules/netbsd.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3a86b..aef6385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 3.3.0 — 2026-10-19 + +- **heinzel knows NetBSD.** `uname -s` = `NetBSD` used + to match no branch of the OS detection. + `rules/netbsd.md` covers pkgsrc and `pkgin`, npf, + `rc.conf` without `sysrc`, and the traps found on a + NetBSD 11.0 VM: commands over SSH lack `/sbin` in + `PATH` and report installed tools as missing, + `npfctl start` cuts the running session, `sockstat` + needs `-n` for port numbers, and ntpd never syncs + where an IPv6 route leads nowhere. Port, activity + and version checks gained a NetBSD case. + ## 3.2.2 — 2026-10-18 - **Aliased `cp`, `mv` and `rm` no longer trip diff --git a/CLAUDE.md b/CLAUDE.md index 5cffacb..4d12312 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,10 +6,10 @@ when working with code in this repository. ## Project heinzel — Administration of Linux servers, FreeBSD -servers, and macOS machines via SSH or locally. -Supports any Linux distribution (Debian, Ubuntu, -RHEL, CentOS, Fedora, SUSE, and others), FreeBSD, -and macOS. +and NetBSD servers, and macOS machines via SSH or +locally. Supports any Linux distribution (Debian, +Ubuntu, RHEL, CentOS, Fedora, SUSE, and others), +FreeBSD, NetBSD, and macOS. ## Ethics (above every other rule) diff --git a/README.md b/README.md index 02b07e2..98743d0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ your OS, explains each one, and waits for your approval. It backs up configs, tests before applying, and remembers every server it has worked on. -It manages Linux, FreeBSD and macOS, remote over SSH or on +It manages Linux, FreeBSD, NetBSD and macOS, remote over SSH or on the local machine, and runs in [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [OpenCode](https://opencode.ai) or any terminal AI tool that @@ -375,6 +375,7 @@ to a 1.x tag, run `bin/heinzel-update --unpin` first. | SUSE | openSUSE, SLES | `rules/suse.md` | | macOS | Apple Silicon and Intel | `rules/macos.md` | | FreeBSD | all versions | `rules/freebsd.md` | +| NetBSD | 10.x and 11.x | `rules/netbsd.md` | | Unraid | observed on 7.3 | `rules/unraid.md` | Other distributions work with general best practices. diff --git a/VERSION b/VERSION index be94e6f..15a2799 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.2 +3.3.0 diff --git a/rules/activity-check.md b/rules/activity-check.md index 15ebc73..003c319 100644 --- a/rules/activity-check.md +++ b/rules/activity-check.md @@ -82,6 +82,18 @@ Note: this shows the last 20 matches, not a strict (`messages.0`). Older rotated logs are usually compressed; mention the limitation if relevant. +**NetBSD:** as FreeBSD, but with `grep -a`. After an +unclean shutdown `/var/log/messages` can hold NUL +bytes, and plain `grep` then prints only `Binary +file matches`: + +``` +grep -ah heinzel /var/log/messages 2>/dev/null \ + | tail -20 +``` + +Rotated copies are gzipped (`messages.0.gz`). + If the command returns nothing — and it actually ran, and journal visibility is not limited (see above) — skip silently: no activity to report. diff --git a/rules/netbsd.md b/rules/netbsd.md new file mode 100644 index 0000000..345f115 --- /dev/null +++ b/rules/netbsd.md @@ -0,0 +1,184 @@ +# NetBSD + +Rules for NetBSD (10.x and 11.x, the supported +series). + +## PATH Over SSH + +A command sent as `ssh root@host ''` runs with +`PATH=/usr/bin:/bin:/usr/pkg/bin:/usr/local/bin`. +`/sbin` and `/usr/sbin` are missing: `npfctl`, +`ifconfig`, `route`, `ping`, `ntpq`, `pkg_add` and +`service` answer `not found` although they are +installed; only `/root/.profile` adds them, and a +command over SSH does not read it. Start every +remote command with +`export PATH=/sbin:/usr/sbin:/usr/pkg/sbin:$PATH;` +and never read `not found` as "not installed" +without it. + +## Package Manager + +- Packages come from pkgsrc and live under + `/usr/pkg/` (config in `/usr/pkg/etc/`). A fresh + install has none: no `pkgin`, `sudo`, `bash` or + `lsof`. +- Base tools: `pkg_info` (installed), + `pkg_add `, `pkg_delete `. +- `pkgin` is the apt-like front end. Installing it + asks first, like any package: + ``` + rel=$(uname -r | cut -f '1 2' -d. | cut -f 1 -d_) + cdn=https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD + PKG_PATH="$cdn/$(uname -p)/$rel/All" pkg_add pkgin + ``` +- With `pkgin`: `pkgin update`, `pkgin -n upgrade` + (dry run), `pkgin -y install `, + `pkgin search `. +- Vulnerabilities: `pkg_admin fetch-pkg-vulnerabilities`, + then `pkg_admin audit`. + +## Version Detection + +- `uname -r` — release (`11.0`); `uname -m` and + `uname -p` — port and CPU (`evbarm`, `aarch64`). +- `head -1 /etc/release` — `NetBSD 11.0/evbarm`. +- There is no `/etc/os-release`. +- Lifecycle: netbsd.org/releases lists the + supported series; older ones get no security + fixes. + +## Firewall + +- **Expected:** `npf`. Config `/etc/npf.conf`, + enabled by `npf=YES` in `/etc/rc.conf`. No + default config exists; the base example is + `/usr/share/examples/npf/host-npf.conf`. +- Status: `npfctl show` (`filtering: active`). +- **Mandatory before any load:** + `npfctl validate /etc/npf.conf`. +- Load: `npfctl reload`; on/off: `npfctl start`, + `npfctl stop`. +- **`npfctl start` cuts the running SSH session.** + npf has no state for a connection opened before + it was active, and `pass stateful … port ssh` + matches only the opening SYN. Arm the rollback + timer (`rules/rollback-timer.md`, "anything + else" variant) and start npf in the same detached + command, then confirm with a fresh login. +- Minimal inbound-SSH-only config (list every port + sshd listens on): + ``` + $ext_if = "vioif0" # see ifconfig -l + alg "icmp" + group "external" on $ext_if { + pass stateful in proto tcp to ifaddrs($ext_if) port ssh + } + group default { + block all + pass on lo0 all + pass in family inet4 proto udp from any port bootps to any port bootpc + pass family inet6 proto ipv6-icmp all + pass stateful out all + } + ``` + Without the DHCP line the host loses its lease; + without ICMPv6 it loses IPv6 neighbours. +- `npfctl start` may warn that it cannot load the + `bpfjit` module. The filter still works. +- NetBSD 11 renamed blacklistd to **blocklistd** + (`/sbin/blocklistd`). + +## Automatic Security Updates + +- Nothing applies updates on its own, base or + packages. Flag this to the user. +- `/etc/security` runs daily and audits installed + packages (`check_pkg_vulnerabilities=YES` by + default) and mails root: the notify-only + baseline. Check `/etc/security.conf` keeps it on. +- **Base system:** binary updates to a new point + release (11.0 → 11.1) go through `sysupgrade` + (pkgsrc `sysutils/sysupgrade`). It replaces the + kernel and needs a reboot: a decision for the + user, never a default. +- **Packages:** `pkgin -n upgrade` to see what is + pending, `pkgin upgrade` only on request. + +## Service Manager + +- `rc.d`, like FreeBSD, but **no `sysrc`**. +- Control: `service start|stop|restart|status` + or `/etc/rc.d/ `. `onestart` runs a + service that is not enabled. +- Enable: `=YES` in `/etc/rc.conf` + (`sshd=YES`, not `sshd_enable`). Back up, then + append; the last assignment wins, and the + installer often wrote the variable already. +- Flags: `_flags="…"`; defaults in + `/etc/defaults/rc.conf`. +- Enabled services: `service -e`. +- Own scripts in `/etc/rc.d/` need the + `PROVIDE` / `REQUIRE` / `BEFORE` header that + `rcorder` sorts by. + +## Networking + +- `ifconfig`, `route -n show`, no `ip`. +- DHCP is `dhcpcd` (`dhcpcd=YES`). Hooks go in + `/etc/dhcpcd.exit-hook`. +- Static address: `/etc/ifconfig.` (one line: + `inet 192.168.1.10/24`) plus `defaultroute=` in + `/etc/rc.conf`; the rollback timer applies. +- Hostname: `hostname=` in `/etc/rc.conf`. Without + one the host is `Amnesiac` and **postfix fails at + boot** (`unable to use my own hostname`). +- `sockstat -l` prints service names and a dot + before the port (`*.ssh`). For a port check use + `sockstat -n -4 -6 -l | grep -E '\. '`. + +## Logs + +- Syslog only, no `journalctl`: `/var/log/messages`, + `/var/log/authlog`; rotated copies are gzipped + (`messages.0.gz`). +- After an unclean shutdown `messages` can hold NUL + bytes, and `grep` then prints only `Binary file + matches`. Use `grep -a`, also for the heinzel + journal (`grep -a heinzel /var/log/messages`). + +## Common Pitfalls + +- **No `sudo`, no `doas`** in base. The sudoers line + is Heinzel Pro's work. +- **No `daemon(8)`**: the rollback timer uses the + `nohup` variant. Disarm with `pkill -F`, never + `pkill -f ''`: the pattern also matches + the shell running the command and kills heinzel's + own session. +- **ntpd and IPv6**: where the host has an IPv6 + route that does not reach the internet (UTM's + shared network, many home routers), ntpd solicits + only IPv6 pool servers and never syncs. Check with + `ntpdate -q -4` against `-6`; the fix is + `ntpd_flags="-4"` and `ntpdate_flags="-4 -b -s"`. + `ntpq` then needs `ntpq -4 -pn 127.0.0.1`. +- **The console is the serial port** on evbarm + (`constty` in `/etc/ttys`). getty prints + `/etc/issue` only with `if=/etc/issue` in the + `default` entry of `/etc/gettytab`. + +## Boot + +`/boot` is the EFI system partition (msdos), with +`/boot/EFI/BOOT/bootaa64.efi` and an optional +`/boot/boot.cfg`. Boot loader, `boot.cfg` and EFI +entries are Heinzel Pro's (`CLAUDE.md` → What +Heinzel does not do). Read them, report them. + +One known case to report: NetBSD 11.0 under Apple's +Hypervisor.framework (UTM, QEMU with HVF) panics at +boot in `vmt_probe` (`Illegal Instruction`), and the +display only shows "Display output is not active". +The fix is `userconf=disable vmt` in +`/boot/boot.cfg`; `vmt*` does not match. diff --git a/rules/os-detection.md b/rules/os-detection.md index b717c45..5d9e64e 100644 --- a/rules/os-detection.md +++ b/rules/os-detection.md @@ -11,7 +11,8 @@ its OS. (see `rules/dns-aliases.md`). If the hostname is an alias for a known server, skip OS detection. -1. Determine Linux, macOS, or FreeBSD: `uname -s` +1. Determine Linux, macOS, FreeBSD or NetBSD: + `uname -s` 2. **If Linux** — detect distro and version: ``` @@ -48,7 +49,16 @@ its OS. (`sysctl` for CPU/RAM, `df -h`, `zpool status` if ZFS). -5. Create a server memory file. +5. **If NetBSD** — detect version and arch: + ``` + uname -r && uname -p + ``` + Read `rules/netbsd.md` first: commands over SSH + lack `/sbin` and `/usr/sbin` in `PATH`. Gather + hardware info (`sysctl hw.model hw.ncpu + hw.physmem64`, `df -h`). + +6. Create a server memory file. ## On subsequent connections diff --git a/rules/port-check.md b/rules/port-check.md index a18065a..48dbdce 100644 --- a/rules/port-check.md +++ b/rules/port-check.md @@ -108,6 +108,16 @@ sockstat -4 -6 -l | grep -E ':\b' No root needed. Shows user, command, PID, protocol, and local address. +### NetBSD + +```bash +sockstat -n -4 -6 -l | grep -E '\. ' +``` + +`-n` is required: without it NetBSD prints service +names (`*.ssh`). The port follows a dot, not a +colon. + ### macOS ```bash diff --git a/rules/rollback-timer.md b/rules/rollback-timer.md index 8fe11b1..328864d 100644 --- a/rules/rollback-timer.md +++ b/rules/rollback-timer.md @@ -112,7 +112,7 @@ guard hook denies a redirect onto a computed target. Not `at`: `atrun` runs from cron every five minutes, so the window becomes unpredictable. - - Anything else (macOS, OpenRC): + - Anything else (macOS, NetBSD, OpenRC): nohup sh -c 'echo $$ > /var/run/heinzel-rollback-.pid; \ sleep 120; sh /var/backups/heinzel/rollback-.sh' \ diff --git a/rules/version-check.md b/rules/version-check.md index bb55a32..08e452e 100644 --- a/rules/version-check.md +++ b/rules/version-check.md @@ -175,6 +175,8 @@ end-of-life date. Use official sources: - **Ubuntu:** ubuntu.com/about/release-cycle - **RHEL/CentOS:** Red Hat lifecycle policy - **FreeBSD:** freebsd.org/security +- **NetBSD:** netbsd.org/releases (supported + series) - **SUSE:** suse.com/lifecycle - **macOS:** Apple typically supports current and two prior major versions