Update ARCHITECTURE.md and CLAUDE.md for SQLite and gRPC

Reflect database schema, write-through pattern, startup behavior,
gRPC admin API config, and updated storage layout. Remove completed
items from future work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 03:10:04 -07:00
parent 9cba3241e8
commit dc04a070a3
2 changed files with 80 additions and 15 deletions

View File

@@ -167,9 +167,10 @@ on which port the client connected to.
### Route Table Source ### Route Table Source
Route tables are defined inline under each listener in the TOML Route tables are persisted in the SQLite database. On first run, they are
configuration file. The design anticipates future migration to a SQLite seeded from the TOML configuration. On subsequent runs, the database is
database for dynamic route management via the control plane API. the source of truth. Routes can be added or removed at runtime via the
gRPC admin API.
--- ---
@@ -179,7 +180,11 @@ TOML configuration file, loaded at startup. The proxy refuses to start if
required fields are missing or invalid. required fields are missing or invalid.
```toml ```toml
# Listeners. Each has its own route table. # Database. Required.
[database]
path = "/srv/mc-proxy/mc-proxy.db"
# Listeners. Each has its own route table (seeds DB on first run).
[[listeners]] [[listeners]]
addr = ":443" addr = ":443"
@@ -205,6 +210,13 @@ addr = ":9443"
hostname = "mcias.metacircular.net" hostname = "mcias.metacircular.net"
backend = "127.0.0.1:28443" backend = "127.0.0.1:28443"
# gRPC admin API. Optional — omit addr to disable.
[grpc]
addr = "127.0.0.1:9090"
tls_cert = "/srv/mc-proxy/certs/cert.pem"
tls_key = "/srv/mc-proxy/certs/key.pem"
client_ca = "/srv/mc-proxy/certs/ca.pem"
# Firewall. Global blocklist, evaluated before routing. Default allow. # Firewall. Global blocklist, evaluated before routing. Default allow.
[firewall] [firewall]
geoip_db = "/srv/mc-proxy/GeoLite2-Country.mmdb" geoip_db = "/srv/mc-proxy/GeoLite2-Country.mmdb"
@@ -240,17 +252,64 @@ these are structural and must be in the TOML file.
## Storage ## Storage
mc-proxy has minimal storage requirements. There is no database in the ### SQLite Database
initial implementation.
Listeners, routes, and firewall rules are persisted in a SQLite database
(WAL mode, foreign keys enabled, busy timeout 5000ms). The pure-Go driver
`modernc.org/sqlite` is used (no CGo).
**Startup behavior:**
1. Open the database at the configured path. Run migrations.
2. If the database is empty (first run): seed from the TOML config.
3. If the database has data: load from it. TOML listener/route/firewall
fields are ignored.
The TOML config continues to own operational settings: proxy timeouts,
log level, gRPC config, GeoIP database path.
**Write-through pattern:** The gRPC admin API writes to the database first,
then updates in-memory state. If the database write fails, the in-memory
state is not modified.
### Schema
```sql
CREATE TABLE listeners (
id INTEGER PRIMARY KEY,
addr TEXT NOT NULL UNIQUE
);
CREATE TABLE routes (
id INTEGER PRIMARY KEY,
listener_id INTEGER NOT NULL REFERENCES listeners(id) ON DELETE CASCADE,
hostname TEXT NOT NULL,
backend TEXT NOT NULL,
UNIQUE(listener_id, hostname)
);
CREATE TABLE firewall_rules (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL CHECK(type IN ('ip', 'cidr', 'country')),
value TEXT NOT NULL,
UNIQUE(type, value)
);
```
### Data Directory
``` ```
/srv/mc-proxy/ /srv/mc-proxy/
├── mc-proxy.toml Configuration ├── mc-proxy.toml Configuration
├── mc-proxy.db SQLite database
├── certs/ TLS certificates (for gRPC admin API)
├── GeoLite2-Country.mmdb GeoIP database (if using country blocks) ├── GeoLite2-Country.mmdb GeoIP database (if using country blocks)
└── backups/ Reserved for future use └── backups/ Database snapshots
``` ```
No TLS certificates are stored — mc-proxy does not terminate TLS. mc-proxy does not terminate TLS on the proxy listeners, so no proxy
certificates are needed. The `certs/` directory is for the gRPC admin
API's TLS and optional mTLS keypair.
--- ---
@@ -334,8 +393,7 @@ Items are listed roughly in priority order:
| Item | Description | | Item | Description |
|------|-------------| |------|-------------|
| **gRPC admin API** | Internal-only API for managing routes and firewall rules at runtime, integrated with the Metacircular Control Plane. | | **MCP integration** | Wire the gRPC admin API into the Metacircular Control Plane for centralized management. |
| **SQLite route storage** | Migrate route table from TOML to SQLite for dynamic management via the admin API. |
| **L7 HTTPS support** | TLS-terminating mode for selected routes, enabling HTTP-level features (user-agent blocking, header inspection, request routing). | | **L7 HTTPS support** | TLS-terminating mode for selected routes, enabling HTTP-level features (user-agent blocking, header inspection, request routing). |
| **ACME integration** | Automatic certificate provisioning via Let's Encrypt for L7 routes. | | **ACME integration** | Automatic certificate provisioning via Let's Encrypt for L7 routes. |
| **User-agent blocking** | Block connections based on user-agent string (requires L7 mode). | | **User-agent blocking** | Block connections based on user-agent string (requires L7 mode). |

