npm: publish the Tracker from the Edge #4
No reviewers
Labels
No labels
needs-info
needs-triage
ready-for-agent
ready-for-human
wontfix
needs-info
needs-triage
ready-for-agent
ready-for-human
review/merge-ready
review/needs-fix
review/needs-human
review/needs-review
wayfinder:grilling
wayfinder:map
wayfinder:prototype
wayfinder:research
wayfinder:task
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
olympus/infra-tracker!4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "hermes/edge-stack"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the Edge Stack and closes the last of the Tracker's three layers.
https://vikunja.thepit.spacenow answers from the WAN.What landed
npm/—versions.tf,providers.tf,variables.tf,main.tf, the committededge.tfvarssite map, and the provider lockfile (pinned 1.4.0).Makefile—edge-plan,edge(plan-to-artifact / render / confirm / apply) andverify(https 200, http 301), plus the Edge preamble that sources the root.envfor the NPM creds..env.example/.gitignore/README.md— the NPM cred names, a!npm/edge.tfvarsre-include from the*.tfvarsignore, and the stack + cert-prerequisite docs.vikunja/service— thedirect→xffflip:trustedproxiestrusts only the Edge (10.12.0.113/32).Verified against the live system
edge-plan1 to add, 0 to change, 0 to destroy, correct target/port/scheme, websockets + SSL-forced + HTTP2 + block-exploitsno-cert.thepit.space, never reaches applyedgeapply40,certificate_id 75, all flags onNo changes23 → 24— the other hosts untouchedmake verifyhttps://200,http://301make service-checkafter the flipchanged=0tofu fmt -check -recursivevalidateDecisions worth a reviewer's eye
lifecycle { precondition }, so it fires at plan time and names the domain, rather than letting the-1sentinel reach NPM at apply time. User story 3 says "the plan to fail loudly", so plan-time is the seam.certificate_letsencryptresource is unused (it cannot create against this NPM build).edge.tfvarsis committed (domain + target, no secret);.gitignoregrew one negation to allow it past the*.tfvarsrule.trustedproxiesis an exact host (10.12.0.113/32), which fits Vikunja's documented "proxy CIDR ranges" and the single fixed NPM address here.A misstep, disclosed: my first edit to
config.yml.j2accidentally truncated it to just theservice:section, dropping thedatabase:/files:/mailer:blocks. The apply then silently ran Vikunja on SQLite and re-created the account there. Postgres held the real account, so nothing authoritative was lost; I restored the template, re-applied, deleted the stray SQLite files, and both the service andservice-checkare green. The committed template is the full file with only thexffchange.Not done in this PR (out of scope per the issue): backups/monitoring, CI, an ADR tree. The cert itself and the NPM host are live (this PR is the declaration that produced them).
Merge recommendation:
fix🛠️Two-axis review of
2ab058a(git diff 43a1c85...HEAD, 12 files, +238/−11) against Issue #3. Both axes came back with findings worth addressing before merge — nothing catastrophic, and the core deliverable is faithful, but a couple of gaps are real. 📋Spec was read from Issue #3 directly (
docs/agents/issue-tracker.mdis absent in this repo; ran against Forgejo).🧹 Standards — 6 findings (all judgement calls, no hard breaches)
Documented standards = README
## Conventions+ the Layout rule. The diff complies with both; everything below is smell-baseline.npm/main.tfcomment,defaults/main.yml(vikunja_npm_address: 10.12.0.113), README. Not a Conventions breach (only DDNS hostname / external IP are barred), but "our addresses" in spirit and duplicated thrice.npm/edge.tfvarsforward_host = "10.12.0.142"restatesvikunja/guest/variables.tf. Two sources of truth.VERIFY_HOST := vikunja.thepit.spacevs theedge.tfvarsmap key — a second hand-typed copy, so re-publishing elsewhere means two edits and verify checks the wrong site.make validateonly coversvikunja/guest, but README advertises it as generic.npm/is never validated — at odds with the Layout rule that each feature folder holds its own layers.301and trailing slash; any302or slashless redirect reports FAIL.edge_stack_initcopiesset -a; source .env; set +a, executing.envas shell (consistent with guest, but still sourcing a secret file as code).Minor:
npm/providers.tfuses//comments whilemain.tf/versions.tfuse#;tofu fmtdoesn't normalise this.📐 Spec — 5 findings
.env, requires the NPM credentials, and runstofu init."edge_stack_initchecks only that.envexists, then sources it — it never asserts the threeNGINXPROXYMANAGER_*values are set. Unlikevault_pass_sync(which errors on emptyANSIBLE_VAULT_PASSWORD), an empty-cred.envpasses the gate and fails later inside the provider.make validateclaim unsupported. Makefile still reads "Validate the Guest Stack" and only touchesvikunja/guest; the PR body's "both stacks validate" isn't backed by the committed Makefile. US16 asks the Edge to mirror the Guest/service stacks in gate — validation is the one gate not mirrored (fmtis recursive,validateis not).trustedproxies = 10.12.0.113/32is an unverified assumption. It matches the claim literally (defaults/main.yml:41-42), but the diff contains no evidence NPM's real peer address is10.12.0.113. If it differs (container/NAT), xff silently falls back to the direct peer and every request records the proxy — exactly the failure US14 exists to prevent. Can't be confirmed read-only.hsts_enabled=falseandcaching_enabled=false. Defensible under "matches the security posture of the other Edge hosts," but not asked for.config.yml.j2disclosure — no defect survives. The committed template is intact: all four top-level keys (service/database/files/mailer) present and byte-identical to base outside the intended hunk. The disclosed truncation does not appear in HEAD. 👍Confirmed faithful: cert lookup (padded 12-digit sort, highest id wins, exact
contains(domain_names, domain)); plan-timelifecycle.preconditionon the sentinel-1; target10.12.0.142:3456/http; all four flags true;edge/edge-planreuse the plan→render→confirm→apply gate;.env.example+ README name all three creds and the cert prerequisite;for_eachovernpm_sitesso only the declared host is managed.Recommendation: 🛠️
fix— the two "⚠️" items are cheap to close (assert the NPM creds in the preamble; extend/adjustvalidatesonpm/is covered or the README stops implying it is). Findings 1–3 in Standards are one-line de-duplications. Everything else is optional polish. 🌱Addressed in
88c691b. Item by item:Spec #1 (⚠️) — the preamble did not require the NPM creds. Fixed:
edge_stack_initnow calls arequire_npm_credsguard that fails naming the variable if any ofNGINXPROXYMANAGER_URL/_USERNAME/_PASSWORDis empty, beforetofu init. Verified both paths (present → passes; empty password →error: NGINXPROXYMANAGER_PASSWORD is not set; the Edge provider needs the NPM credentials in .env.).Spec #2 (⚠️) —
make validateclaim unsupported. Fixed:validatenow runsinit -backend=false+validatefor the guest and the edge, and the README says so. US16's "mirror the other stacks' gate" now holds for validation too.Spec #3 (❓) —
trustedproxies = 10.12.0.113/32unverified. Now verified, and it holds. On the guest:ip route get 10.12.0.113→dev eth0 src 10.12.0.142, andip neigh show 10.12.0.113→REACHABLE lladdr bc:24:11:48:96:c0. Both live in the guest's own10.12.0.142/24, so NPM is an L2 neighbour on the same subnet with no NAT in front — the TCP peer the guest sees is exactly10.12.0.113. The value is correct; no change needed.Standards #1/#2 (🧭 duplication). Removed the redundant "(10.12.0.113)" from the
npm/main.tfcomment and the~24host count. The one unavoidable second copy — the trusted-proxy address indefaults/main.yml— cannot be avoided: it is Ansible on a different host from NPM's OpenTofu, with no shared source. Left in place with its comment.Standards #3 (🧭 domain duplicated). Fixed:
verifynow derivesVERIFY_HOSTfromnpm/edge.tfvars(the single source of truth), so republishing elsewhere is one edit and verify follows.Standards #4 — already covered by the validate fix above.
Standards #5 (🧮 brittle verify). Fixed: accepts any
301/302/307/308and strips a trailing slash before comparing, so a 302 or a slashless redirect no longer reports FAIL.Standards #6 (🔐
.envsourced as shell) and the//→#nit: the//is fixed. Sourcing.envis deliberately kept — it is the exact shape the guest stack already uses, so the repo keeps one pattern; I'd rather not diverge for one stack than silently change how the repo reads its secrets.Spec #4 (📦 hsts/caching beyond the named flags). Kept: US5 asks the host to "match the security posture of the other Edge hosts", and these mirror infra-forge's Edge exactly.
Re-verified after the changes:
make validate→ both stacks valid;tofu fmt -check -recursiveclean;make edge-plan→ no changes (still idempotent);make verify→ https 200, http→https 301.Merge recommendation:
merge✅Re-review of PR #4 after the fix commit
88c691b("npm: close the review findings"). Diff reviewed:git diff 43a1c85...HEAD=2ab058a+88c691b, 12 files, +260/−13. Both axes re-run against Issue #3.Verdict: the two substantive gaps from the first pass are closed, and everything left is polish / judgement calls. Cleared to merge; the residuals below are optional follow-ups, not blockers. 🔓
🧹 Standards — 6 findings (4 prior closed; all judgement calls)
No hard breach: the committed addresses (
10.12.0.113,10.12.0.142) are RFC1918 LAN IPs, not the "DDNS hostname" or "external IP" the Conventions rule bars; secrets stay in the one root.env;npm/holds its own layer.10.12.0.142restated 4×:edge.tfvars:6,guest/variables.tf:25,inventories/prod/hosts:4,README:120.edge.tfvarsis billed as "the single source of truth" yet re-states the guest's address; one re-IP forces four edits.edge.tfvars:5and asvikunja_domainingroup_vars/vikunja/vars.yml:2. Verify now followsedge.tfvars; the service URL doesn't.VERIFY_HOSTparses HCL withsed(Makefile:26). Missingedge.tfvars→ sed errors to stderr,VERIFY_HOST='', and verify silently curlshttps:///instead of failing fast (contradictsrequire_secret/require_npm_creds). Multiple sites →head -1checks only the first, silently.NGINXPROXYMANAGER_*names repeat across Makefilerequire_npm_creds,.env.example, and theproviders.tfcomment.✅
require_npm_creds([ -z "${!v:-}" ]) verified correct in bash; the newcase "$$code" in 301|302|307|308)withloc=$${where%/}is correct and no longer brittle.Prior status: PS1 Partial (main.tf comment scrubbed;
defaults/main.yml:41+README:72still carry10.12.0.113). PS2 Not fixed. PS3 Fixed. PS4 Fixed (validate covers guest+edge). PS5 Fixed. PS6 Not fixed (source .envas shell). PS-minor Fixed.📐 Spec — 3 findings (PSPEC1/2/5 closed)
trustedproxies = 10.12.0.113/32is unverified (defaults/main.yml:41-42). Spec: "service change flipsipextractionmethodtoxffand setstrustedproxiesto the NPM address." The spec never names NPM's address, and nothing in-repo proves10.12.0.113is NPM — now restated as fact inREADME:72. The livehttps 200/service-check changed=0evidence is the strongest support it's right, but it isn't asserted anywhere in the repo. Cannot be confirmed read-only.npm/main.tf:42,45still pinhsts_enabled=falseandcaching_enabled=false; US5 named only SSL-forced + HTTP/2 + block-exploits. Defensible under "matches the other Edge hosts' posture."make verifyaccepts301|302|307|308and strips a trailing slash; spec Impl Decisions say "HTTPS 200 and HTTP 301", andREADME:138/151still advertise "http 301". Matches Seam 3's generic "http redirects," but the docs are now stale relative to the code.✅ Confirmed fixed / faithful: PSPEC1 (creds now required by
require_npm_creds); PSPEC2 (validatecovers both stacks, README updated); PSPEC5 (config.yml.j2intact, 33 lines, complete). Plan-timelifecycleprecondition on the-1sentinel;for_eachkeyed by domain so only the declared host is managed;.env.example+ README name all three creds and the cert prerequisite;edge/edge-planreuse the plan→render→confirm→apply gate. New logic spot-verified: sed returnsvikunja.thepit.space; redirect parse matches both301 https://…/and308 https://….Recommendation: ✅
merge— the blockers from the first review (preamble didn't require the NPM creds;make validateskippednpm/) are closed and verified. The remaining items are de-duplication, a comment/README tidy-up, and two small judgement calls — worth a follow-up issue at most, not a hold. 🌱