Skip to content

feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0062 (#142) - #733

Merged
Gerrrt merged 8 commits into
mainfrom
gerrrt/actual-budget-sensitive-tier-f7f412
Sep 29, 2026
Merged

Gerrrt merged 8 commits into
mainfrom
gerrrt/actual-budget-sensitive-tier-f7f412

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

What changed

This adds Actual, the household's budget, to stacks/sensitive on trinity, behind Caddy at actual.matrix.elysium, and records the decision as ADR-0062. The service is authored and validated here; deploying it on trinity comes afterwards and is listed at the end.

Actual has no password setting: a new server is claimed by whoever sets the first password. The new scripts/seed-actual-password.sh claims it from SOPS (ACTUAL_SERVER_PASSWORD), inside the pinned image and with no network, before the service's first start. make up runs it every time, and once the server is claimed it only checks that the SOPS password still logs in.

Also in this PR:

  • The Caddy site block and alias.
  • actual-data joins the nightly backup set and the restore uid table.
  • The restore runbook gains Actual checks.
  • A link-only Homepage tile.
  • A CI job that boots Actual under its hardening.
  • Pointer notes on ADR-0022 (no second factor) and ADR-0023 (class Durable).
  • Runbooks, roadmap and changelog.

Why

Closes out the decision half of #142. Miniflux (#147, ADR-0057), Memos (#145, ADR-0059), Mealie (#146, ADR-0060) and linkding (#144, ADR-0061) landed first, and HomeBox's decline took ADR-0058, so Actual is the fifth tier extra and its record is ADR-0062. The issue sits under Tier extras, and the roadmap requires each of those to have its own decision before any of it is written. ADR-0062 is that decision:

  • Actual rather than Firefly III.
  • Password login only.
  • No bank sync.
  • The server is claimed before it can answer anyone.

Blast radius

This affects trinity (VLAN 99) only, and adds one container, one volume and one name. Nothing is deployed by merging.

  • No change to network segmentation or firewall rules. Hicks reaches it through Caddy's 443 under the existing 50→99 rule.
  • No new port published to a VLAN that could not already reach the service. It uses expose: 5006 only.
  • No credential added outside secrets/*.sops.yaml. ACTUAL_SERVER_PASSWORD is read from SOPS by the seed script; it is never in the container's environment or in .env.

Known limit (ADR-0062): in password mode every device shares one login session, and changing the password does not sign anyone out. The stack README has a tested command that does.

Verification

Measured on actualbudget/actual-server:26.9.0-alpine, pinned by digest, on trinity:

  • It runs as uid 1001, read-only, with no capabilities, and docker diff is empty.
  • It boots and answers /health with --network none.
  • account.sqlite is in rollback-journal mode.
  • Memory: 279 MiB idle, 329 MiB peak.
  • A sixth failed login within 15 minutes gets 429.
  • A header login is refused.

The seed script was tested on throwaway volumes in four cases, and each gave the expected result:

  • new volume → claimed
  • already claimed → left alone
  • live check → ok
  • wrong password → warning, and --check exits 1

Session revocation was tested: after deleting sessions, the old token gets 401.

Actual also booted from the real compose.yaml on an internal network with a spare subnet (the live stack holds the fixed one): healthy, read-only, CapDrop=[ALL], user 1001:1001. CI's hardened-boot job runs it on the real subnet.

  • make validate passes. shellcheck was run separately in Docker, because it isn't installed on trinity.

  • Deployed to the lab and confirmed working. Not yet; the steps are:

    1. Put ACTUAL_SERVER_PASSWORD in SOPS on trinity, and a copy in the password manager.
    2. Add the actual host override on morpheus.
    3. Run make up STACK=sensitive.

    No real budget goes in before Build the sensitive-tier host on the ProDesk (ADR-0034) #404 step 10.

  • Docs updated (docs/, service README, or docs/roadmap.md)

Refs #142

🤖 Generated with Claude Code

Gerrrt and others added 2 commits September 29, 2026 14:50
…ded by ADR-0057 (#142)

Actual joins stacks/sensitive as the household's budget. It is the first
service beyond ADR-0008's nine, so ADR-0057 decides it: Actual rather
than Firefly III, because it is one container on SQLite and keeps
working when the server is down. It is authored and validated here.
Deploying it on trinity is the remaining step.

Actual has no password setting. A fresh server belongs to whoever sets
the first password, and the image's reset script needs a terminal.
scripts/seed-actual-password.sh claims it from SOPS (ACTUAL_SERVER_PASSWORD),
inside the pinned image with --network none, before its first start;
`make up` runs it every time, and on a claimed server it only checks.
The value never enters the container's environment or .env. Proved on
throwaway volumes: fresh, already claimed, a live check, a wrong password.

Measured on 26.9.0-alpine:
- runs as the image's own uid 1001, read-only, no capabilities, and
  docker diff is empty
- boots with no network
- account.sqlite is in rollback-journal mode
- 279 MiB idle, 329 MiB peak
- five failed logins per client per 15 min
- header login is refused

In password mode every device shares one session token, and a changed
password does not revoke it. Deleting the sessions rows with the
service stopped does; the stack README has the command, proved the
same way.

Also:
- actual-data joins the nightly set (sentinel
  ./server-files/account.sqlite) and restore-volumes.sh's uid table
- the restore runbook checks the claim
- password login only, with Caddy as the one trusted proxy
- no bank sync
- a link-only Homepage tile
- a CI hardened boot
- ADR-0023 class Durable; ADR-0022 no second factor, as pointer notes

Refs #142

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0058

Miniflux (#147) landed first as the first tier extra, so Actual is the
second: ADR-0057 → ADR-0058 in every Actual reference, and "first beyond
ADR-0008's nine" becomes "second, after Miniflux". Both services kept in
every conflicted file (compose, Caddyfile, secrets template, README,
runbooks, security.md, ADR-0022's note, roadmap, changelog, restore uid
table).

Counts corrected for both services, including ones main left stale:
- 17 services and 18 secrets
- 10 names, 9 of them host overrides
- 15 volumes, 12 archived, 7 not rebuildable
- 58 ADRs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gerrrt Gerrrt changed the title feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0057 (#142) feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0058 (#142) Sep 29, 2026
…R-0059

Actual's ADR moves again, 0058 → 0059, in every Actual reference; HomeBox's
own ADR-0058 references are untouched. The changelog keeps both entries,
Actual's first. README's count is 59 ADRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gerrrt Gerrrt changed the title feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0058 (#142) feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0059 (#142) Sep 29, 2026
@Gerrrt
Gerrrt enabled auto-merge (squash) September 29, 2026 16:19
Gerrrt and others added 3 commits September 29, 2026 16:23
Memos (#145) landed as the second tier extra, so Actual is the third,
and its record moves to ADR-0060. Every Actual reference is renumbered;
Memos' own ADR-0059 references are untouched. Both services are kept in
every conflicted file: the Homepage tiles, the README, the restore
checks (Actual's is now step 8), the host-override names, and the notes
in ADR-0022 and security.md.

This merge also brings in #734, which gives Miniflux's Postgres its own
cluster directory. With it, the sensitive backup inventory passes again.

Counts recounted from the files:
- 18 services, 18 secrets
- 11 names, 10 of them host overrides
- 16 volumes, 13 archived, 8 not rebuildable
- 60 ADRs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… extra

Mealie (#146, ADR-0060) landed as the third tier extra. Actual is now
the fourth and the eleventh service in the compose header; its record
was renumbered to ADR-0061 in the commit before this. Both services are
kept in every conflicted file: the Caddy blocks and aliases, the backup
and restore tables, the README, and the notes in ADR-0022 and
security.md. Actual's restore check is now step 9.

Counts recounted from the files:
- 19 services, 18 secrets
- 12 names, 11 of them host overrides
- 17 volumes, 14 archived
- 61 ADRs

The sensitive backup inventory passes, and so does make validate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gerrrt Gerrrt changed the title feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0059 (#142) feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0061 (#142) Sep 29, 2026
Gerrrt and others added 2 commits September 29, 2026 16:31
…142)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r extra

linkding (#144, ADR-0061) landed as the fourth tier extra. Actual is now
the fifth and the twelfth service in the compose header, and its record
was renumbered to ADR-0062 in the commit before this. Both services are
kept in every conflicted file, including the CI boot jobs, the Caddy
blocks and aliases, the secrets template, the backup and restore tables,
the README, and the notes in ADR-0022 and security.md.

Counts recounted from the files:
- 20 services, 19 secrets
- 13 names, 12 of them host overrides
- 18 volumes, 15 archived, 9 not rebuildable
- 62 ADRs

make validate passes. The sensitive backup inventory does not: on main,
linkding-data and vaultwarden-data share the sentinel ./db.sqlite3. That
came in with #731 and is left for its own fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gerrrt Gerrrt changed the title feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0061 (#142) feat(sensitive): Actual behind Caddy, claimed before it answers, decided by ADR-0062 (#142) Sep 29, 2026
@Gerrrt
Gerrrt merged commit d5a12ef into main Sep 29, 2026
6 checks passed
@Gerrrt
Gerrrt deleted the gerrrt/actual-budget-sensitive-tier-f7f412 branch September 29, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant