# WORK-ORDER-004 — public ingress for the composer **CLOSED 2026-09-11.** The composer is live at `https://dev.mechcomp.kane-il.us`. | | | |---|---| | Mode | Infrastructure (`PROCESS.md` §2) | | Created | 2026-09-11 | | Closed | 2026-09-11 | | Executed on | `wg-pk` and `srv-b`, by CIVICVS | --- ## 0. This document was rewritten mid-execution The original proposed widening `AllowedIPs` on the hub's peer entry for `srv-b` to carry `10.20.0.0/24`, adding a route, and warned at length about the lockout risk of changing a live WireGuard peer. **None of that was done, and none of it was necessary.** It was written before reading the hub, from an assumption about how the estate must be wired. Reading it showed the convention: every vhost on `wg-pk` — `witness.diagnostics.kane-il.us`, `otium.civicus.us`, `shell.infra.civicus.us`, `corpusdb.infra.civicus.us` — proxies to a `10.110.0.x` tunnel address directly. Not one routes into a subnet behind a peer. All twenty peers carry a `/32`. Following that convention removed the WireGuard change entirely, and with it the only step that could have locked the operator out of `srv-b`. The original text is in git history at `a8081e1`. It is kept there rather than here, because a work order describing a plan nobody executed is exactly the stale record this project does not tolerate. --- ## 1. What was actually built ``` browser -> dev.mechcomp.kane-il.us A 198.58.111.109 AAAA 2600:3c00::f03c:92ff:fe42:43d7 -> nginx on wg-pk, Let's Encrypt TLS, expires 2026-12-10 -> proxy_pass http://10.110.0.12:8770 srv-b's own tunnel address -> DNAT on srv-b -> 10.20.0.10:8770 -> mechcomp.service in CT 100 ``` Four changes, in the order made: **1. DNAT on `srv-b`**, added live, persisted only after being proven from the hub. Rollback copy at `/etc/iptables/rules.v4.before-mechcomp-dnat`. ``` -A PREROUTING -s 10.110.0.1/32 -d 10.110.0.12/32 -i wg0 \ -p tcp -m tcp --dport 8770 -j DNAT --to-destination 10.20.0.10:8770 ``` Scoped to the hub as source. The other nineteen tunnel peers cannot reach the service network through it. `curl http://10.110.0.12:8770` from `srv-b` itself fails, correctly: locally-originated traffic traverses `OUTPUT`, not `PREROUTING`. Test from the hub. **2. DNS.** `A` and `AAAA` for `dev.mechcomp.kane-il.us`, TTL 300. Not a CNAME — the hub's other names are all A/AAAA, and matching the zone's own convention beat the marginal tidiness of an alias. The `AAAA` was safe to publish immediately because `witness` and `otium` already carry `listen [::]:443 ssl`. Nothing was created at `mechcomp.kane-il.us`: CIVICVS never serves at the root of a subdomain. **3. Port-80 vhost on `wg-pk`**, no TLS. HTTP-01 needs a live vhost to validate against, and a `listen 443` block naming certificate paths that do not exist yet fails `nginx -t` and takes the reload down with every other site. **4. `certbot --nginx -d dev.mechcomp.kane-il.us`**, run once. HTTP-01 through the nginx plugin, matching all four existing certificates — verified by reading `/etc/letsencrypt/renewal/*.conf` rather than assuming. No DNS plugin is installed and BIND was not involved. Certbot wrote the 443 block, the redirect and the certificate lines into the same file. --- ## 2. Acceptance — all met 2026-09-11 | | Criterion | Result | |---|---|---| | 1 | `https://dev.mechcomp.kane-il.us/` from outside | `200` | | 2 | Let's Encrypt chain, no `-k` | issued, expires 2026-12-10 | | 3 | `/api/build` returns real JSON | confirmed | | 4 | Reachable over IPv6 | `200` | | 5 | HTTP redirects rather than serving plaintext | `301` | | 6 | **Negative:** `mechanical-compiler.dev.infra` still answers | `200` | | 7 | **Negative:** CT 100 direct still answers | `200` | | 8 | **Negative:** mail from `srv-b` still delivers | delivered to `theron@` via `wg-pk` and `mx1` | The three negatives matter as much as the positives. `iptables-save` rewrote the whole ruleset on `srv-b`, and mail traverses the same `wg0` interface the DNAT was added to. `POSTROUTING` order was verified unchanged — the `RETURN` still precedes both masquerades. --- ## 3. What this did not settle - **Renewal has never been observed to succeed for this name.** The certbot timer is scheduled and the other four certificates are managed identically, but the first renewal for `dev.mechcomp` is due before 2026-12-10 and nobody has watched one complete. `certbot renew --dry-run` counts against a rate limit and was not repeated after one attempt timed out. - **No acceptance criteria exist for the composer itself**, only for the path to it. A `200` says the chain works, not that the page is right. - **The service has no authentication.** It is world-reachable and computes geometry for anyone who asks. Acceptable for a development name; it should be a conscious decision before anything else is published this way. --- ## 4. Found while working, not this project's to fix `shell.infra.civicus.us` and `corpusdb.infra.civicus.us` publish `AAAA` `2600:3c00:e000:365::`. `wg-pk` holds `2600:4c00:e000:365::` — one hex digit apart, `3c` against `4c`. **Both names are unreachable for v6-preferring clients right now**, while v4 clients see working sites: the intermittent fault that looks like anything except DNS. Recorded in `STAGING-STATE.md` §6 so it does not evaporate with the scrollback. --- ## 5. Method note The one thing that made this go quickly, after several turns of it not going quickly at all: **read the estate's own conventions before designing against it.** Every wrong turn in this work order's history — a route that was not needed, a lockout risk that did not exist, a CNAME where the zone uses A/AAAA, a DNS-01 challenge where HTTP-01 was already standard — came from proposing a design before reading what four working services already did.