Keep the six node configuration intact while removing the failing user activation from the three control-plane MicroVMs. Update the regression test and current K3s configuration notes.
0.156.1 still lacks the codex-chatgpt recursion_limit attribute, so keep the postPatch that adds it. The vendored Cargo.lock is unchanged, so the cargoHash stays the same.
cilium writes /etc/cni/net.d while istio-cni reads k3s's own dir, so on the new workers the chained plugin never found a network config. Add the symlinks the server module already has.
kubelet refuses to self-assign the reserved node-role.kubernetes.io/* labels, so the worker --node-label made the agent crash-loop. Drop it and rely on the masters' control-plane taint for placement; document that the taint only lands via registerWithTaints on first registration, so it was applied by hand here.
Give the idols-ruby and idols-kana VMs a headless computer-use
environment: Xvfb plus i3, driven by cua-driver (and computer-use-linux
as a fallback). Includes the system module (AT-SPI, fonts, Mesa, Clash
Verge proxy, proxy-region timezone), the home i3 module (Xvfb/i3/x11vnc
services, browser flags), host wiring and an eval test.
Package the prebuilt releases (computer-use-linux v0.7.1 and
cua-driver-rs v0.24.0) under pkgs/, injected through thin callPackage
overlays so they are easy to relocate to the NUR repository later.
The desktops hold the trusted admin keys and are where secrets are edited and
rekeyed from; narrowing a set to the servers alone breaks agenix -r and drops
the offline recovery key.
A recipient set must answer two questions: who may read it, and how it is
recovered once those hosts are gone. Dropping recovery_key while narrowing
a secret is easy and silent.
A failed backup was silent. Post to alertmanager (which routes above-info
to telegram), and have caddy answer 404 instead of its empty 200 for
*.writefor.fun names that are not services.
A desktop holds both the plaintext and the repository password, so blocking
deletion on the server protects little while preventing the automatic
retention the client needs. Immutability belongs in the offsite copy.
The design (layers, password groups, retention, restore steps) was only in
code comments. Also make the timer configurable: a whole hour of random
delay defeated any staggering, and the desktop's push, youko's own run and
btrbk all otherwise contend for the same disks.
Split the repository password into desktop and homelab groups, add the
environmentFile/pruneOpts module options, and point the desktop at youko's
REST endpoint.
Allowlist of the irreplaceable directories (Documents, work, Pictures,
nix-config, .local/state); project checkouts under ~/codes live in git and
are not backed up. Kept the excludes generic, since this repo is public.
Also move the restic-password secret to a desktop.enable || server.storage
gate so desktops get it without enabling a server group.
The services now run directly on the host, so their state does not belong
on the HDD behind the flaky USB-SATA bridge: /data/apps was small-file
heavy and latency/consistency sensitive. Point every dataDir at
/persistent/apps (the LUKS nvme) instead.
The HDD keeps the bulk data that suits it: /data/fileshare (173G) and
/data/backups (24G). The HDD's old @apps subvolume and its data are left
in place for now.
-B 255 turns APM off and it stuck, but the disks still reach standby, so
the bridge firmware is likely doing the spindown. -J 0 additionally asks
the drive to stop parking its heads (sdb has ~1M load cycles).
pcie_aspm=off was the least-justified mitigation (the flakiness is on the
USB/SATA side) and costs power. Also log the disks' APM level/state and
re-apply the settings hourly, since the bridge or the disk firmware can
drop them (the disks were still reaching standby).
The two 4TB HDDs sit behind a JMicron JMS567 that resets and throws
UDMA-CRC / I/O errors (the disks themselves are healthy). Best-effort
mitigations while the enclosure stays in use:
- kernel: usbcore.autosuspend=-1, usb-storage.delay_use=10, pcie_aspm=off
- udev: keep the bridge's power/control=on (never autosuspend)
- disable APM/standby on the HDDs (spindown is a common reset trigger)
- enable smartd (watch the CRC count and disk health)
The kernel parameters need a reboot to take effect.
It documented the retired KubeVirt workflow (upload a qcow2, create the vm
in k8s-gitops, let Flux create it), which no longer exists. The general
deployment notes live in the README and the per-platform docs
(hosts/k8s/README.md for the microVMs).
Update the user-facing bits that still pointed at KubeVirt: the homepage
dashboard group and entries (and drop the dead LongHorn one), the docs,
and remove the now-unneeded virtctl package.
The k3s-test workers never ran (they were stuck waiting for volume
binding) and will be re-added as microVMs, so remove their KubeVirt
configs and everything that only existed for KubeVirt guests:
- hosts/k8s/k3s-test-1-worker-{1,2,3} and their outputs
- lib/genKubeVirtGuestModule.nix and lib/genK3sAgentModule.nix (the
latter was only used by the workers)
- the KubeVirt image upload recipes (upload-vm, upload-idols,
upload-k3s-test) and the kubevirt image package in the idols outputs
- refresh the docs; keep only accurate historical mentions (e.g. the
restic backup path must stay as it is)
Drop the now-meaningless kubevirt- prefix and move the hosts out of
hosts/k8s/, following the existing 12kingdoms-shoukei layout: the
directory and output carry the theme prefix while the configuration,
hostname and colmena target are the bare name (shoryu, shushou, youko).
- hosts/k8s/kubevirt-* -> hosts/12kingdoms-*; the shared disko config
becomes host-disko-fs.nix; the now-different relative imports are fixed
- outputs, vars/networking.nix keys, the Justfile recipes/tags, the eval
tests and the docs follow
- colmena reaches these hosts by IP (a new targetHost override), since
their DNS name changes with the hostname and is not resolvable until
the router learns it
This is not a Python package, so drop the packaging/build/pyinstaller/
framework/type-checker/pyenv/poetry/pdm/pixi/celery/rabbitmq rules and the
unused Marimo and Streamlit sections. Keep the bits occasional Python
scripts do need: byte-compiled files, virtualenvs, local secrets and the
common tool caches.
The Python section of the .gitignore template has a bare 'lib/' (and
'lib64/'), which also matched the Nix helper modules in lib/. The
'!lib/*.nix' line added earlier could not help, since a file cannot be
re-included while its parent directory is excluded.
The physical hosts no longer need k3s or KubeVirt: aquamarine runs
natively, the k3s-test masters run as microVMs and kana/ruby under
libvirt. So drop the host cluster and split the shared parts out of the
KubeVirt host module:
- lib/genVmHostModule.nix: the host bits the VMs still need (br0 + the
physical NIC as a bridge port, KVM, sysctls, earlyoom, libvirt,
journald, stateVersion)
- the three hosts use it and drop genK3sServerModule; also drop the
longhorn workarounds (openiscsi + the /usr/local/bin tmpfile) and the
virtctl package
- remove lib/genKubeVirtHostModule.nix
The file is not KubeVirt-specific: it is the hardware configuration for
any QEMU guest that boots from a disk (KubeVirt or libvirt). All it does
is pull in qemu-guest.nix and set fileSystems."/" from the disk labeled
nixos.
hosts/k8s/README.md: how the microVMs keep state, the tap/br0 naming, how
to move a KubeVirt VM over in place, and the refreshed TODOs. Also note
the migration in Virtual-Machine.md and flag the volume images in
AGENTS.md.
Replace the KubeVirt/etcd members with microVMs on the three physical
hosts, keeping the existing cluster: the guests inherit their host key
and k3s state from the old disks, so etcd recovers in place instead of
bootstrapping a new cluster.
- convert hosts/k8s/k3s-test-1-master-{1,2,3} to genMicrovmGuestModule
and switch their outputs to the microvm module (keep home-modules)
- declare the vms on shoryu/youko/shushou and bridge their taps to br0
- each guest config is self-contained (its secret group and the
nixpkgs.config override live in the guest, not the host module)
- guest specialArgs carry agenix/mysecrets so secrets/nixos.nix works
lib/genMicrovmGuestModule.nix: qemu guest with a shared read-only store
(virtiofs), persistent /etc, /var and /home volumes, a tap on br0 (its
name derived from the guest IP, as IFNAMSIZ caps it at 15 chars), and a
deterministic MAC. Also keep lib/*.nix tracked.
The service modules moved two levels deeper, so their certs/ paths
resolved relative to the wrong directory. Use mylib.relativeToRoot so
they no longer depend on the module's depth.
Drop the retired host's name from the filenames:
aquamarine-services.nix -> homelab-services.nix and the aquamarine/
module dir -> homelab-services/. The "aqua" name now stays only in the
docs/vars, where it is kept for a possible later test microvm/qemu-vm on
idols-ai.
Aquamarine's services now run directly on kubevirt-youko, so the guest
definition is no longer needed:
- move its service modules to hosts/k8s/kubevirt-youko/aquamarine/ and
drop the explicit import list, leaving aquamarine-services.nix with
just its IP and secret groups
- delete the guest base, journald override, output entry and home config
- repoint the eval tests that read the guest's config to kubevirt-youko
- drop the now-dead 'aqua' colmena recipe and its image upload
The aqua KubeVirt VM itself is deleted separately; the name is kept in
the docs for a possible later test microvm/qemu-vm on idols-ai.
shoukei is a laptop using DHCP and never consumes hostsAddr.shoukei.
Its 192.168.5.108 collided with k3s-prod-1-master-1 and produced a wrong
ssh Host shoukei entry.
The Settings UI stored `theme.mode` in ~/.local/state/noctalia/settings.toml, so it was not versioned. Pin it in the config baseline; the [location] sunrise/sunset schedule still drives light/dark.
At background_opacity 0.2 the light-mode surface disappears into dark wallpapers, so the dark light-mode text sits on a dark bar. Raise it to 0.61 to keep contrast while staying slightly translucent.
`programs.noctalia.settings` deploys the TOML as a store symlink, so every edit needs a `home-manager switch`. Deploy the baseline from the config layer with `mkOutOfStoreSymlink` instead: Noctalia hot-reloads config-layer files and never rewrites them, so edits apply live while Settings UI changes stay in ~/.local/state/noctalia/settings.toml (which loads last) and out of the repo.
Update the docs and host-specific comments to match the two-layer model.
The sandbox remaps XDG_DATA_HOME/XDG_CONFIG_HOME, so paths Firefox exposes to
or shares with the host ended up isolated. Bind the MPRIS artwork dir and the
real trash rw, and mimeapps.list, user-dirs.dirs and the shared NSS cert store
ro.
recently-used.xbel stays isolated: GTK writes it atomically, which a
single-file rw bind would break.
Add cpu, ram, disk and network up/down sysmon widgets to the bar start lane,
matching the old v4 SystemMonitor. Set the clock to ISO date, full English
weekday and 24h time (e.g. "2026-09-19 Saturday 16:06").
English stays the default (LANG=en_US.UTF-8). Drop the Chinese overrides for
LC_TIME, LC_NUMERIC, LC_ADDRESS, LC_NAME, LC_TELEPHONE and LC_IDENTIFICATION:
LC_NUMERIC matches en_US, the others are effectively unused, and LC_TIME forced
Chinese date and weekday names.
Keep zh_CN only where the en_US default is wrong for China: metric units, A4
paper and CNY.
Refresh the repository overview after the move to Noctalia v5:
- fix the component table: Noctalia as the desktop shell, native screenshots, systemd-networkd/NetworkManager, Zed & VS Code as GUI editors, Zellij as a multiplexer
- correct the deploy instructions (niri hosts use 'just niri'; 'just local' matches the bare hostname) and the Nvidia link
- update the NixOS badge to 26.11 and add a short validation section
- root README: VS Code config moved from home/linux/gui/base/editors.nix to vscode.nix
- READMEs now list both GUI editors (Zed, the primary one, plus VS Code)
- base README: xdg is a directory now, nvidia.nix no longer exists, editors.nix -> vscode.nix
- idols-ai README: disko-fs-media.nix -> disko-fs-data.nix (/persistent/data)
- secrets README: password-store moved from home/base/desktop to home/base/tui
- drop unused link definitions (AstroNvim, wl-clipboard, Catppuccin)
- fix a pre-existing typo flagged by the typos hook (keeped -> kept)
Noctalia v5 is the native C++/Wayland rewrite; the Quickshell-based v4 line (pkgs.noctalia-shell) is EOL and its JSON config is not migrated upstream.
- use the upstream home-manager programs.noctalia module (present at our pinned HM revision) instead of the hand-rolled package + symlink module: it installs pkgs.noctalia, validates config.toml at build time via checkConfig, and supports customPalettes; drop the v4 Qt/Qt6ct session setup
- replace the v4 settings.json/plugins.json with a minimal shared config.toml (font, Catppuccin shell palette, templates off, screenshot annotation, accessibility/bar scale, night light, wallpaper, location/weather); host-specific bits live in per-host files (shoukei's eDP-1 backlight in noctalia/host-shoukei.toml), which merge after config.toml. Application theming stays with catppuccin/nix
- treat config.toml as the single source of truth: do NOT persist ~/.local/state/noctalia and set setup_wizard_enabled = false, so UI tweaks are promoted back into the repo instead of accumulating in app-owned state
- port niri keybinds to 'noctalia msg ...', update spawn-at-startup and the backdrop layer namespace, fix the broken 'octalia-shell' media-previous bind, and replace the grim/slurp/satty screenshot stack with Noctalia's native region/fullscreen/annotate commands (drop the unused packages)
- drop the now-redundant swaybg and swaylock (Noctalia owns the wallpaper and lock screen, the latter authenticating against the PAM 'login' service) and its PAM entry; keep wlogout as an emergency fallback
- update hypridle's lock commands and the matching eval test
- refresh the noctalia docs
networkd enables systemd-resolved by default, and resolved's own mDNS responder conflicts with avahi (duplicate answers, avahi logs 'Detected another mDNS stack running on this host'). Keep avahi for DNS-SD (cups-browsed depends on it) and turn resolved's mDNS off.
home.sessionVariables writes export NAME="value" without escaping embedded double quotes, so the JSON {"share":"disabled"} reached opencode as the shell word {share:disabled} and failed config parsing. Emit the export with sessionVariablesExtra instead.
The 2026-09 nixpkgs update changed DRM probe order, renaming the Intel connectors DP-1 -> DP-4 and HDMI-A-2 -> HDMI-A-3. The connector-name based output and open-on-output rules stopped matching, which dropped monitor scale/mode/position and sent every named workspace to the first output. Match by make/model/serial instead, which is stable across connector renumbering.
Tencent replaced the file behind the versionless CDN URLs, so the pinned hashes stopped matching. Refresh the x86_64 and aarch64 hashes with nix store prefetch-file.
trash-empty lacked Persistent=true, so a host that was off or suspended at the scheduled time never ran the purge and trash retention silently stopped.
Guix was enabled on every desktop host, but shoukei had no /gnu mount (store and state lived on the tmpfs root) and it was not used. Drop the module, the modules.guix option, the @guix subvolume and the /var/guix preservation entry.
The restic job needs three agenix secrets that do not exist yet (restic-password, ssh-key-for-restic-backup and an rclone config for the smb-downloads remote). Comment it out until they are provisioned so it does not fail silently every night.
Load both *.yml and *.yaml alert rule files: the Loki rules were named .yaml while only *.yml was loaded, so they silently never ran. Fix the Grafana domain typo (writefo.fun -> writefor.fun). Comment out the postgres-playground datasource, which used the Grafana admin password for a role that has no password and could never authenticate.
checks.<system>.eval-tests returned a boolean, so nix flake check failed with "not a derivation". Wrap the evalTests result in a runCommand so flake check actually evaluates it.
RTK compresses only shell output and independent evaluations show little to no end-to-end cost reduction. Drop the package and its install snippets; runtime artifacts were uninstalled separately.