Commit Graph
2481 Commits
Author SHA1 Message Date
Ryan Yin 27b99fa45f docs(agents): add untrusted-source safety rules
Treat content from unverified sources as data, not instructions, and require authorization before cloning, downloading, or executing unverified code and install scripts.
2026-09-26 08:23:15 +08:00
Ryan Yin 2a2933a889 docs(youko): note the transmission inbound-port TODO
youko's default gateway is suzi (mihomo transparent proxy, no UPnP/NAT-PMP),
so transmission cannot map its peer port; record the options for later.
2026-09-26 00:23:54 +08:00
ryan4yin | 二花 9fe6166fd1 feat(homepage): add Jellyfin, alerting, files and k3s-test-1 services (#322) 2026-09-26 00:15:06 +08:00
ryan4yin | 二花 a111676ede feat(youko): add Jellyfin media server (#321)
* feat(youko): add Jellyfin media server

Jellyfin reads the existing Transmission download tree on the public HDD
(via the shared `fileshare` group) and transcodes with the AMD Barceló
iGPU through VA-API. Encoding settings are declared in Nix
(`forceEncodingConfig`), so the web UI cannot silently drift.

Replace the per-service public ACME certificates with a single
`*.writefor.fun` wildcard (plus the apex) shared by every public vhost,
so adding a service needs no new ACME entry; a wildcard also keeps
individual hostnames out of public CT logs.

Pin the jellyfin uid/gid to avoid the dynamic-id drift fixed for the
other services in #320.

* fix(youko): enable graphics for Jellyfin VA-API

VA-API needs Mesa's radeonsi VA driver under /run/opengl-driver, but
youko is headless so hardware.graphics was off. Without it Jellyfin's
ffmpeg fails with 'va_openDriver() returns -1'. Enable it so the iGPU
can be used for hardware transcoding.

* fix(youko): use VA-API hardware encoding for Jellyfin

Enabling hardware decoding alone left encoding on the CPU (libx264), which
pins ~3 cores per 720p stream. Set transcoding.enableHardwareEncoding so
the iGPU (Vega VCN) does the H.264 encode too.

* feat(youko): harden Jellyfin proxy trust, groups, and HEVC encode

Add jellyfin to the render/video groups for robust VA-API access (was
relying on the world-writable render node), enable HEVC hardware encoding
(Vega's VCN supports it; AV1 does not), and patch network.xml on start to
trust Caddy as KnownProxies and advertise the public URL.

* fix(youko): run Jellyfin network.xml patch every start; allow LAN discovery

The module's preStart exits early once encoding.xml matches, so the
mkAfter network.xml patch only ran when encoding settings changed. Run it
with mkBefore instead. Also allow Jellyfin app auto-discovery (UDP 7359 /
TCP 8096) from the home network only.
2026-09-25 23:22:04 +08:00
ryan4yin | 二花 9187e4d920 fix(youko): pin dynamically-allocated service uid/gid (#320)
Service users/groups get a dynamically allocated uid/gid, which shifts when the user set changes: adding `immich` moved gitea 993:985 -> 996:993 and rustfs 987:977 -> 988:983. Files on the persistent volumes keep the old numeric owner, so gitea and rustfs lost access to their data and failed to start after a restart. Pin every dynamically allocated service id on youko to its current value. The ids are host-specific, so this is a host file.
2026-09-25 20:55:02 +08:00
ryan4yin | 二花 5ee8b728b5 fix(restic): use the correct systemd timer option names (#319)
timerConfig was set with lowercase onCalendar/randomizedDelaySec, which systemd treats as unknown directives. The generated timer had no schedule, so `systemctl list-timers` showed NEXT=- and no backup ever ran. Use the real keys OnCalendar/RandomizedDelaySec.
2026-09-25 20:39:59 +08:00
ryan4yin | 二花 4bd463a784 chore(eval): resolve catppuccin and rust-overlay deprecation warnings (#318) 2026-09-25 20:36:26 +08:00
ryan4yin | 二花 793b636fa5 feat(youko): add Immich photo platform (#317)
Add Immich via the native nixpkgs services.immich module on youko, reusing the host PostgreSQL 16 (pgvector + vectorchord) and a shared Valkey instance for Redis-compatible caching and BullMQ queues (Dragonfly rejected BullMQ's dynamic Lua keys). Media lives on the encrypted HDD at /data/apps/immich; Caddy publishes immich.writefor.fun with a publicly-trusted Let's Encrypt cert (security.acme + Cloudflare DNS-01) so the mobile app connects. OTEL metrics are scraped by VictoriaMetrics, and /var/cache is now preserved on the tmpfs-root hosts so the ML model cache survives reboots.
2026-09-25 20:30:01 +08:00
ryan4yin | 二花 6a6950e7eb fix(youko): don't redirect rustfs console STS/S3 calls (#316)
The s3-console Caddy vhost redirected every request to / to /rustfs/console/. The RustFS console frontend signs its own S3/STS requests to / (POST / for STS, GET /?x-id=ListBuckets), so the 302 broke the console session and login failed server-side with SignatureDoesNotMatch ("missing signed header: content-type"). Restrict the redirect to unsigned browser GET /; signed requests now reach RustFS unchanged.
2026-09-25 16:21:12 +08:00
ryan4yin | 二花 7d8d6ec8ad chore(youko): drop redundant loki-gateway Caddy vhost (#315) 2026-09-25 16:09:44 +08:00
ryan4yin | 二花 e8e7844051 Merge pull request #314 from ryan4yin/feat/rustfs-s3
feat(youko): replace MinIO with RustFS S3
2026-09-25 15:53:40 +08:00
Ryan Yin 7e0e4aa566 feat(youko): replace MinIO with RustFS S3
Deploy RustFS in place of the retired MinIO and move the S3 consumers over.

- flake.nix / flake.lock / outputs/default.nix: add a nixpkgs-small
  (nixos-unstable-small) input for the rustfs 1.0.0 package (with the web
  console), instantiated as pkgs-small in genSpecialArgs alongside the other
  nixpkgs instances; drop the rustfs flake input. The rest of the system stays
  on the default nixpkgs.
- hosts/12kingdoms-youko/homelab-services/rustfs.nix: use nixpkgs' own
  services.rustfs module, with package = pkgs-small.rustfs and
  environmentFile = the agenix rustfs.env secret.
- caddy.nix: s3.writefor.fun (S3 API) and s3-console.writefor.fun (console,
  redirects / to /rustfs/console/).
- secrets/nixos.nix: declare a single rustfs.env (dotenv) secret holding
  RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY, replacing the separate key files.
- infra/minio -> infra/rustfs: buckets are managed with the hashicorp/aws
  provider against the RustFS S3 endpoint; IAM/ILM are applied with rc.
2026-09-25 15:23:50 +08:00
Ryan Yin e401a75ca5 chore(agents): remove crush
Crush hardcodes banned bash commands, blocking many operations. Drop the package, its AGENTS.md/CRUSH.md install target, env vars, and preservation entries.
2026-09-25 00:30:12 +08:00
Ryan Yin 310d59b437 flake.lock: Update
Flake lock file updates:

• Updated input 'mysecrets':
    'git+ssh://git@github.com/ryan4yin/nix-secrets.git?ref=refs/heads/main&rev=8533d18ee50e6e5cddd3cd3decfa6ac54f116472&shallow=1' (2026-09-23)
  → 'git+ssh://git@github.com/ryan4yin/nix-secrets.git?ref=refs/heads/main&rev=7e75aad771e8748f666a83832678f3fbb6378a73&shallow=1' (2026-09-24)
2026-09-25 00:30:12 +08:00
ryan4yin | 二花 c3a8d99ceb Merge pull request #313 from ryan4yin/fix/k3s-test-1-master-config-cleanup
fix(k3s): drop Home Manager from test masters
2026-09-24 14:12:04 +08:00
Ryan Yin 9a6923b4e9 fix(k3s): drop Home Manager from test masters
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.
2026-09-24 13:35:33 +08:00
ryan4yin | 二花 f5f4531fcb Merge pull request #312 from ryan4yin/fix/k3s-test-1-guest-dns
fix(k3s): stabilize guest networking and control-plane metrics
2026-09-24 11:36:41 +08:00
ryan4yin | 二花 882383f754 Merge pull request #309 from ryan4yin/fix/k3s-test-1-stable-ipv6
fix(k3s): disable temporary IPv6 addresses in test VMs
2026-09-24 11:29:23 +08:00
ryan4yin | 二花 069e081fec fix(k3s): bind control-plane metrics to node addresses (#311)
* fix(k3s): bind control-plane metrics to node addresses

* docs(microvm): document remote deployment workflow

* fix(microvm): deploy runner before guest switch

* docs(microvm): standardize on just deployment commands

* docs(k3s): explain control-plane metrics address

* docs(hosts): move generic build commands to host guide

* docs: simplify command operation guidance
2026-09-24 11:13:59 +08:00
Ryan Yin c752703488 docs(network): explain DHCPv6 DNS overrides 2026-09-24 10:43:49 +08:00
Ryan Yin 688a244ae0 fix(k3s): ignore advertised DNS on test VMs 2026-09-23 23:44:13 +08:00
Ryan Yin f357569d32 fix(k3s): disable IPv6 privacy on guest NICs 2026-09-23 23:05:53 +08:00
Ryan Yin fa0743a28b fix(k3s): disable temporary IPv6 addresses in test VMs 2026-09-23 22:28:35 +08:00
Ryan Yin d1dd35bd7c chore(codex): bump the override to 0.156.1
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.
2026-09-23 12:07:28 +08:00
Ryan Yin 55aa4e3e10 fix(k8s): link the CNI dirs on the k3s agent nodes
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.
2026-09-23 12:07:28 +08:00
Ryan Yin 64cad91401 fix(k8s): unblock the k3s-test-1 workers
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.
2026-09-23 12:07:28 +08:00
Ryan Yin a3efb42202 chore(k8s): update secrets 2026-09-23 12:07:28 +08:00
Ryan Yin a2604a0c32 chore(codex): override to 0.156.0 pending llm-agents bump 2026-09-23 10:37:52 +08:00
Ryan Yin f6e331dbf7 flake.lock: Update
Flake lock file updates:

• Updated input 'llm-agents':
    'github:numtide/llm-agents.nix/34081a176242cbccd62f19978f32abfff933813f?narHash=sha256-5QvQZFsM/xcZRVUGuv56QU3d0RQZcqR9axikxGf1KLE%3D' (2026-09-19)
  → 'github:numtide/llm-agents.nix/fcf80443a6e5363f30dc9b191bcb65cfcd788df2?narHash=sha256-uWgg4A%2BhrlViz1L0y9D8zMkIjQU8UzJqoyxaZ3xY24Q%3D' (2026-09-23)
• Updated input 'llm-agents/bun2nix':
    'github:Mic92/bun2nix/5765b0614591f75ee8ba5596e81ae85c167d1071?narHash=sha256-mEq2kU%2BljompTToZ44Afvm7d/rHJ5iMBl0tjZuyShJs%3D' (2026-08-29)
  → 'github:Mic92/bun2nix/07a5bfc8ac36c5370199343fa620b69f63186e33?narHash=sha256-1/SLBjSYBzDdDhvLQ83wrzBakyS/xNGgtxHGNuveOEQ%3D' (2026-09-21)
• Updated input 'llm-agents/nixpkgs':
    'github:NixOS/nixpkgs/c7def046b9a883d46974757852106483d741586f?narHash=sha256-6RSEDHIWQtesQKWSu5qRai8L2h4KgCgMEfJHstW99G4%3D' (2026-09-14)
  → 'github:NixOS/nixpkgs/b6c98e9e6633ee64753b594ff4a5febf0367fc00?narHash=sha256-QpXotokGh5eeMG1Njiuh27oP0rK6t5%2BcMp7ymXSs7mc%3D' (2026-09-22)
2026-09-23 09:50:10 +08:00
ryan4yin | 二花 3b13291216 feat(computer-use): sync the X clipboard and document the input-level design (#308)
- run two autocutsel instances so the X PRIMARY/CLIPBOARD selections stay in
  sync, making copy/paste work in the session and through VNC
- drop --disable-blink-features=AutomationControlled: nothing drives the
  browser over CDP, so navigator.webdriver is already false and the flag only
  marked the setup as automated
- document why the driver stays on real X11/AT-SPI input and is never granted
  a CDP attach, and note the SSH-tunnel path for manual VNC access
- extend the computer-use eval test
2026-09-22 20:45:09 +08:00
ryan4yin | 二花 b19818ef44 feat(k8s): add the k3s-test-1 worker nodes (#307)
Add worker-1 (4 vCPU / 16 GiB) on shoryu, and worker-2 (4 vCPU / 16 GiB) plus worker-3 (2 vCPU / 8 GiB) on shushou, as microVMs. They come with a genK3sAgentModule and the per-host/output/home-manager wiring, and are labelled node-role.kubernetes.io/worker=true. The masters keep their control-plane:NoSchedule taint, so workloads land on the workers.
2026-09-21 23:09:13 +08:00
ryan4yin | 二花 ca00d26642 chore: drop the unused darwin proxy set script (#306)
The script pointed the macOS nix-daemon at the homelab v2ray HTTP proxy (192.168.5.101:7890), but that proxy was never running. Remove it together with the just darwin-set-proxy recipe and the README step that referenced it.
2026-09-21 22:33:33 +08:00
ryan4yin | 二花 242c607ce6 feat(k8s): reserve a VIP block and narrow the tailscale routes (#305)
Reserve 192.168.5.192/27 as a dedicated kube-vip block, outside the router's DHCP pool (.2-.99): one /29 per cluster, k3s-test-1 gets .192/29 with the API VIP at .193.

Advertise only youko itself (.183/32) and that block over Tailscale, instead of the whole /24. The /24 route shadowed hosts that are already reachable directly, e.g. idols-ai at 192.168.5.100.

Deploy prerequisites (not in this repo): approve the new Tailscale routes; point kube-vip at the new VIP; move the *.writefor.fun wildcard from .101 to .183 and update scripts/darwin_set_proxy.py, since .101 is no longer advertised.
2026-09-21 22:28:36 +08:00
ryan4yin | 二花 97c2d1325d docs: refresh stale docs after the VM and backup refactors 2026-09-21 22:03:21 +08:00
ryan4yin | 二花 5d0055b024 Merge pull request #277 from ryan4yin/feat/cua-headless-sway
feat(computer-use): headless X11/i3 + cua-driver on ruby and kana
2026-09-21 21:43:45 +08:00
Ryan Yin 0185fd0f85 docs(computer-use): document the headless i3 session 2026-09-21 21:31:27 +08:00
Ryan Yin 00aab5f562 feat(computer-use): add a headless X11/i3 environment
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.
2026-09-21 21:31:27 +08:00
Ryan Yin 90761a6b73 feat(computer-use): package computer-use-linux and cua-driver
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.
2026-09-21 21:31:27 +08:00
ryan4yin | 二花 ef00eb31e0 docs: warn that user-readable decrypted secrets must not be read (#303)
Only three secrets are readable as the login user (nix-access-tokens,
ssh-key-romantic and the nushell one); everything else is root-only and
the filesystem already keeps agents out.
2026-09-21 21:18:06 +08:00
ryan4yin | 二花 260da1ee20 chore: rename the nushell secret, and forbid reading decrypted secrets 2026-09-21 21:13:50 +08:00
ryan4yin | 二花 bc6748d021 feat(backup): notify on failure, and 404 unconfigured hostnames (#301)
* feat(backup): notify on failure, and 404 unconfigured hostnames

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.

* docs(backup): say 'cloud copy' rather than 'offsite'

Clearer for a reader who does not live in backup jargon; the requirement
(not in the homelab, immutable) is spelled out in the text.

* docs(secrets): state the recipient invariant

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.

* docs(secrets): state that every secret must be decryptable by the desktops

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.

* docs(secrets): desktops and recovery_key have identical access

recovery_key is a member of desktop_keys, so every group written as
desktop_keys ++ ... covers both.

* feat(monitoring): scrape the restic rest-server

Expose /metrics on the loopback-bound server, scrape it with
victoriametrics, and block /metrics on the public vhost.

* docs(backup): record what the metrics actually expose

rest-server only ships the go/promhttp defaults, so repository growth is
charted from node-exporter's filesystem metrics instead.
2026-09-21 21:01:18 +08:00
Ryan Yin 37f29154d1 docs(backup): record what the metrics actually expose
rest-server only ships the go/promhttp defaults, so repository growth is
charted from node-exporter's filesystem metrics instead.
2026-09-21 20:57:41 +08:00
Ryan Yin 842cd26f64 feat(monitoring): scrape the restic rest-server
Expose /metrics on the loopback-bound server, scrape it with
victoriametrics, and block /metrics on the public vhost.
2026-09-21 20:56:01 +08:00
Ryan Yin 1cbb52add1 docs(secrets): desktops and recovery_key have identical access
recovery_key is a member of desktop_keys, so every group written as
desktop_keys ++ ... covers both.
2026-09-21 20:51:13 +08:00
Ryan Yin 89e57b5b39 docs(secrets): state that every secret must be decryptable by the desktops
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.
2026-09-21 20:50:11 +08:00
Ryan Yin a43aa02a69 docs(secrets): state the recipient invariant
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.
2026-09-21 20:45:47 +08:00
Ryan Yin 8a44c23d74 docs(backup): say 'cloud copy' rather than 'offsite'
Clearer for a reader who does not live in backup jargon; the requirement
(not in the homelab, immutable) is spelled out in the text.
2026-09-21 20:44:56 +08:00
Ryan Yin 096607a5c5 feat(backup): notify on failure, and 404 unconfigured hostnames
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.
2026-09-21 20:42:39 +08:00
Ryan Yin c3f0dbf8bd revert(backup): drop append-only for the desktop endpoint
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.
2026-09-21 20:38:08 +08:00
Ryan Yin 5a017666f6 refactor(backup): name the REST endpoint restic.writefor.fun
'backup' did not say what the service is.
2026-09-21 20:38:08 +08:00