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) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 01:19:19 -07:00
parent c87d8b95db
commit 5baf12e303
3 changed files with 483 additions and 20 deletions

12
.mcp.json Normal file
View File

@@ -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"
}
}
}
}

View File

@@ -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.
---

452
docs/svc-deployment-plan.md Normal file
View File

@@ -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 <maxmind-url>
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 <svc-public-ip>
```
### 5.2 NS Delegation for mcp.metacircular.net
```
mcp.metacircular.net. NS ns.mcp.metacircular.net.
ns.mcp.metacircular.net. A <svc-public-ip>
```
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 <svc-public-ip>
host metacrypt.svc.mcp.metacircular.net <svc-public-ip>
# Forwarding works
host google.com <svc-public-ip>
# 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.