podman-hermes-agent.service RequiresMountsFor /mnt/jupiter, but the mount
option copied from terra's (read-only, nothing depends on it) browsing
mount included x-systemd.idle-timeout=60 — confirmed on real hardware,
this killed the container ~60-70s after every start with no crash or
error, just an idle auto-unmount taking the dependent service down with
it. Keep the lazy x-systemd.automount (so boot doesn't stall if jupiter's
down) but drop the timeout now that something needs the mount to persist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FHr5ug9pu8q4XPrRkFnzJ
New on-site host mars runs Hermes Agent as its sole service: joins the
tailnet, mounts jupiter's samba share at /mnt/jupiter (doubling as
Hermes's shared dropbox), and hosts state locally under /var/lib/hermes.
Same Authentik OIDC app/Telegram bot as before, just relocated — neptun's
hermes.mgaction.town vhost now points at mars.orbit.sol instead of jupiter.
hosts/jupiter/hermes-agent.nix and its three sops secrets are removed;
jupiter's Caddy vhost for it is gone too. Also refreshes tailscale_authkey
across all hosts and fixes two stale "erik@laptop" keys in flake.nix's
kexec/installer-iso images (leftover from a previous laptop, already
swapped out of common.nix back in 2fd5752) to darman@terra.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FHr5ug9pu8q4XPrRkFnzJ
The container has no host /etc/localtime bind-mount, so hermes_time.py's
timezone resolution fell through to UTC. HERMES_TIMEZONE is its
highest-priority source (checked before config.yaml's timezone key).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up to e5ba1bf — that commit only staged the deleted module file.
Drops the flake module import, opencode_go_api_key/telegram_bot_token
secrets, and the stale hermes-agent.nix cross-reference in ollama's
context_length comment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Consolidating on jupiter's isolated instance (hosts/jupiter/hermes-agent.nix)
instead of running a second one here. Drops the module import, its
opencode_go_api_key/telegram_bot_token secrets, and the now-stale
cross-reference in ollama's context_length comment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A separate instance from terra's, deliberately locked down harder given
jupiter's much bigger blast radius (irreplaceable immich photos on an
unredundant RAID0, gitea/CI tokens, the whole media stack): its own
dedicated "hermes" system user rather than darman (who is in jupiter's
root-equivalent docker group), container.enable = true for whole-process
containment rather than native/bare-metal, its own Telegram bot + explicit
allowlist, and no volume access to /mnt/data or this repo. stateDir/
workingDirectory live on the array (off the 29G eMMC) for future coding-task
state, guarded by RequiresMountsFor like the rest of jupiter's array-backed
services.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
nix.gc (common.nix) is weekly, too slow to catch a switch pinning the old
generation's closure on a 29G eMMC — add a full nix-collect-garbage on every
boot instead. Also set boot.swraid.mdadmConf so eval stops warning that
mdmon will crash (dormant here: the RAID0 array uses native superblocks, so
mdmon never actually runs).
sabnzbd.configFile is deprecated by the module; move to services.sabnzbd.settings
with credentials (web login, api/nzb keys, eweka.nl server) sourced from sops via
secretValues instead of living in a plaintext ini. admin_dir/log_dir are pinned
absolute at their original /mnt/data location so the existing download
queue/history isn't reset by the ini moving to /var/lib/sabnzbd.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Points Nous Research's Hermes Agent at terra's own ROCm ollama server
(gemma4:12b) as a custom OpenAI-compatible provider instead of a cloud
key. Native systemd mode via the hermes-agent flake's own NixOS module
— simpler than container mode, avoids the podman-rootful-sudo dance
its docs call out.
Also bumps OLLAMA_CONTEXT_LENGTH (and Hermes' mirrored model.context_length)
from ollama's ~4k default to 131072, load-tested with real multi-ten-
thousand-token prompts rather than just idle `ollama ps` checks — chosen
as the practical ceiling where VRAM headroom and prefill throughput both
start visibly degrading, not just the largest number that technically fit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Enables hardware.graphics + intel-media-driver for the Apollo Lake's
Gen9 iGPU (VAAPI only — QSV needs an insecure/EOL runtime on this
chip) and adds jellyfin's service user to video/render for the DRI
card node. 4K HDR still can't be tone-mapped on this hardware; those
files need to direct-play or be kept as 1080p SDR.
Also relocates podman's container storage and immich's postgres
cluster to /mnt/data/AppData, after a deploy holding two ~9G closures
at once filled the 29G eMMC and postgres died mid-write.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Running sops --decrypt/edit_secrets --show and displaying the result
puts every plaintext secret in the file wherever that output lands,
not just the one value being checked. Point at `sops --set` instead
for adding/changing a single value non-interactively.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Local LLM server on the 6800 XT (ollama-rocm, gfx1030 needs no
HSA_OVERRIDE_GFX_VERSION) fronted by a LibreChat web UI, talking to it
over the OpenAI-compatible /v1 route. Also wires up LibreChat's
persistent-memory feature, which needed its own agent+model plus a
custom extraction prompt: the default 3b model couldn't reliably tell
the user's stated facts apart from its own boilerplate, and even a
tuned prompt didn't fix that — so memory extraction now reuses
gemma4:12b, the same model as the daily-driver chat endpoint.
flake.lock bump pulls in the ollama and librechat NixOS modules.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
After the rack move one of the RAID0 disks failed to enumerate, and jupiter
boot-looped into an emergency shell nobody could use — root is locked, so
sulogin offers a prompt with no answer, and there is no ssh from there:
Timed out waiting for device /dev/disk/by-uuid/dadbff6f-...
Dependency failed for /mnt/data.
Dependency failed for /var/lib/private/prowlarr.
Dependency failed for Local File Systems.
local-fs.target: Job local-fs.target/start failed with result 'dependency'
Reached target Emergency Mode.
`nofail` on /mnt/data did not help, because the prowlarr and seerr bind
mounts layered on top of it had none: without it a mount is RequiredBy
local-fs.target, so those two failed the target on the array's behalf. Give
them `nofail` too and let them fail alone. `systemd.enableEmergencyMode =
false` then keeps a bad array from costing a reachable box at all — far more
useful on a headless host than a console prompt.
Booting further is only safe if nothing quietly relocates onto the 29G eMMC,
so pin the array-backed services to the mount. systemd derives
RequiresMountsFor from a unit's own paths, which for these is somewhere under
/var/lib (eMMC) — nothing pointed immich at mediaLocation or sabnzbd at its
configFile, so with the array gone they would have started and written to the
OS disk, into directories that go invisible the moment /mnt/data mounts over
them. jellyfin, sonarr, radarr and gitea already had a real dependency and
are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Workflows push as a dedicated ci-bot account rather than a human one, so its
PAT can be scoped, rotated and revoked on its own. Adding a repo to
`ciBotRepos` and redeploying is all it takes to grant access.
Collaborator access and branch-protection push-whitelisting exist only on
gitea's HTTP API — no CLI, no config-file surface — so this one part stays
imperative: a oneshot that PUT/PATCHes the API into the desired state. It
runs on deploys where the script changed, which means it won't self-heal a
revert done through the web UI unless the unit is restarted too.
Two secrets, deliberately distinct:
- gitea_provisioning_token is darman's own token (write:repository +
write:user). Only an owner-scoped token clears reqOwnerCheck on the
collaborator and branch-protection endpoints, and write:user is what lets
it write the Actions secret below. ci-bot cannot grant itself access.
- gitea_ci_bot_token is ci-bot's push token, generated once by hand (the
command is in the comment) and pushed into gitea as a user-level Actions
secret CI_BOT_TOKEN. Gitea has no instance-wide secret scope, and every
repo here is owned by darman directly rather than an org, so a user-level
secret is the closest thing — repo-level lookups fall back to it.
Branch protection is applied to the default branch plus `develop`, since
version-bump.yml pushes there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Now that the plugin draws its own outline and glow, hyprland's own border is
redundant — border_size 0 and let hyprchrome own the window edge (outline_size
2 in fg_color). Fill in the rest of its knobs: glow 12/0.85, shadow 24 offset
{4,8} in bg_color.
Accents go warm: bg_accent to a muted red (963c38) and a new fg_accent_alt
(ff9d42) so the active-border gradient runs amber->orange instead of
amber->background. Colour literals lowercased for consistency.
Also re-pick the placeholder wallpaper, and drop the comment explaining
hyprland's lua gradient table format — the surviving call site is now the
only one and reads plainly enough.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- install tome from pkgs/tome.nix, built against the re-added flake input
- import services/containers.nix and put darman in `render`/`video`:
/dev/dri/renderD128 is root:render 0660, so a rootless container can only
reach the GPU if the host user is in the group. Needed by the Vulkan
whisper.cpp/llama.cpp containers in content-trigger-scanner.
- point DOCKER_HOST at the podman *user* socket and add docker-compose.
dockerCompat gives a `docker` CLI shim, but compose v2 is its own binary
talking to a socket, and rootless podman's socket is the user one under
/run/user/1000 — not root's /var/run/docker.sock.
- direnv + nix-direnv, so per-repo devShells load in the shell and in Rider
via its direnv plugin, instead of hand-wiring a toolbox SDK per repo
- kicad as a flatpak, alongside the other flatpak desktop apps
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three separate reasons the packaged build didn't behave like the one Rider
launches:
- dotnet-runtime must be aspnetcore_10_0, not runtime_10_0. Tome.App's
runtimeconfig.json requires Microsoft.AspNetCore.App as well as
Microsoft.NETCore.App, because Photino hosts a local Kestrel server, and
only the aspnetcore bundle ships it.
- wrapGAppsHook3, so gappsWrapperArgs get spliced into buildDotnetModule's
own wrap step (it sets dontWrapGApps itself; same pattern as nixpkgs'
libation). Without it nothing sets XDG_DATA_DIRS/GSETTINGS_SCHEMA_DIR, so
GTK/WebKitGTK found neither the icon theme nor GTK settings — missing icons
and a denser default font than in an already-initialized session.
- glib in runtimeDeps. It doesn't arrive via gtk3/webkitgtk's RPATH because
the consumer is Photino.Native.so, a prebuilt binary out of the nuget
package rather than something Nix built and patched.
tome-deps.json is the regenerated nuget lock for the aspnetcore switch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c5a231b pinned the hash by hand because nixpkgs bumped 1.98.8->1.98.9 without
updating vendorHash (NixOS/nixpkgs#545860). The previous commit's lock moves
nixos-26.05 past the point where that fix was promoted from release-26.05, so
the override is now dead weight — and a stale vendorHash override is worse
than none, since it silently wins over a correct upstream value on the next
version bump.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tome (formerly AudibleLibrary) comes back as a git+ssh input against our own
gitea, fetched with darman's ambient key. `flake = false` — it's a plain
source tree consumed by pkgs/tome.nix, not a flake of its own.
This re-breaks `./scripts/deploy install terra localhost` exactly the way
4f79ec7 removed it for: the installer-iso has no credentials, so the git+ssh
fetch fails at nixos-install, post-disko. Taking that tradeoff knowingly
rather than losing the app from the desktop config again — the note in
flake.nix spells it out for whoever hits it next.
The lock also picks up the routine input refresh, including the nixos-26.05
rev that finally carries the tailscale vendorHash fix (next commit) and a new
client-ts-generator-src node pulled in by authentik-nix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- swap gtk portal/apps for cosmic (xdg-desktop-portal-cosmic, cosmic-files,
cosmic-settings) and drop dolphin/protonplus/bambu-studio (bambu-studio
moved to flatpak alongside the other comms/gaming flatpaks)
- mount jupiter's samba share at /mnt/jupiter (automount, credentials from
the same samba_password secret jupiter itself uses)
- add tea (gitea's remote API CLI) for talking to git.mgaction.town from
terra without SSHing into jupiter
- new dark icon themes (Amy, Azure Glassy, Slot Beauty) vendored from
gnome-look.org tarballs, packaged since pling download links expire
- rishot: fix Qt5Compat.GraphicalEffects QML import (was missing qt6.qt5compat
on QML_IMPORT_PATH, so quickshell failed at config-load)
- launcher widgets: stop LauncherConsole/LauncherDock from reserving
compositor space (ExclusionMode.Ignore, they're overlays not real docks);
bump LauncherCorner app icon size 28->34
- comms script: launch telegram/discord via flatpak, not native binaries
- nix-ld + boot.binfmt aarch64 emulation (for building/flashing mercury
from terra)
configurationLimit prunes generations beyond the cap as part of the switch,
but pruning only drops a generation as a GC root — the store paths
themselves still need an actual collect to free the disk. Do that right
after every jupiter switch rather than waiting up to a week for
gc.dates=weekly to matter again.
Own Hyprland plugin (border + title bar), public repo on our own gitea,
fetched over https (no credentials needed). nixpkgs.follows keeps its build
ABI-correct — Hyprland plugins are ABI-locked to the exact Hyprland build
they load into, so it has to build against this flake's own nixpkgs rather
than whatever hypr-chrome's own flake.lock pins standalone.
TEMPORARY: nixpkgs bumped tailscale 1.98.8->1.98.9 without updating
vendorHash (NixOS/nixpkgs#545860, fixed on release-26.05 but not yet
promoted to the nixos-26.05 channel branch this flake tracks). Remove once
`nix flake lock --update-input nixpkgs` picks up a fixed rev.
Addressing is fully static, but netcup's router still sends periodic RAs on
this segment; the kernel then tries (and fails, since the static route
already exists) to install its own default route from them, spamming
"ndisc_router_discovery failed to add default route" on the console.
Runner registers against the same gitea instance and runs jobs in podman
containers (services/containers.nix), one image per runs-on label using the
catthehacker act-compatible images. Registration token comes from gitea
itself (gitea actions generate-runner-token) and is stored in
secrets/jupiter.yaml, rendered into a TOKEN=... env file via sops.templates
since gitea-actions-runner takes an EnvironmentFile, not a raw secret path.
The old key was a leftover from a previous laptop. Also cap every host at
5 boot generations and journald at 200M so none of them can quietly repeat
jupiter's 34-generations-on-a-29G-eMMC incident.
- Hyprland workspace rules: start-communications.sh launches telegram +
discord into special:communications; qbz/discord/telegram switched to
flatpak (nix-flatpak, Flathub) — removes qbz and proton-pass-cli flake
inputs
- proton-pass-cli and claude-code sourced from nixpkgs-unstable; unstable
pkgs set threaded into home-manager via extraSpecialArgs
- GTK/libadwaita dark theme fixed: dconf color-scheme = prefer-dark written
declaratively instead of a per-session gsettings call
- scripts/keys: store/restore SSH host keys and sops age keys via Proton
Pass (ssh_host#<config> / age#<config> / age#admin naming); no jq dep,
uses pass-cli --field directly
- jq added to desktop-apps system packages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hyprland workspace rules: start-communications.sh launches telegram +
discord into special:communications; qbz/discord/telegram switched to
flatpak (nix-flatpak, Flathub) — removes qbz and proton-pass-cli flake
inputs
- proton-pass-cli and claude-code sourced from nixpkgs-unstable; unstable
pkgs set threaded into home-manager via extraSpecialArgs
- GTK/libadwaita dark theme fixed: dconf color-scheme = prefer-dark written
declaratively instead of a per-session gsettings call
- scripts/keys: store/restore SSH host keys and sops age keys via Proton
Pass (ssh_host#<config> / age#<config> / age#admin naming)
The staged installer booted, the auto-install service picked up terra's host
key and removed its temporary UEFI entry — then died before running anything:
env: 'bash': No such file or directory (status 127)
The service ran with the restricted PATH a `path = [...]` list produces, which
has no bash — so `./scripts/deploy`'s `#!/usr/bin/env bash` shebang could not
resolve, let alone the nix / nixos-install / git / sudo it then calls.
Point the unit's PATH at /run/current-system/sw/bin (+ /run/wrappers/bin for
sudo), which carries the whole installer toolset. mkForce because NixOS
otherwise derives environment.PATH from `path` and that line would win. HOME
moves into the same environment attr.
Verified: environment renders {HOME=/root,
PATH=/run/current-system/sw/bin:/run/wrappers/bin}, and sw/bin contains bash,
nix, nixos-install, git, sudo, efibootmgr, mount, grep, sed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
findiso now works — the installer loop-mounted the iso, mounted the store
squashfs and the overlay — then died with:
stage 2 init script (/mnt-root//init) not found
The live ISO's root is a tmpfs; stage 1 locates the real system's init via
init=<toplevel>/init, which the grub/isolinux menu supplies on a normal boot
(iso-image.nix:47,159). EFI-stub-booting our own cmdline off the ESP, we
never passed it, so stage 1 fell back to /mnt-root/init on the empty tmpfs.
Build the installer-iso toplevel and prepend init=$toplevel/init to the
cmdline (both boot modes). That path resolves once the store squashfs mounts,
because the iso carries the full closure of its own toplevel. Also switch
root=fstab -> root=LABEL=<volumeID> to match what the ISO menu passes (findiso
overwrites /dev/root regardless), and add boot.shell_on_fail for a shell
instead of the reboot/ignore prompt if stage 1 ever fails again.
Verified: the generated cmdline carries init=/nix/store/<toplevel>/init and
that store path contains /init.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
terra booted the staged installer this time but dropped to an emergency
shell: stage 1 mounted /sysroot, then timed out on /sysroot/nix/.ro-store
waiting for /dev/disk/by-label/nixos-minimal-26.05-x86_64.
findiso= is handled only in the scripted stage-1-init.sh, which loop-mounts
the file the param points at and symlinks it to /dev/root. The systemd initrd
— the default since 26.05 — has no findiso handling at all: iso-image.nix
mounts /iso directly from /dev/disk/by-label/<volumeID>, a label that only
exists when the ISO is the physical boot medium. Booted as kernel + initrd
off the ESP with the iso as a plain file on another partition, that label
never appears, so the store squashfs never mounts.
The entire `install <config> localhost` path is built on findiso, so pin the
installer to script stage 1. Verified: /iso device flips to /dev/root,
root=LABEL=... is added to the params, the rebuilt initrd's /init is
stage-1-init.sh and carries the findiso logic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`install <config> localhost` assumed systemd-boot. terra's CachyOS boots
Limine, so it stopped at "/boot/loader/entries doesn't exist" — the check
added in e538788 doing its job, but with nowhere to go.
Limine cannot help here at all: `bootctl status` lists it as
`✗ One-shot entry control`, and CachyOS's pacman hooks regenerate
limine.conf regardless. So drop below the bootloader entirely and use the
firmware's own BootNext, pointing at a temporary UEFI entry that
EFI-stub-boots the installer kernel straight off the ESP. That keeps the
property which makes this safe to attempt: BootNext is spent by that one
boot, so a failed try still comes back on the normal bootloader.
- picked at runtime: systemd-boot loader entry when $BOOT/loader/entries
exists, else arm_efi_bootnext(). jupiter/neptun and terra-after-install
keep the systemd-boot path.
- `efibootmgr --create-only`, NOT `--create`: the latter pushes the entry
to the front of BootOrder, which would make a wiped installer the
permanent default if the install died halfway.
- the EFI stub loads initrd= from the volume it was loaded from, so this
mode stages on --print-esp-path rather than --print-boot-path.
- stale entries from an earlier attempt are removed before adding one, and
homelab-auto-install.service deletes the entry as soon as it boots, so
nothing lingers in NVRAM pointing at a reformatted partition.
- label matching is EXACT ("Homelab Installer"); a prefix match would have
deleted this box's Windows or Limine entry.
Verified against terra's real NVRAM (read-only): the label parser picks out
Limine/UEFI OS/Windows by exact name and rejects prefixes, and both branches
run end-to-end under stubs — BootNext mode emits the right --disk/--part,
loader path and initrd= cmdline, systemd-boot mode still writes its entry and
never calls efibootmgr.
README/CLAUDE.md corrected: terra runs Limine, not systemd-boot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `install <config> localhost` auto-path added in 0ea9020 could not have
completed. Fixed three blockers plus the guard that was silently not guarding.
Inside installer-iso the run died before doing anything:
- systemd sets no $HOME for a service without User= (SetLoginEnvironment=
defaults to false), and this script runs under `set -u`, so it aborted on
the bare $HOME with "unbound variable". Added $KEYDIR + Environment=HOME.
- the host key it needs to seed /etc/ssh isn't on the ISO at all — that is
built from a public repo and carries no credentials on purpose. It now
travels on the boot partition, located via homelab.keypart=<PARTUUID> on
the kernel cmdline, and dies with the disko wipe minutes later. Without
it sops can't decrypt on boot #1 and mutableUsers locks darman for good.
- installation-cd-minimal leaves experimental-features unset, so both
`nix run` and `nixos-install --flake` failed. (The nixos-images kexec
installer sets them itself, which is why the same branch worked after
kexec-local but not from the ISO.)
The staging-dir guard passed everything on btrfs: findmnt prints the
subvolume as /dev/sdb2[/@], lsblk can't open that, and an empty parent was
treated as "different disk" — so it allowed staging the iso on the very disk
disko then wiped. terra's current CachyOS root is exactly that layout. Now
uses --nofsroot, resolves EVERY whole-disk ancestor (LVM/RAID span several:
/mnt/ssd_01 -> sdd+sde), and treats "can't tell" as a hard error. btrfs
staging is refused outright — stage-1 mounts a btrfs volume's top level, so
an iso inside a subvolume is unreachable.
findiso= lost its leading slash whenever the staging mountpoint was /,
giving /findisovar/tmp/x.iso and an emergency shell after the reboot.
Also:
- confirm before rebooting, like flash/kexec-local already do; --yes skips
it and is what the ISO passes itself
- $BOOT from `bootctl --print-boot-path`, not a hardcoded /boot
- free-space checks on both target partitions before the ~1GB copy
- `nix run .#disko` / `.#nixos-anywhere` from locked inputs instead of
github:... master-of-the-day, resolved while a disk is being wiped
- one_match warns instead of silently taking [0]; require_tracked covers
every hosts/<config>/*.nix; flash traps its mount
- drop nixos-images' `inputs.nixpkgs.follows` — that input doesn't exist,
it only printed a warning on every nix command
Verified: the prepare path exercised under stubs against this box's real
disks (btrfs-on-OS-disk, tmpfs, LVM, subdirectory), shellcheck clean, all
six configs evaluate, checks.kexec-local still passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
./scripts/deploy install <config> localhost now branches on is_live_installer()
(checks uname -n): outside a live installer it builds installer-iso, stages
its kernel/initrd on the ESP and the iso file on a disk the caller picks
(never auto-picked — the wrong disk here is destroyed mid-install), writes a
systemd-boot one-shot findiso= entry with homelab.install=<config> on the
kernel cmdline, and does a real systemctl reboot (not kexec — terra's
kexec-local hang is specifically in kexec's device-shutdown pass, a real ACPI
reboot never runs that code at all).
installer-iso gains homelab-auto-install.service: once homelab-checkout.service
clones the repo, it reads homelab.install= back off /proc/cmdline and re-runs
the identical deploy command itself, now genuinely inside the installer, so
it takes the disko+nixos-install branch instead of preparing again. The whole
reinstall is one command and unattended after the first reboot.
Also: every root-requiring path (kexec-local, the new prepare-and-reboot
branch, the disko+nixos-install branch) self-elevates via a require_root()
helper that re-execs the original invocation under sudo -E, instead of dying
and asking the caller to prefix sudo themselves. Uses an absolute script path
captured before the script's own cd, so the re-exec is correct regardless of
how it was invoked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
require_tracked() in scripts/deploy now skips its git-tracked-file check
when there's no .git at all (nothing can be untracked in that case) — needed
for an earlier baked-in-`self` approach and kept as a generic fallback.
Since the repo is public now, installer-iso instead clones current master
via a homelab-checkout.service (after network-online.target) on every boot,
to /root/homelab. One ISO build stays useful indefinitely instead of going
stale, and there's still no rsync-the-repo-over step.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed on real hardware: kexec's device_shutdown() pass runs (SCSI disks
sync fine in the log) then the machine goes dark for good — journalctl
--list-boots showed a ~15min gap before the next boot, a genuine hang needing
a manual power cycle, not a slow jump. Near-certainly amdgpu (RX 6800 XT):
discrete AMD GPUs are known to hang during kexec's device-shutdown pass with
no clean handoff before the jump, same class of issue as jupiter's
reboot=pci warm-reboot workaround, just fatal here instead of slow.
README's terra install section now leads with the USB installer path instead
(build ISO, dd to USB, rsync the repo over, disko + nixos-install locally).
CLAUDE.md's gotchas list gets the same warning. installer-iso is renamed from
jupiter-installer to homelab-installer since it's genuinely host-agnostic,
and now ships git.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tome (née AudibleLibrary) is darman's own Photino/.NET desktop app, private
repo on our own gitea. Fetched as a flake input over ssh with darman's
ambient key — same mechanism as any other git input, private or not.
buildDotnetModule package: the Preact/Vite frontend (Tome.App/ClientApp)
builds as its own buildNpmPackage derivation and gets copied into the
published app's wwwroot, since upstream's in-project MSBuild npm target has
no network access in the Nix sandbox. Photino.Native's runtime deps (gtk3,
webkitgtk_4_1, libnotify) are wrapped in — confirmed via readelf/ldd that
this Photino build already targets webkit2gtk-4.1, not the now-removed 4.0.
Also added dotnet-sdk + nodejs to terra for developing Tome locally, and
vivaldi (unfree, extends the existing allowUnfreePredicate).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not in nixpkgs — upstream ships a shell launcher + QML tree with no build
step, driven entirely by `qs -p <dir>`. Packaged as a stdenvNoCC derivation
that wraps the launcher with RISHOT_CONFIG_DIR (sidesteps its argv0-relative
self-lookup, which wrapProgram breaks) and its runtime deps on PATH.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not in nixpkgs; packaged by github:tomsch/proton-pass-cli-nix. Used by
./scripts/deploy to autofill sudo/ssh passwords from the "HomeLab" vault.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>