Commit Graph
16 Commits
Author SHA1 Message Date
darmanandClaude Sonnet 5 dc83e8c156 add node_exporter host vitals + quickshell HUD
Prometheus node_exporter enabled on every host, plus a quickshell widget
(SUPER+CTRL+V on terra) to view live CPU/mem/disk/net/uptime without a
separate dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FHr5ug9pu8q4XPrRkFnzJ
2026-08-22 20:50:16 +02:00
darmanandClaude Sonnet 5 7d63ba95df add mars host, move Hermes Agent there from jupiter
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
2026-08-22 03:00:24 +02:00
darmanandClaude Sonnet 5 9403122888 jupiter: fix Hermes cron scheduler defaulting to UTC
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>
2026-08-19 23:00:55 +02:00
darmanandClaude Sonnet 5 b5fa599671 jupiter: add isolated Hermes Agent instance
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>
2026-08-19 22:46:32 +02:00
darmanandClaude Sonnet 5 0fa567245a jupiter: GC on every boot, silence mdadm warning, migrate sabnzbd off reused ini
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>
2026-08-19 20:58:40 +02:00
darmanandClaude Sonnet 5 6c8046bac8 jupiter: VAAPI hardware transcoding for jellyfin, move heavy state off the eMMC
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>
2026-08-18 00:38:22 +02:00
darmanandClaude Opus 5 0ec3c6413c jupiter: keep booting when the data array is missing
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>
2026-08-06 03:32:42 +02:00
darmanandClaude Opus 5 5a4588c532 gitea: provision a ci-bot account with repo + branch-protection access
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>
2026-08-06 03:32:28 +02:00
darman 63ca6f8409 jupiter: enable gitea Actions + register a jupiter runner
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.
2026-07-29 21:43:05 +02:00
darman 78dcdb6f57 jupiter: cap systemd-boot generations at 2 (eMMC space)
common.nix's cap of 5 comes from this box's own 34-generation incident, but
at ~5G free on a 29G eMMC even 5 is too many.
2026-07-29 21:42:59 +02:00
darman a4c7768625 immich: fix OIDC clientId, redirect logout to immich's own login page
clientId was still the placeholder "immich" instead of Authentik's actual
generated id, and the sops secret it points at (immich_oauth_client_secret)
was never declared on jupiter. Wire both up, and set endSessionEndpoint so
logout lands back on immich instead of Authentik's "logged out" page.
2026-07-23 23:14:11 +02:00
darman 6ce61ab519 immich: add the service and import the ZimaOS library
jupiter had a leftover docker-compose Immich on the RAID (/mnt/data/Immich,
9.9G) that survived the NixOS install. Native module now, media at
/mnt/data/AppData/immich, caddy vhost on 2283 with a 50GB body limit
(caddy's default rejects video uploads).

The package comes from nixpkgs-unstable, the module from the 26.05 pin:
26.05 ships immich 2.7.5, but that database was last written by 3.0.0 and
migrations only run forward --

  corrupted migrations: previously executed migration
  1776217577402-DropAuditTable is missing

Safe because the two module files are byte-identical at these revisions;
services/media/immich.nix carries the diff command to re-check on a bump.
Drop the input once the stable pin ships >= 3.0.0.

immich needs group "users" only to traverse /mnt/data/AppData (drwx--x---);
its own dir stays 0700 immich:immich. mediaLocation is outside /var/lib, so
the module's tmpfiles entry only ADJUSTS it -- add a rule that creates it.

scripts/immich-import-legacy-db does the database half: boots a copy of the
legacy PGDATA under the matching image (PG14 + vchord 0.3.0 + pgvector
0.8.1), dumps it with the local pg_dump 17, restores into a scratch DB,
fixes ownership, and only swaps after confirmation. Never touches the
original. The old cluster ran VectorChord, not pgvecto.rs, so the smart
search and face embeddings survive -- no ML re-run.

Imported: 666 assets, 25 people, 647 clip + 359 face embeddings, 2 users.
2026-07-21 00:51:10 +02:00
darmanandClaude Sonnet 5 d7a66f3e3b Reorganize services/ into category subfolders
Group service modules by category (media, network, vpn, identity,
dev, desktop) to make the growing services/ dir easier to navigate.
containers.nix stays at the top level since it's a shared backend,
not a single-category service.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:08:43 +02:00
darmanandClaude Sonnet 5 69a63dc615 Add *arr media stack + Gitea to jupiter
- sabnzbd, prowlarr, sonarr, radarr, clonarr, seerr, cinephage, mediamanager
  services, wired into jupiter with LAN Caddy vhosts.
- Gitea: migrated the old ZimaOS docker instance's data (sqlite db, 4 repos,
  no LFS objects) into the NixOS module's default stateDir layout. HTTP via
  Caddy; git SSH on its own built-in server at :2222 (not :222 - the unpriv
  gitea user can't bind <1024).
- mediamanager-nix flake input for the mediamanager service.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 21:27:50 +02:00
darman fe5b363b73 Added Jellyfin service to jupiter 2026-07-17 23:12:04 +02:00
erik b6c393ff98 refactor: move host configs under hosts/{jupiter,vps}
- git-mv jupiter/ vps/ into hosts/; fix ../ -> ../../ for common/services/secrets
- flake.nix + deploy point at hosts/<config>/
- README structure updated
- verified: jupiter/vps/vbox all eval
2026-07-13 19:34:27 +02:00