View File

@@ -15,6 +15,8 @@ make build # compile all packages
make test # run all tests make test # run all tests
make vet # go vet make vet # go vet
make lint # golangci-lint make lint # golangci-lint
make proto # regenerate gRPC code from proto definitions
make devserver # build and run locally with srv/mc-proxy.toml
``` ```
Run a single test: Run a single test:
@@ -26,9 +28,10 @@ go test ./internal/sni -run TestExtract
- **Module path**: `git.wntrmute.dev/kyle/mc-proxy` - **Module path**: `git.wntrmute.dev/kyle/mc-proxy`
- **Go with CGO_ENABLED=0**, statically linked, Alpine containers - **Go with CGO_ENABLED=0**, statically linked, Alpine containers
- **No API surface yet** — config-driven via TOML; gRPC admin API planned for future MCP integration - **gRPC admin API** — manages routes and firewall rules at runtime; TLS with optional mTLS; optional (disabled if `[grpc]` section omitted from config)
- **No auth** — this is pre-auth infrastructure; services behind it handle their own MCIAS auth - **No auth on proxy listeners** — this is pre-auth infrastructure; services behind it handle their own MCIAS auth
- **No database** — routes and firewall rules are in the TOML config; SQLite planned for dynamic route management - **SQLite database** — persists listeners, routes, and firewall rules; pure-Go driver (`modernc.org/sqlite`); seeded from TOML on first run, DB is source of truth thereafter
- **Write-through pattern** — gRPC mutations write to DB first, then update in-memory state
- **Config**: TOML via `go-toml/v2`, runtime data in `/srv/mc-proxy/` - **Config**: TOML via `go-toml/v2`, runtime data in `/srv/mc-proxy/`
- **Testing**: stdlib `testing` only, `t.TempDir()` for isolation - **Testing**: stdlib `testing` only, `t.TempDir()` for isolation
- **Linting**: golangci-lint v2 with `.golangci.yaml` - **Linting**: golangci-lint v2 with `.golangci.yaml`
@@ -36,10 +39,13 @@ go test ./internal/sni -run TestExtract
## Package Structure ## Package Structure
- `internal/config/` — TOML config loading and validation - `internal/config/` — TOML config loading and validation
- `internal/db/` — SQLite database: migrations, CRUD for listeners/routes/firewall rules, seeding, snapshots
- `internal/sni/` — TLS ClientHello parser; extracts SNI hostname without consuming bytes - `internal/sni/` — TLS ClientHello parser; extracts SNI hostname without consuming bytes
- `internal/firewall/` — global blocklist evaluation (IP, CIDR, GeoIP via MaxMind GeoLite2); thread-safe GeoIP reload - `internal/firewall/` — global blocklist evaluation (IP, CIDR, GeoIP via MaxMind GeoLite2); thread-safe mutations and GeoIP reload
- `internal/proxy/` — bidirectional TCP relay with half-close propagation and idle timeout - `internal/proxy/` — bidirectional TCP relay with half-close propagation and idle timeout
- `internal/server/` — orchestrates listeners → firewall → SNI → route → proxy pipeline; graceful shutdown - `internal/server/` — orchestrates listeners → firewall → SNI → route → proxy pipeline; per-listener state with connection tracking
- `internal/grpcserver/` — gRPC admin API: route/firewall CRUD, status, write-through to DB
- `proto/mc-proxy/v1/` — protobuf definitions; `gen/mc-proxy/v1/` has generated code
## Signals ## Signals
@@ -52,3 +58,4 @@ go test ./internal/sni -run TestExtract
- Firewall rules are always evaluated before any routing decision. - Firewall rules are always evaluated before any routing decision.
- SNI matching is exact and case-insensitive. - SNI matching is exact and case-insensitive.
- Blocked connections get a TCP RST — no error messages, no TLS alerts. - Blocked connections get a TCP RST — no error messages, no TLS alerts.
- Database writes must succeed before in-memory state is updated (write-through).