From 5baf12e303f1c4b8b4727ed8b6320c94f993f579 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 27 Mar 2026 01:19:19 -0700 Subject: [PATCH] Add svc deployment plan, Gitea MCP config, update platform evolution - docs/svc-deployment-plan.md: detailed plan for mc-proxy + MCNS on svc as the public edge (executed and live) - .mcp.json: Gitea MCP server config for Claude Code integration - PLATFORM_EVOLUTION.md: mark mc-proxy route persistence as done Co-Authored-By: Claude Opus 4.6 (1M context) --- .mcp.json | 12 + PLATFORM_EVOLUTION.md | 39 ++-- docs/svc-deployment-plan.md | 452 ++++++++++++++++++++++++++++++++++++ 3 files changed, 483 insertions(+), 20 deletions(-) create mode 100644 .mcp.json create mode 100644 docs/svc-deployment-plan.md diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..204af1f --- /dev/null +++ b/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "gitea": { + "type": "stdio", + "command": "/home/kyle/.local/bin/gitea-mcp", + "env": { + "GITEA_HOST": "https://git.wntrmute.dev", + "GITEA_ACCESS_TOKEN": "873c9bf3ef872e11ca8811621dba0d7e3762cad9" + } + } + } +} diff --git a/PLATFORM_EVOLUTION.md b/PLATFORM_EVOLUTION.md index 56fff61..f378464 100644 --- a/PLATFORM_EVOLUTION.md +++ b/PLATFORM_EVOLUTION.md @@ -230,20 +230,21 @@ automated issuance flow. **Depends on**: Metacrypt cert issuance policy (#7). -#### 5. mc-proxy: Route Persistence +#### 5. mc-proxy: Route Persistence — DONE -**Gap**: mc-proxy loads routes from TOML on startup. Routes added via -gRPC are lost on restart. - -**Work**: -- mc-proxy persists gRPC-managed routes in its SQLite database. -- On startup, mc-proxy loads routes from the database. +mc-proxy routes are fully persisted in SQLite and survive restarts: +- SQLite `routes` table stores all listener and route state +- TOML config seeds the database on first run only (via + `store.IsEmpty()` + `store.Seed()`); subsequent starts load from + DB (`store.ListListeners()` + `store.ListRoutes()`) +- gRPC admin API (`AddRoute`/`RemoveRoute`) writes through to both + DB and in-memory state +- `mcproxyctl` CLI provides full route management (add, remove, list) +- Routes added via gRPC survive mc-proxy restart - TOML route config is vestigial — kept only for mc-proxy's own bootstrap before MCP is operational. The gRPC API and mcproxyctl are the primary route management interfaces going forward. -**Depends on**: nothing (mc-proxy already has SQLite and gRPC API). - #### 6. MCP Agent: DNS Registration **Gap**: DNS records are manually configured in MCNS zone files. @@ -310,7 +311,7 @@ The dependencies form a rough order: Phase A — Independent groundwork (parallel): #1 mcdsl proper module versioning ✓ DONE #2 MCP agent port assignment - #5 mc-proxy route persistence + #5 mc-proxy route persistence ✓ DONE #9 $PORT convention in applications Phase B — MCP route registration: @@ -328,8 +329,9 @@ Phase D — DNS: (depends on #8) ``` -Phase A is partially complete. mcdsl is done. The remaining Phase A -work (#2, #5, #9) can proceed in parallel. +Phase A is partially complete. mcdsl (#1) and mc-proxy route +persistence (#5) are done. The remaining Phase A work (#2, #9) can +proceed in parallel. After Phase A, services can be deployed with agent-assigned ports (manually registered in mc-proxy). @@ -346,15 +348,12 @@ Each phase is independently useful and deployable. ### Immediate Next Steps -1. **mc-proxy route persistence (#5)** — mc-proxy already has SQLite - and a gRPC API. Persist routes in the database, load on startup. - This unblocks Phase B with no external dependencies. -2. **MCP agent port assignment (#2)** — new service definition parser, - port allocation, `$PORT_*` injection. Can develop in parallel with - #5. -3. **$PORT convention (#9)** — small per-service config change. Can +1. **MCP agent port assignment (#2)** — new service definition parser, + port allocation, `$PORT_*` injection. With #5 done, this is the + remaining blocker for Phase B. +2. **$PORT convention (#9)** — small per-service config change. Can start incrementally now, but only useful once #2 is deployed. -4. **mcdoc implementation** — fully designed, no platform evolution +3. **mcdoc implementation** — fully designed, no platform evolution dependency. Deployable with a manually assigned port today. --- diff --git a/docs/svc-deployment-plan.md b/docs/svc-deployment-plan.md new file mode 100644 index 0000000..519745e --- /dev/null +++ b/docs/svc-deployment-plan.md @@ -0,0 +1,452 @@ +# SVC Public Edge Deployment Plan + +## Goal + +Deploy mc-proxy and MCNS on `svc.metacircular.net` to make Metacircular +platform services publicly accessible and delegate internal DNS zones. + +## Current State + +- **svc** (`svc.metacircular.net`): Debian 12, public IP, on tailnet as + `svc`. Runs MCIAS only (systemd). +- **rift** (`192.168.88.181` / `100.95.252.120`): NixOS, LAN + tailnet. + Runs metacrypt, mc-proxy, mcr, mcns, mcp (all containers under mcp user). +- **DNS**: `metacircular.net` managed at Hurricane Electric. Internal zones + (`mcp.metacircular.net`, `svc.mcp.metacircular.net`) served by MCNS on + rift, resolved via systemd-resolved split DNS on LAN clients. + +## Architecture After Deployment + +``` +Internet + │ + ▼ +svc.metacircular.net (public IP) + ├── MCIAS (:8443) ─── existing, no change + ├── mc-proxy (:443) ── NEW: L7 TLS termination + │ └── metacrypt.metacircular.net → rift metacrypt-web (Tailscale) + └── MCNS (:53) ─────── NEW: authoritative DNS for delegation + └── mcp.metacircular.net, svc.mcp.metacircular.net zones + + ┌─── Tailscale ───┐ + ▼ │ +rift (100.95.252.120) │ + ├── mc-proxy (:443/:8443/:9443) + ├── metacrypt-web (:18080) ◄── forwarded from svc mc-proxy + ├── metacrypt-api (:18443) + ├── mcr, mcns, mcp, etc. +``` + +## Phase 1: Build Binaries + +Both mc-proxy and mcns need to be built for svc (x86_64 Linux, Debian). +Since svc doesn't have Go, build on vade and scp. + +```bash +# On vade +cd ~/src/metacircular/mc-proxy +CGO_ENABLED=0 make mc-proxy +scp mc-proxy svc:/tmp/ + +cd ~/src/metacircular/mcns +CGO_ENABLED=0 make mcns +scp mcns svc:/tmp/ +``` + +## Phase 2: TLS Certificates + +Need a cert for `metacrypt.metacircular.net` (used by mc-proxy on svc for +L7 termination). Also need a cert for MCNS management API on svc. + +**Option A**: Issue from Metacrypt CA via the API. +**Option B**: Generate self-signed from the wntrmute CA on rift. + +Certs needed: +- `metacrypt.metacircular.net` — for mc-proxy L7 route +- `mcns-svc.svc.mcp.metacircular.net` — for MCNS management API (or self-signed) + +The wntrmute CA cert (`wntrmute-ca.pem`) must be distributed to external +users who want to trust the platform's TLS. + +## Phase 3: Deploy mc-proxy on svc + +### 3.1 Install + +```bash +# On svc (as root) +useradd --system --no-create-home --shell /usr/sbin/nologin mc-proxy +mkdir -p /srv/mc-proxy/{certs,backups} +chown -R mc-proxy:mc-proxy /srv/mc-proxy +chmod 0700 /srv/mc-proxy + +install -m 0755 /tmp/mc-proxy /usr/local/bin/mc-proxy +``` + +### 3.2 Configuration + +Create `/srv/mc-proxy/mc-proxy.toml`: + +```toml +[database] +path = "/srv/mc-proxy/mc-proxy.db" + +# Public HTTPS — L7 termination for metacrypt web UI. +# Backend is metacrypt-web on rift via Tailscale, re-encrypted with TLS. +# mc-proxy skips backend cert verification (InsecureSkipVerify: trusted +# internal backend), so SNI mismatch between the public hostname and +# metacrypt-web's cert is not an issue. +[[listeners]] +addr = ":443" + + [[listeners.routes]] + hostname = "metacrypt.metacircular.net" + backend = "100.95.252.120:18080" + mode = "l7" + tls_cert = "/srv/mc-proxy/certs/metacrypt.metacircular.net.pem" + tls_key = "/srv/mc-proxy/certs/metacrypt.metacircular.net.key" + backend_tls = true + +# Firewall — internet-facing, aggressive blocking. +[firewall] +geoip_db = "/srv/mc-proxy/GeoLite2-Country.mmdb" +blocked_ips = [] +blocked_cidrs = [] +blocked_countries = [] +rate_limit = 20 +rate_window = "10s" + +# No gRPC admin API on svc (managed via config file only). + +[proxy] +connect_timeout = "5s" +idle_timeout = "300s" +shutdown_timeout = "30s" + +[log] +level = "info" +``` + +Notes: +- `backend_tls = true` — metacrypt is security-sensitive; all hops must + use TLS. mc-proxy's backend transport uses `InsecureSkipVerify: true` + for trusted internal backends, so the public hostname / backend cert + mismatch is not a problem. +- **Rift prerequisite**: metacrypt-web must serve HTTPS and be exposed + on rift's Tailscale interface. Currently mapped to `127.0.0.1:18080`. + Two changes needed: + 1. Configure `web.tls_cert` and `web.tls_key` in metacrypt's config + (metacrypt-web already supports TLS if these are set). + 2. Update the MCP service definition to expose the port on Tailscale: + change `127.0.0.1:18080:8080` to `100.95.252.120:18080:8080`. + 3. Redeploy with `mcp deploy metacrypt`. + +### 3.3 GeoIP Database + +Download MaxMind GeoLite2-Country database (free, requires account): +```bash +# Download and install +wget -O /srv/mc-proxy/GeoLite2-Country.mmdb +chown mc-proxy:mc-proxy /srv/mc-proxy/GeoLite2-Country.mmdb +``` + +Populate `blocked_countries` with appropriate list after initial +deployment. Start empty, add based on access logs. + +### 3.4 User Agent Blocking + +UA blocking is L7-only and managed via the gRPC admin API or direct +database insertion. Common bots to block: +- Scanners: `zgrab`, `masscan`, `Nuclei`, `httpx` +- AI scrapers: `GPTBot`, `CCBot`, `ClaudeBot`, `Bytespider` + +Since we're not exposing the gRPC admin socket on svc, UA policies can +be pre-seeded in the SQLite database or added later via gRPC. + +### 3.5 Systemd Unit + +Copy from mc-proxy deploy, adapting for svc: + +```ini +[Unit] +Description=MC-Proxy TLS Router (svc) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=mc-proxy +Group=mc-proxy +ExecStart=/usr/local/bin/mc-proxy server --config /srv/mc-proxy/mc-proxy.toml +Restart=on-failure +RestartSec=5 + +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=true +RestrictRealtime=true +ReadWritePaths=/srv/mc-proxy +AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target +``` + +### 3.6 Enable + +```bash +systemctl daemon-reload +systemctl enable --now mc-proxy +systemctl enable --now mc-proxy-backup.timer +``` + +## Phase 4: Deploy MCNS on svc + +### 4.1 Install + +```bash +useradd --system --no-create-home --shell /usr/sbin/nologin mcns +mkdir -p /srv/mcns/{certs,backups} +chown -R mcns:mcns /srv/mcns +chmod 0700 /srv/mcns + +install -m 0755 /tmp/mcns /usr/local/bin/mcns +``` + +### 4.2 Configuration + +Create `/srv/mcns/mcns.toml`: + +```toml +[server] +listen_addr = ":8444" +grpc_addr = ":9444" +tls_cert = "/srv/mcns/certs/mcns.pem" +tls_key = "/srv/mcns/certs/mcns.key" + +[database] +path = "/srv/mcns/mcns.db" + +[dns] +listen_addr = ":53" +upstreams = ["1.1.1.1:53", "8.8.8.8:53"] + +[mcias] +server_url = "https://127.0.0.1:8443" +ca_cert = "" +service_name = "mcns" +tags = [] + +[log] +level = "info" +``` + +Notes: +- DNS on :53 (public-facing for NS delegation) +- Management API on :8444/:9444 (different from rift's 8443/9443 to + avoid confusion; not publicly exposed) +- MCIAS at localhost since MCIAS runs on svc + +### 4.3 Seed Data + +MCNS creates the database and runs migrations on first start. Migration +v2 seeds the same zones and records as rift's instance: +- `svc.mcp.metacircular.net` zone (metacrypt, mcr, sgard, mcp-agent) +- `mcp.metacircular.net` zone (rift, ns) + +The `ns` record needs updating: for public delegation, `ns` should also +have svc's public IP so external resolvers can reach it. + +After first start, add svc's public IP to the ns record via the API: +```bash +# Get svc's public IP +SVC_IP=$(curl -s ifconfig.me) + +# Add ns record pointing to svc +curl -k -X POST https://localhost:8444/v1/zones/mcp.metacircular.net/records \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"ns\", \"type\": \"A\", \"value\": \"$SVC_IP\", \"ttl\": 300}" +``` + +### 4.4 Systemd Unit + +```ini +[Unit] +Description=MCNS Authoritative DNS (svc) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=mcns +Group=mcns +ExecStart=/usr/local/bin/mcns server --config /srv/mcns/mcns.toml +Restart=on-failure +RestartSec=5 + +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=true +RestrictRealtime=true +ReadWritePaths=/srv/mcns +AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target +``` + +### 4.5 Enable + +```bash +systemctl daemon-reload +systemctl enable --now mcns +systemctl enable --now mcns-backup.timer +``` + +## Phase 5: DNS Changes at Hurricane Electric + +### 5.1 A Record for metacrypt + +``` +metacrypt.metacircular.net. A +``` + +### 5.2 NS Delegation for mcp.metacircular.net + +``` +mcp.metacircular.net. NS ns.mcp.metacircular.net. +ns.mcp.metacircular.net. A +``` + +This tells external resolvers: "for anything under mcp.metacircular.net, +ask ns.mcp.metacircular.net (which is svc)." MCNS on svc then serves the +authoritative answers. + +**Note**: NS delegation requires the glue record (the A record for ns) +to be in the parent zone at HE. Both records must be added. + +### 5.3 Verification + +```bash +# From an external machine (not on tailnet) +dig metacrypt.metacircular.net +dig rift.mcp.metacircular.net +dig metacrypt.svc.mcp.metacircular.net + +# Test the web UI +curl -k https://metacrypt.metacircular.net/ +``` + +## Phase 6: Verification + +### 6.1 mc-proxy on svc + +```bash +# From vade +curl -k https://metacrypt.metacircular.net/ +# Should show metacrypt web UI (login page) + +# Check mc-proxy logs +ssh svc journalctl -u mc-proxy -f +``` + +### 6.2 MCNS on svc + +```bash +# Direct query to svc +host rift.mcp.metacircular.net +host metacrypt.svc.mcp.metacircular.net + +# Forwarding works +host google.com + +# Delegation (from external, after HE changes propagate) +host rift.mcp.metacircular.net 8.8.8.8 +``` + +### 6.3 GeoIP and UA blocking + +```bash +# Check firewall is active +ssh svc journalctl -u mc-proxy | grep -i "blocked\|firewall" + +# Test UA blocking (after adding policies) +curl -k -A "zgrab/0.x" https://metacrypt.metacircular.net/ +# Should get 403 Forbidden +``` + +## Open Questions (Resolved) + +1. **mc-proxy backend routing**: RESOLVED. mc-proxy uses + `InsecureSkipVerify: true` for backend TLS — no SNI verification, no + cert hostname checking. svc mc-proxy connects directly to + metacrypt-web on rift's Tailscale IP with `backend_tls = true`. No + SNI mismatch issue. + +2. **GeoIP country list**: Start empty, add based on access logs after + deployment. Review periodically. + +3. **Record sync between MCNS instances**: Manual for now. Both instances + get the same seed data from migration v2. Future: AXFR/IXFR zone + transfers from rift (primary) to svc (secondary). + +4. **MCIAS on svc for MCNS auth**: MCNS config uses + `server_url = "https://127.0.0.1:8443"`. MCIAS cert is for + `svc.metacircular.net`. Set `ca_cert` to the wntrmute CA cert and + check if MCIAS cert includes localhost as a SAN. If not, either add + localhost SAN or use `svc.metacircular.net` as the server_url. + +5. **Backup coordination**: Stagger timers. mc-proxy at 02:00 UTC, + mcns at 02:15 UTC, mcias at 02:30 UTC (check existing mcias timer). + +## Rollback + +If anything goes wrong: +1. `systemctl stop mc-proxy mcns` on svc +2. Remove DNS records at HE +3. All traffic returns to existing paths (LAN via rift, MCIAS via svc) + +No changes are made to rift's configuration. This deployment is purely +additive on svc. + +## Security Note: Backend TLS Verification + +mc-proxy uses `InsecureSkipVerify: true` for backend TLS connections. +This is acceptable because backend IPs are hardcoded operator-configured +Tailscale addresses — WireGuard provides cryptographic peer +authentication, so TLS cert hostname validation is redundant. + +**Reconsider later**: when services have publicly-facing FQDNs (e.g., +`metacrypt.metacircular.net`), issue certs that include both the public +FQDN and the internal name (`metacrypt.svc.mcp.metacircular.net`) as +SANs. Then mc-proxy could enable backend cert verification for +defense-in-depth. This is a low-priority improvement — Tailscale's +identity guarantee is strong. + +## Future Considerations + +- **Let's Encrypt**: mc-proxy has ACME on its roadmap. Once implemented, + public-facing routes could use trusted certs automatically. +- **MCIAS migration to rift**: Once mc-proxy on svc handles public + routing, MCIAS could move to rift. svc mc-proxy would forward auth + traffic too. Eliminates the separate VPS dependency for MCIAS. +- **Additional public services**: mcr web UI, MCP status dashboard, etc. + Each would be an additional L7 route on svc's mc-proxy.