Compare commits

...
6 Commits
Author SHA1 Message Date
kiri ae4a4f0824 terminfo fix 2026-10-10 16:19:08 +02:00
kiri 8d4aa87dd0 update gitea 2026-10-10 16:10:15 +02:00
kiri 71aad7fc55 install wardrowbe on orion 2026-10-10 15:58:44 +02:00
kiri bd287a324a updates 2026-10-10 12:42:42 +02:00
kiri c671bf0bfe update llm-agents 2026-10-03 10:10:37 +02:00
kiri c9cd45e712 install audacity 2026-09-27 12:35:18 +02:00
15 changed files with 507 additions and 72 deletions

No files matched your search

+4
View File
@@ -35,6 +35,10 @@ Both workstations import the provisional `work` profile. See
company credentials and always-available read-only mounts. Only company-root and
workbench environments are migrated; product/personal projects remain user-owned.
Orion's wardrobe service has its own
[setup and operations guide](modules/capabilities/wardrowbe/README.md).
Provision its SOPS secrets and two DNS records before building or activating Orion.
## Maintenance
```console
+31
View File
@@ -18,6 +18,29 @@ Keep this record current by replacing superseded state. Pass-specific procedures
stay here, not in AGENTS. The user must agree before the overall pass is complete.
The unedited `~/sync/notes/setup-todo.md` remains an idea inbox, not authorization.
## Wardrowbe on Orion: implemented, awaiting provisioning and activation
- Orion selects the Wardrowbe capability at `wardrobe.jelles.net`, with Tinyauth
password access at `wardrobe-auth.jelles.net`. Trusted household members select
their profile through Wardrowbe's development login after the gateway; profile
identity is not independently verified.
- Podman containers use immutable image digests, a dedicated network and persistent
named volumes. Database/queue health gates migrations and API startup; frontend
and workers wait for the API. Caddy protects all application routes; published
container ports are localhost-only. Internal AI is disabled everywhere. No
Codex bridge, Ollama or paid provider is configured.
- Four new system SOPS keys and both DNS records remain user-provisioned. Missing
SOPS keys must be supplied before Orion builds or activates. See
[Wardrowbe setup](../modules/capabilities/wardrowbe/README.md) for provisioning,
activation, operations, upgrade limits and manual acceptance.
- Focused source/configuration review only. No tests, host evaluation/build,
`just validate`, activation or remote changes. No existing secrets were decrypted
or read for discovery.
- A user-run Orion build failed compiling Contour's SIMD code, pulled in by
`enableAllTerminfo`. Orion now selects Kitty terminfo for workstation SSH clients
instead of the complete terminal set. The pinned NixOS module was inspected;
formatting and focused diff review only. No rebuild was run after this fix.
## Implemented, reviewed and validated; awaiting user checkpoint
- Flat capability owners; service category flattened. AI, theme, Handy and
@@ -56,6 +79,14 @@ The unedited `~/sync/notes/setup-todo.md` remains an idea inbox, not authorizati
reports interruption concisely. Full builds still include the tools-only shell.
The devenv cache is now in persistent NixOS Nix settings; the flake cache hint
was removed.
- Noctalia tracks upstream's `cachix` branch with its own nixpkgs pin; persistent
Nix settings include the official cache and signing key. The existing 5.2.1
commit is unchanged. Its resulting x86_64 package has a matching signed cache
entry; the previous package with our nixpkgs pin returned 404. No build or
activation was run. For the first activation, pass the cache URL and key from
`modules/capabilities/nix.nix` as `extra-substituters` and
`extra-trusted-public-keys` options to `nh os switch`; afterward Nix uses the
persistent settings.
## Decisions/provisioning still needed
+5
View File
@@ -22,6 +22,11 @@ a provisional ownership boundary, not a different workstation variant. Steam and
LAN SSH on Polaris, and laptop power/firmware management on Zenith, are deliberate.
Orion owns shared services. Tailnet administration supplements existing access;
public Orion SSH closes only after private access is verified. Web services stay public.
Orion installs Kitty terminfo for SSH sessions from the workstations.
Wardrowbe uses a password gateway for trusted household access, followed by profile
selection inside the app; verified individual identity is deferred. Its internal AI
is disabled. Paid API usage, a Codex bridge and local model integration are deferred.
Nix owns reproducible installation, integration and selected settings. Applications
own credentials, sessions, content and deliberately interactive preferences. No
Generated
+70 -58
View File
@@ -98,11 +98,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1789991752,
"narHash": "sha256-SiH+H00IutELZBg7BBNo6r5DU6E+DG7DjrbeX5+QLa0=",
"lastModified": 1791388908,
"narHash": "sha256-eqnGuVYIh6u8XU6UXueceaCi3UfSqbvopeuLYgClEDY=",
"owner": "cachix",
"repo": "devenv",
"rev": "1c57b5dea0d400af97053fdd1a536fca17378f73",
"rev": "27af3b2029a6d1274db8a570fb57292dae5a2551",
"type": "github"
},
"original": {
@@ -143,11 +143,11 @@
]
},
"locked": {
"lastModified": 1775706155,
"narHash": "sha256-h7Rw0vlb0n0Jsk21WJPm7H+1T1bG+PEuxE5cJ2TZl8A=",
"lastModified": 1790316550,
"narHash": "sha256-Qr/8JQ8zpmmWmZ7UAjhxVdRbSgwQ0uc2r/wUBMe4NBk=",
"owner": "abenz1267",
"repo": "elephant",
"rev": "376ee71c66db38683daabd57350bf3f6f086eaf8",
"rev": "a0670fb2cf590a3ceed6f799a45947b86a78d5f4",
"type": "github"
},
"original": {
@@ -198,11 +198,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1788450739,
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
"lastModified": 1790898470,
"narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
"rev": "024633cd702b10285db5cb19b40ad48d2399ba60",
"type": "github"
},
"original": {
@@ -219,11 +219,11 @@
]
},
"locked": {
"lastModified": 1788450739,
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
"lastModified": 1790898470,
"narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
"rev": "024633cd702b10285db5cb19b40ad48d2399ba60",
"type": "github"
},
"original": {
@@ -281,11 +281,11 @@
]
},
"locked": {
"lastModified": 1790080548,
"narHash": "sha256-u4sa7RCbwvk8cZmAGs35F7o0h4IqDc3wsrrnjQvMumk=",
"lastModified": 1791306979,
"narHash": "sha256-KuLePuhwJM9NLt/gTLHzXaOoSZKGoB3m7NfXjiQ98HY=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "cbcbfa2778e0c5653ef349df3ebae8288efac3c0",
"rev": "fae6e9e42c3b762ab47635cddcfaf6f52374a61b",
"type": "github"
},
"original": {
@@ -318,11 +318,11 @@
"treefmt-nix": "treefmt-nix_2"
},
"locked": {
"lastModified": 1790311386,
"narHash": "sha256-bOexMr1RcAS6PFcMnEfwe5apsEoaLmSUUxlqUftd2uU=",
"lastModified": 1791440787,
"narHash": "sha256-nRO1wEJmb4wjz+0Mhpg5VWURTqZRBZq3MvMjtPkKuAI=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "1446b3cb242c8cd24619b58054112cc2d870e591",
"rev": "2109db8971dc18137ff1ac9393627c761609600d",
"type": "github"
},
"original": {
@@ -357,11 +357,11 @@
]
},
"locked": {
"lastModified": 1788036898,
"narHash": "sha256-3NT3yTvoRT7+rxLDNovpyeTDIJkZlBoO72rcu2x9Y9o=",
"lastModified": 1790176416,
"narHash": "sha256-acP7QvsB+dHghUeL9AwSSZ3qS//skpqjv484aveg1EQ=",
"owner": "cachix",
"repo": "nix",
"rev": "b9b81726b38469c55b9706d80d37d6c73cc7f76c",
"rev": "bd10d7f563420c6034b9d1dc75c4168fc01e5afc",
"type": "github"
},
"original": {
@@ -378,11 +378,11 @@
]
},
"locked": {
"lastModified": 1789892076,
"narHash": "sha256-Dkg4VKPmDPqTwaiw2WH5br73tXoL1qrOO0xJnR4TlcA=",
"lastModified": 1791105314,
"narHash": "sha256-oTM7QFgRQ+vxBpMBMc7hEYpqBtpXAzXu2xuB/tru84U=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "9ad722673ab3b3f91f02135e53775825b240b869",
"rev": "e740bc4e9d8918d7819a51486d5099930c65c72e",
"type": "github"
},
"original": {
@@ -398,11 +398,11 @@
]
},
"locked": {
"lastModified": 1788981141,
"narHash": "sha256-7xWS16u13YGlRNKWaAPzRbEyh4OLOXJe31k7h4AnXNU=",
"lastModified": 1791440872,
"narHash": "sha256-PwiSn1VO3/6BD/Gxw/3Ei2sPB3SccyKyJ7nD72Y+WU4=",
"owner": "BirdeeHub",
"repo": "nix-wrapper-modules",
"rev": "1db3c116a6aa61823f8d8f3c47c306846428fc54",
"rev": "d82c328c504648fa952f25fac8332319eb425d4c",
"type": "github"
},
"original": {
@@ -444,11 +444,11 @@
]
},
"locked": {
"lastModified": 1789978172,
"narHash": "sha256-FIRXajv1pPZ+l6On6ekkmcQ6le0Zi0ncRUoR3nB0vKA=",
"lastModified": 1791373788,
"narHash": "sha256-c3oMt0QoE+4yiRk6u1m2L9ogjaUw0uTGn9yk808KkgE=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "9ebcb7766700d7e006d9505247bd7ce0426f4232",
"rev": "4bc63156b109ee7b4103e3ec30565c8c92bde435",
"type": "github"
},
"original": {
@@ -479,11 +479,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1788057806,
"narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
"lastModified": 1790477367,
"narHash": "sha256-oujbxmVKDhci2E4EM3KyAbRUgp5/IsCoiA/LfEF7OOk=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
"rev": "f7cd230690d9fc982d06129f9189b101fddcfa3b",
"type": "github"
},
"original": {
@@ -511,11 +511,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1790238896,
"narHash": "sha256-ym4BG18Pu2awiJIehla1Lo9xiB2QYRcCzW3SB74gEBY=",
"lastModified": 1790600678,
"narHash": "sha256-6k3n7X2UsZ6tAlWTz99RimiZab+17tMUrw9sRUcELiA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "34ca302a9572963c02e385c056be37c85ff51b77",
"rev": "f45c6f04c2f013f004bf94e284e95d72898d9393",
"type": "github"
},
"original": {
@@ -527,33 +527,45 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1788834054,
"narHash": "sha256-kkWAS4mE+fmi+u3mxwD9/bfX6DGJ49Tv8O9kzN5r0HQ=",
"rev": "e9b9cbecbf6c25ca26c150d78570e6332694a129",
"lastModified": 1791336361,
"narHash": "sha256-7WQwlykef1x8n7aoyw4XP6882i2NRs5F6dVCXZ1NI1g=",
"rev": "7dd199b0e2993e37b4775ed66b1c291699608c9f",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1069601.e9b9cbecbf6c/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1086972.7dd199b0e299/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1790822859,
"narHash": "sha256-VBA0QerzzmL+4wxgtwiCdiLwLpJg2/9xOxhgO3BRJQA=",
"rev": "c59305bab2065cfecc4944690d9eedbb56f3a9fa",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1083223.c59305bab206/nixexprs.tar.zst"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst"
}
},
"noctalia": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1790104132,
"narHash": "sha256-yvcudrhnWvWSFS2ujWxAJ7CVdgdiZs2BDGbgn38p1l0=",
"lastModified": 1791427429,
"narHash": "sha256-FzM3cunGIn8oDC1afUsXzGdfI/tK5HjPOHzHXFIAVx4=",
"owner": "noctalia-dev",
"repo": "noctalia",
"rev": "61aa22730c434500fd3f2048eba47658b1f20aad",
"rev": "3e059de4aec58ba5b81f6c366b2b234a58f5ab0a",
"type": "github"
},
"original": {
"owner": "noctalia-dev",
"ref": "cachix",
"repo": "noctalia",
"type": "github"
}
@@ -603,11 +615,11 @@
]
},
"locked": {
"lastModified": 1789890976,
"narHash": "sha256-GKwH3zpy7tartuJMG0Rv/xUsdetG1QLmTVv8UKgJLmA=",
"lastModified": 1791103873,
"narHash": "sha256-nFxM+pKoZ8LJAEnUXARyCaOAloWgaW9kZQOSjWzKcTE=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "7214124c20c1542c90deb54af50e2f53ae02711f",
"rev": "dcd241ba97088c22569d1573286e1b9daad340c0",
"type": "github"
},
"original": {
@@ -618,11 +630,11 @@
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"lastModified": 1791244623,
"narHash": "sha256-uuanbkFk8a313Zfrxdcz8Us/yQtaxNnKJtX8v0ZPpd8=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"rev": "d36eeed142f88b5a636f2e3856a53c61654cf29b",
"type": "github"
},
"original": {
@@ -676,11 +688,11 @@
]
},
"locked": {
"lastModified": 1786901030,
"narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=",
"lastModified": 1791203292,
"narHash": "sha256-vuzVwvjeucSnwKwMBpyP8r5IzdrMKCHEdwTrRFwUN7I=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa",
"rev": "03d8ee1bcbc8f9638907bb79edb9673151d0e22b",
"type": "github"
},
"original": {
@@ -698,11 +710,11 @@
"systems": "systems_2"
},
"locked": {
"lastModified": 1784217994,
"narHash": "sha256-gxHJsrJZo2qbKK47Kf/9ho3i/HhRwkL8XvPnczGPd9E=",
"lastModified": 1791441924,
"narHash": "sha256-2SLAXtgtsZgjK+dyzDAV47oQNoip3dsGX+XXrT/udEk=",
"owner": "abenz1267",
"repo": "walker",
"rev": "42b3ed88abf50bc52638fb2835b7f17e3ea3ac4c",
"rev": "a515085fd39fae80acc086c8fbfd9b7771a9a11d",
"type": "github"
},
"original": {
+2 -2
View File
@@ -24,8 +24,8 @@
};
nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
noctalia = {
url = "github:noctalia-dev/noctalia";
inputs.nixpkgs.follows = "nixpkgs";
url = "github:noctalia-dev/noctalia/cachix";
# Keep upstream's nixpkgs pin to match its cached builds.
};
sops-nix = {
url = "github:Mic92/sops-nix";
+4 -3
View File
@@ -100,10 +100,11 @@ in
pkgs.nodejs
pkgs.python3
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.codex
pkgs.bubblewrap
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.chatgpt
pkgs.llm-agents.pi
pkgs.llm-agents.claude-code
pkgs.llm-agents.herdr
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.pi
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.herdr
pkgs.ctx7
];
@@ -65,6 +65,7 @@
dbeaver-bin
onlyoffice-desktopeditors
pinta
audacity
];
xdg.mimeApps.defaultApplicationPackages = lib.mkAfter [ pkgs.onlyoffice-desktopeditors ];
};
-1
View File
@@ -15,7 +15,6 @@ in
settings = {
server = {
DOMAIN = service.domain;
ROOT_URL = "${service.origin}/";
HTTP_PORT = 3001;
HTTP_ADDR = "127.0.0.1";
+5 -1
View File
@@ -14,9 +14,13 @@ in
settings = {
trusted-users = [ "@wheel" ];
use-xdg-base-directories = true;
extra-substituters = [ "https://devenv.cachix.org" ];
extra-substituters = [
"https://devenv.cachix.org"
"https://noctalia.cachix.org"
];
extra-trusted-public-keys = [
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
];
experimental-features = [
+131
View File
@@ -0,0 +1,131 @@
# Wardrowbe on Orion
[Wardrowbe](https://github.com/Anyesh/wardrowbe) is served through the existing
Caddy instance. The capability owns container images, routing, runtime secret
templates and startup ordering. Image references and settings live in
`default.nix`; endpoints live in the shared service facts.
Internal AI is disabled. Upload clothes and enter tags manually; automatic tagging
and AI outfit generation are unavailable. Local rotation and background removal
remain available. No AI provider key or notification provider is configured.
Tinyauth provides a cookie-based password gateway. After authenticating there,
Wardrowbe's development login accepts an email/name to select a profile. Use a
consistent email for each person. This is for a trusted household: gateway users
can select one another's profiles. It is not verified individual identity.
## Provision before building or activating Orion
Add these top-level keys with `sops edit modules/secrets/secrets.yaml`, using the
existing system-secret recipients. The keys are deliberately not provisioned by
the configuration change; Orion's SOPS build checks require them to exist.
| Key | Value |
| --- | --- |
| `wardrowbe-postgres-password` | A new 64-character hexadecimal password |
| `wardrowbe-secret-key` | A separate new 64-character hexadecimal signing secret |
| `wardrowbe-nextauth-secret` | A separate new 64-character hexadecimal session secret |
| `wardrowbe-auth-users` | A single-line `kiri:bcrypt-hash` string |
Generate each of the first three separately with `openssl rand -hex 32`. If
OpenSSL is unavailable, use `nix run --quiet nixpkgs#openssl -- rand -hex 32`.
Keep values inside the encrypted document; do not paste them into Nix or chat.
Hexadecimal database passwords work directly in the application's connection URL.
On a workstation with Podman, create the gateway user interactively:
```console
podman run --rm -it ghcr.io/tinyauthapp/tinyauth:v5.2.0@sha256:220aa235c76393a73fece831d7ba967b8698d3907fd8e3c4c952f699c7b142a5 user create --interactive
```
Choose username `kiri` and your password, and answer **No** to Docker formatting.
Put the emitted `username:hash` string
in `wardrowbe-auth-users` as a quoted YAML scalar. Additional gateway accounts can
be added as a comma-separated list on the same line. If Podman is unavailable,
prefix its arguments with `nix run --quiet nixpkgs#podman --` instead of `podman`.
This command only creates a hash; gateway sessions persist separately on Orion.
Create DNS records for `wardrobe.jelles.net` and `wardrobe-auth.jelles.net` pointing
at Orion. Publish an AAAA record only if Orion has working public IPv6. Existing
Caddy firewall access supplies HTTP/HTTPS and automatic certificates.
The private container network reserves `10.89.30.0/24`, with gateway
`10.89.30.1`. It must not overlap Orion's routes. If changing it, update both the
network declaration and Tinyauth's trusted-proxy address before first activation.
Add new source files to Git before using a Git-backed flake. From this repository,
activate explicitly:
```console
nixos-rebuild switch --quiet --target-host orion --sudo --flake .#orion
```
The initial activation downloads container images, including the image-processing
model. Allow time and disk space for those downloads. Containers start on boot;
PostgreSQL and Redis become healthy before the backend runs `alembic upgrade head`.
The frontend and workers wait for the backend's health check. A failed migration
prevents the API from starting. Each container has a 15-minute startup deadline.
## Operation
Run these commands on Orion. Container names and unit names correspond directly
to the declarations in `default.nix`:
```console
sudo systemctl status podman-wardrowbe-backend.service
sudo journalctl -u podman-wardrowbe-backend.service -n 80 --no-pager
sudo podman ps --filter name=wardrowbe
sudo podman volume ls --filter name=wardrowbe
```
Persistent named volumes contain PostgreSQL, Redis's append-only queue, uploaded
images and Tinyauth's SQLite sessions. They are root-owned Podman state under
`/var/lib/containers/storage/volumes`, outside Nix. Container recreation and system
rollback preserve these volumes; do not remove them or prune volumes. The network
is also retained when services stop.
Only Caddy serves public traffic. Frontend, backend and gateway host ports bind to
localhost; database and queue ports remain inside the container network. Every
Wardrowbe route, including API and signed image URLs, passes through the gateway.
The gateway's own login endpoint is public. Gateway cookies are secure, HTTP-only
and scoped to `jelles.net` to support redirects between the two subdomains.
SOPS renders root-only mode-0400 environment files at activation. Changes to their
contents restart the consuming containers. Rotating session secrets signs users out.
Changing `POSTGRES_PASSWORD` alone does **not** change an existing database role's
password: coordinate the database password change with the encrypted value.
## Upgrade and recovery
Review upstream release notes and migrations, then update the image tags and
manifest digests together. Upgrade backend and frontend to the same release;
workers use the backend image. Image updates happen through explicit configuration
activation, with no automatic image updater. Review resource requirements before
enabling AI later.
If startup fails, inspect the failing container unit's journal. After correcting
the cause, start the affected units and any dependents whose startup was blocked.
To restart the complete application in dependency order:
```console
sudo systemctl restart podman-wardrowbe-postgres.service podman-wardrowbe-redis.service podman-wardrowbe-backend.service podman-wardrowbe-worker.service podman-wardrowbe-image-worker.service podman-wardrowbe-frontend.service podman-wardrowbe-auth.service
```
Reverting a NixOS generation restores configuration and image references, not
database schema changes. Do not assume an older application can use a migrated
database; follow upstream recovery guidance. No backup system is added here.
## Manual acceptance after activation
- Open the wardrobe in a private browser window. It should require the gateway
password; unauthenticated API and image requests should also be blocked.
- Authenticate, select a Wardrowbe profile, complete onboarding and upload a photo.
Add tags manually and confirm the photo renders; try a rotation.
- With the gateway session active, open `/api/v1/capabilities` on the wardrobe
origin. Both internal AI capabilities should be `false`.
- Restart the application and confirm the profile, photo and tags remain present.
- Check that both public hostnames have valid HTTPS certificates and that the
existing Orion web services still work.
No tests, builds, activation or browser acceptance checks were performed while
preparing this capability.
+234
View File
@@ -0,0 +1,234 @@
{ config, ... }:
let
nixosModules = config.flake.modules.nixos;
in
{
flake.modules.nixos.wardrowbe =
{ config, lib, ... }:
let
service = config.facts.services.wardrowbe;
auth = config.facts.services.wardrowbe-auth;
podman = lib.getExe config.virtualisation.podman.package;
# Multi-architecture manifest digests; updating a tag alone cannot change an image.
images = {
backend = "ghcr.io/anyesh/wardrowbe:backend-1.10.3@sha256:75fd6f85fb7b91ccbb227b52d616bab11b5130c4e22858579f45736419fb592d";
frontend = "ghcr.io/anyesh/wardrowbe:frontend-1.10.3@sha256:730cf52bd8a03b94473b129e9a988b3dae9818e9377fd88917029156ee7a2e17";
auth = "ghcr.io/tinyauthapp/tinyauth:v5.2.0@sha256:220aa235c76393a73fece831d7ba967b8698d3907fd8e3c4c952f699c7b142a5";
postgres = "docker.io/library/postgres:15-alpine@sha256:f7d23353e1b15400d22ebe31189f4d314b87a4c129cc400c8c2d8d4ca127bf81";
redis = "docker.io/library/redis:7-alpine@sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499";
};
healthOptions = [
"--health-interval=10s"
"--health-timeout=5s"
"--health-start-period=30s"
"--health-retries=6"
"--health-on-failure=kill"
];
backendContainer = {
image = images.backend;
environment = {
# Profile selection is available only behind the password gateway.
DEBUG = "true";
AI_INTERNAL_ENABLED = "false";
AI_VISION_ENABLED = "false";
AI_TEXT_ENABLED = "false";
APP_URL = service.origin;
CORS_ORIGINS = builtins.toJSON [ service.origin ];
REDIS_URL = "redis://wardrowbe-redis:6379/0";
STORAGE_PATH = "/data/uploads";
LOG_LEVEL = "INFO";
};
environmentFiles = [ config.sops.templates."wardrowbe-backend.env".path ];
volumes = [ "wardrowbe-uploads:/data/uploads" ];
};
backendUnits = [
"podman-wardrowbe-backend.service"
"podman-wardrowbe-worker.service"
"podman-wardrowbe-image-worker.service"
];
containers = {
wardrowbe-postgres = {
image = images.postgres;
environment = {
POSTGRES_USER = "wardrobe";
POSTGRES_DB = "wardrobe";
};
environmentFiles = [ config.sops.templates."wardrowbe-postgres.env".path ];
volumes = [ "wardrowbe-postgres:/var/lib/postgresql/data" ];
podman.sdnotify = "healthy";
extraOptions = healthOptions ++ [
"--health-cmd=pg_isready -h 127.0.0.1 -U wardrobe -d wardrobe"
];
};
wardrowbe-redis = {
image = images.redis;
cmd = [
"redis-server"
"--appendonly"
"yes"
];
volumes = [ "wardrowbe-redis:/data" ];
podman.sdnotify = "healthy";
extraOptions = healthOptions ++ [ "--health-cmd=redis-cli ping" ];
};
wardrowbe-backend = backendContainer // {
dependsOn = [
"wardrowbe-postgres"
"wardrowbe-redis"
];
ports = [ "127.0.0.1:8002:8000" ];
cmd = [
"sh"
"-c"
"alembic upgrade head && exec uvicorn app.main:app --host 0.0.0.0 --port 8000"
];
podman.sdnotify = "healthy";
extraOptions = healthOptions ++ [
"--health-cmd=curl --fail --silent http://127.0.0.1:8000/api/v1/health"
];
};
wardrowbe-worker = backendContainer // {
dependsOn = [ "wardrowbe-backend" ];
cmd = [
"arq"
"app.workers.worker.WorkerSettings"
];
};
wardrowbe-image-worker = backendContainer // {
dependsOn = [ "wardrowbe-backend" ];
cmd = [
"arq"
"app.workers.image_worker.ImageWorkerSettings"
];
};
wardrowbe-frontend = {
image = images.frontend;
dependsOn = [ "wardrowbe-backend" ];
environment = {
BACKEND_URL = "http://wardrowbe-backend:8000";
NEXTAUTH_URL = service.origin;
DEV_MODE = "true";
};
environmentFiles = [ config.sops.templates."wardrowbe-frontend.env".path ];
ports = [ "127.0.0.1:3002:3000" ];
podman.sdnotify = "healthy";
extraOptions = healthOptions ++ [
"--health-cmd=node -e \"fetch('http://127.0.0.1:3000').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\""
];
};
wardrowbe-auth = {
image = images.auth;
environment = {
TINYAUTH_APPURL = auth.origin;
TINYAUTH_AUTH_SECURECOOKIE = "true";
TINYAUTH_AUTH_TRUSTEDPROXIES = "10.89.30.1";
TINYAUTH_LABELPROVIDER = "none";
TINYAUTH_ANALYTICS_ENABLED = "false";
TINYAUTH_DATABASE_PATH = "/data/tinyauth.db";
TINYAUTH_UI_TITLE = "Wardrobe login";
};
environmentFiles = [ config.sops.templates."wardrowbe-auth.env".path ];
volumes = [ "wardrowbe-auth:/data" ];
ports = [ "127.0.0.1:3003:3000" ];
podman.sdnotify = "healthy";
extraOptions = healthOptions ++ [ "--health-cmd=tinyauth healthcheck" ];
};
};
in
{
imports = [
nixosModules.caddy
nixosModules.sops
];
sops.secrets = {
wardrowbe-postgres-password = { };
wardrowbe-secret-key = { };
wardrowbe-nextauth-secret = { };
wardrowbe-auth-users = { };
};
sops.templates = {
"wardrowbe-postgres.env" = {
content = ''
POSTGRES_PASSWORD=${config.sops.placeholder.wardrowbe-postgres-password}
'';
restartUnits = [ "podman-wardrowbe-postgres.service" ];
};
"wardrowbe-backend.env" = {
content = ''
DATABASE_URL=postgresql+asyncpg://wardrobe:${config.sops.placeholder.wardrowbe-postgres-password}@wardrowbe-postgres:5432/wardrobe
SECRET_KEY=${config.sops.placeholder.wardrowbe-secret-key}
'';
restartUnits = backendUnits;
};
"wardrowbe-frontend.env" = {
content = ''
NEXTAUTH_SECRET=${config.sops.placeholder.wardrowbe-nextauth-secret}
'';
restartUnits = [ "podman-wardrowbe-frontend.service" ];
};
"wardrowbe-auth.env" = {
content = ''
TINYAUTH_AUTH_USERS=${config.sops.placeholder.wardrowbe-auth-users}
'';
restartUnits = [ "podman-wardrowbe-auth.service" ];
};
};
virtualisation.oci-containers = {
backend = "podman";
containers = lib.mapAttrs (
_: container:
{
networks = [ "wardrowbe" ];
}
// container
) containers;
};
systemd.services = {
wardrowbe-network = {
description = "Wardrowbe container network";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
${podman} network exists wardrowbe || \
${podman} network create --subnet 10.89.30.0/24 --gateway 10.89.30.1 wardrowbe
'';
};
}
// lib.mapAttrs' (
name: _:
lib.nameValuePair "podman-${name}" {
requires = [ "wardrowbe-network.service" ];
after = [ "wardrowbe-network.service" ];
serviceConfig.TimeoutStartSec = lib.mkForce 900;
}
) containers;
services.caddy.virtualHosts = {
${auth.domain}.extraConfig = "reverse_proxy 127.0.0.1:3003";
${service.domain}.extraConfig = ''
request_body {
max_size 50MB
}
route {
forward_auth 127.0.0.1:3003 {
uri /api/auth/caddy
}
handle /api/auth/* {
reverse_proxy 127.0.0.1:3002
}
handle /api/* {
reverse_proxy 127.0.0.1:8002
}
handle {
reverse_proxy 127.0.0.1:3002
}
}
'';
};
};
}
+1 -2
View File
@@ -92,8 +92,7 @@
in
{
# devenv already installs its bundled SecretSpec CLI in the workstation profile.
home.packages =
lib.remove pkgs.secretspec (import ./_support/tools.nix { inherit pkgs; }).packages;
home.packages = lib.remove pkgs.secretspec (import ./_support/tools.nix { inherit pkgs; }).packages;
programs.zsh.initContent = lib.mkAfter ''
workbench-reload() {
if [[ "''${DEVENV_ROOT:-}" != ${lib.escapeShellArg root} ]]; then
+8
View File
@@ -235,6 +235,14 @@ in
scheme = "https";
domain = "vault.jelles.net";
};
wardrowbe = {
scheme = "https";
domain = "wardrobe.jelles.net";
};
wardrowbe-auth = {
scheme = "https";
domain = "wardrobe-auth.jelles.net";
};
};
externalDevices.pixel-10.syncthingId = "MTJHEHA-UMZDQZ7-BTMRRLQ-Y7BFPUJ-ZTY6LZX-PDXV3IS-XVJCU7B-EPETBQZ";
+4 -2
View File
@@ -10,7 +10,7 @@ in
stateVersion = "24.05";
};
flake.modules.nixos.orion = {
flake.modules.nixos.orion = { pkgs, ... }: {
imports = [
nixosModules.host-base
@@ -24,11 +24,13 @@ in
nixosModules.actual
nixosModules.gitea
nixosModules.transcription
nixosModules.wardrowbe
./_hardware.nix
./_disk.nix
];
environment.enableAllTerminfo = true;
# SSH clients on both workstations use Kitty.
environment.systemPackages = [ pkgs.kitty.terminfo ];
users.users.${config.facts.account.name}.linger = true;
};
+7 -3
View File
@@ -5,6 +5,10 @@ orion-ip: ENC[AES256_GCM,data:S6fpCWnD8dvchvrHlEo=,iv:72+oRxHUEJ7imJ+sWjGbG+TUrS
hashed-password: ENC[AES256_GCM,data:mhYvALF/VrPMFFYEIGiML1MoRxdOI/J9akZPD+OF5drDTR4J5LBn3EejlbzP8CTCRsyoIF6tigMBxF8ZLz9W7hOVwT70Hr/yXfLWJ2lfBsf7l/SPZ+6NglKppCeQQktm4QKIHTVj7CkbRw==,iv:0RxXkb+vz/QK9dvecCZpWCWCsoPhi2F3OCt6CwUDqX4=,tag:j7QO8xUfWUnSpk4mMQUYmA==,type:str]
gemini-api-key-neovim: ENC[AES256_GCM,data:B8FeFt45FsU3aagyLDKXiwmx0mRrsw4C8RQ3EWXwZ+YfWLMvwJad,iv:1HqBD6vc07Ke/PMYXfHqFrWDGw/UMjiiBjLRN33/xHI=,tag:czcrYGbJFi41rYtIPM4qTQ==,type:str]
booklore-db-pass: ENC[AES256_GCM,data:dlPGXQ24itEaBRJSJ9WOogWCdF3atFQ2ZtlLGyGq8Tin5OmSZI6lZUzSE+femBW5SBTIlKQvzHEPCs9MT5tyMIqetzGLm+mMN3FDW7si684Cuv9z9Uq5gjAZWh14KQMWYPI=,iv:oLnqu2EDFBVcBpswVRXXeF617YolPxOUx9CscHRRn/8=,tag:Si6gF1EXhcHalk11D3Exlw==,type:str]
wardrowbe-auth-users: ENC[AES256_GCM,data:0padDqNkWA9p6GXaX3bpyH78tEeOndlX6/Gm0OzsfecnPkJmBJUQZr/328+51Q4eWPRflg4NRm04nejrqohKGcs=,iv:VJQD/ko4B18PbJh0gWdfQJ3xlGeHW4oBlho4e3Bkhk8=,tag:iE7gfUi1khmwIJA/3L8zvA==,type:str]
wardrowbe-postgres-password: ENC[AES256_GCM,data:3t+py1nmtzbdFFMbBtQXQKttRj1MQrz+wcfjQGPYahOaQTIDu1Pzk3KJf+LANCTftpvrwO6N92K0b5q0+1MEzw==,iv:o9Hiew2vx6OinLBOKgPyx0K+WVTAvPopcEJoLtYCZTY=,tag:xi0wueITInYXwvVaLusqyw==,type:str]
wardrowbe-secret-key: ENC[AES256_GCM,data:c9++BrtGsAFHML3gUG/E319jg9kxixH8RfZqs9pymxjYEFJsclaiWmknqeel4luIAuD5ufvDRzBLqxaBSE6rhQ==,iv:YntoeASLdE+lbU+15eIaR4SqWU3PzaWH8MJFlW1FwaM=,tag:aZhDTPc3qbNTZ8N9lvCJgA==,type:str]
wardrowbe-nextauth-secret: ENC[AES256_GCM,data:3UZm0L+0q7C6OVYfCyIghzK80dPKOOz9BLfFCHctD2oMckPl0yfL5S1NqsXULycx+ctwWPV7SQj7/AiD6cyw1w==,iv:SocQDS2GbQ6ZWifmwb7AJC7SNkAMjJWetlTM9TfUC3Q=,tag:WuXajPY4e4zF5T6zC7okTQ==,type:str]
sops:
age:
- enc: |
@@ -25,7 +29,7 @@ sops:
sWu/+CcQDpUu4tAeBSCUZ/BV2nTvfb94DZGK2dWUFniJaZit5tRZlg==
-----END AGE ENCRYPTED FILE-----
recipient: age1upsxyevxdv3xax8jhnffclryuve70k63cjrca69ffds4h2m3nvuql59ufw
lastmodified: "2026-08-08T14:56:00Z"
mac: ENC[AES256_GCM,data:pXcvH73xNMSkOGytdhJ4Qu1pH8d3qS/FkPfGvbkzQoNLKRlLiArJExPYIkEtFdr7mDhYer91wPtkJu2HyyxCVPkPp8EFaeVdGuFufLQruQjP2KRfR841hohVW4KN53nh1AJT22CW2LTqgS0BnTBrd6wUQWvBHdek99eh8vwgurI=,iv:d8I+vKIgfirDvkHXVSNWJrKUO5vACkbk3VSN8TXSE+Q=,tag:itzAvtD+aBFmDilxZBepLw==,type:str]
lastmodified: "2026-10-10T13:58:26Z"
mac: ENC[AES256_GCM,data:S21qc5jLIoRrR28yiNmQHAU9soV74haGAbYXxsut0PqeRAMzUj4RSdBiR7Mja84r+w148Gx4GqTrPLhVxfj+78lyWcV3MWYbf9rr4tbxUYbhlJPRwVPdeP/JEc4rhPzX7QnUcrlym2zOhumJ/RoUHWW2yUEC/7DmzCqMXH1Kx5U=,iv:p7lwWl76cvELLhMlOdnxFdYdoIt/tVwxL/ihR/T3d7Y=,tag:DWsJYfaIZ2yZqXPktl9V1g==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.2
version: 3.13.3