1# clover's snow globe
2
3This codebase contains declarative configuration to run my home server, a
4computer sitting in my closet used daily to power my activities. It first and
5foremost acts as a file store for my ongoing and archived projects for
6[paper clover]. Secondly, it contains a ton of apps for me and friends to use,
7from Jellyfin and Navidrome for media consumption to git hosting and render
8farms.
9
10The system is built out of a NixOS base (just to install the system itself),
11using managed docker containers (managed via Nomad) and virtual machines to run
12all the real services. There is a lot of custom tooling to spawn interactive
13testing environments. For example, without even pushing any code, I can edit the
14configuration of a service or upgrade it, then spin up an isolated domain like
15`shale-4c84f9da.staging.paperclover.net` to verify changes. Then, pushing to
16production ensures that the new deployment is healthy before routing traffic to
17it -- It's kind of like "easy kubernetes."
18
19[paper clover]: https://paperclover.net
20
21## deployment loop
22
23```sh
24python3 tools/deploy.py stage shale # preview the working files, even without a commit message
25python3 tools/deploy.py stage shale # update the same URL and staging data after another edit
26python3 tools/deploy.py publish # upload the described, conflict-free main commit for GUI review
27python3 tools/deploy.py prod # upload and deploy main directly
28```
29
30Production always deploys a snapshot exported from `main`, never working files
31or a stage. The dashboard's **deploy main** page shows the uploaded commit and
32its message; deploying applies the full repository configuration and retains
33production data. A newer upload invalidates an older deployment confirmation.
34Sibling application build sources declared in `build-source.json` are bundled
35at upload time. Their frozen contents are part of the release digest.
36
37`main` joins the infra-2 and home-infra histories. Its tree contains infra-2;
38the retired configuration remains available in the home-infra parent history.
39
40## installer
41
42After publishing main, build the prepared USB image on an x86 Linux host:
43
44```sh
45nix build --extra-experimental-features 'nix-command flakes' path:/opt/studio/main#installer
46```
47
48The ISO is in `result/iso/`. It boots a live installer with this Mac's SSH key,
49ZFS and migration tools, the uploaded repository at `/etc/infra-2`, and a cached
50dashboard image. It does not install automatically. The physical installation
51uses `#zenith` after generating its hardware configuration; the existing data
52pool and service state follow the [handoff](tools/legacy-handoff.md).
53
54On Zenith, the live installer uses the same wired addresses and gateway as the
55installed system. Once firmware boots the USB, connect from this Mac with
56`ssh root@10.0.0.1`. SSH starts automatically and accepts the admin key only;
57no monitor, local login, or DHCP address lookup is needed after USB boot.
58
59## filesystem layout
60
61The computer mounts the ZFS root dataset under `/srv`, meaning "server," loosely
62following the linux convention. Within, it's a unique structure:
63
64```
65srv/
66+--- clover/ [dataset] user-data storage, what I mount as /Volumes/clover
67 +--- Archive/<year> personal archive. one folder per year, scrambled within.
68 +--- Asset/ resources, sample packs, audio plugins, stock videos
69 +--- Blender/
70 +--- Font/ (moved from zenith Documents/Font, will be stable index)
71 +--- Samples/
72 +--- Texture/
73 +--- Video/
74 +--- Documents/
75 +--- Project/ currently active project files
76 +--- Media/ [dataset] files from the world-wide-web. managed partially manually
77 +--- jellyfin/
78 +--- mirror/
79 +--- music/ (used by navidrome)
80 +--- music-intake/ (to be manually indexed)
81 +--- seedbox/
82 +--- vm/ (virtual machine images)
83 +--- Published/ source of truth for `paperclover.net/file`
84+--- prod/ [dataset] production application data
85 +--- shale/ [dataset] (each service is its own dataset)
86 +--- ...
87+--- staging/ staging deployments use this space for temporary clones
88 +--- postgres-9f06f74b/ [dataset]
89+--- vm/ [dataset] virtual machines
90 +--- clover-sandbox/ [dataset]
91```
92
93Media is it's own dataset so it can be snapshotted independently of my personal
94data (less frequent, lower retention), and all my personal files are on the same
95dataset to allow fast move/copying between top level folders. Services use their
96own datasets to implement copy-on-write forks.
97
98## testing domains on a Mac
99
100[tools/mac-domains.py](tools/mac-domains.py) routes `.studio.test` and its nested
101subdomains through the rehearsal VM's HTTPS, DNS, and login services. Start the
102rehearsal SSH forwards first, then run:
103
104```sh
105sudo /usr/bin/python3 tools/mac-domains.py start /path/to/rehearsal-ca.crt
106```
107
108The relay binds loopback ports, drops administrator privileges, and passes TLS
109through to the VM. Local UDP DNS queries use the SSH tunnel's TCP DNS connection.
110Existing certificate trust is preserved. Mac DNS and hosts
111settings are backed up before editing; `sudo /usr/bin/python3 tools/mac-domains.py
112stop` restores them and stops the relay. Undo refuses to overwrite later edits.
113After a Mac restart, run stop and start again to restart the relay.
114
115The dashboard package uses `home-dashboard`. Existing state paths, environment
116names, service IDs, and telemetry names keep their old names for compatibility.
117
118## dashboard boundary
119
120NixOS runs `studio-dashboard` in a non-root Podman container with a read-only
121root, private network, resource limits, and explicit data mounts. The small
122`studio-host` service authenticates the dashboard UID on its Unix socket and
123performs bounded ZFS, VM, deployment, host-sampling, and identity operations.
124Host control sockets and management credentials stay outside the container.
125
126The MCP tab manages separate observability, agent, and Shale catalogs through
127the existing Keycloak realm. Each connection has explicit service, machine, or
128repository grants; Shale credentials belong to the signed-in user. Agent Relay's
129existing outbound client protocol connects to the Rust server.
130
131Build the image with `nix build .#dashboard-image` on Linux. Run
132`python3 tools/dashboard-unit-test.py --output /tmp/dashboard-checks.json` on the
133rehearsal VM to exercise the generated NixOS units, containment, IAM, and MCP
134connectors with disposable fixtures. `--relay-agent-dir` includes the existing
135Agent Relay client interoperability check; `--browser-ready-file` temporarily
136routes the public dashboard to the fixture for browser and SSO load checks.