5 Commits

Author SHA1 Message Date
4f3249fdc3 Regenerate proto files for mc/ module path
Raw descriptor bytes in .pb.go files were corrupted by the sed-based
module path rename (string length changed, breaking protobuf binary
encoding). Regenerated with protoc to fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:54:22 -07:00
f31a7f20fb Bump flake.nix version to match latest tag
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:16:38 -07:00
feeadc582b Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00
a45ed03432 Use http.Transport for non-TLS backends (HTTP/1.1 support)
The h2c-only transport (http2.Transport) fails against backends like
Gitea that only speak HTTP/1.1. Switch to standard http.Transport for
non-TLS backends, which handles HTTP/1.1 natively and can upgrade to
h2c if the backend supports it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:32:42 -07:00
dc1816b159 Add MCP deployment section to RUNBOOK.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:09:18 -07:00
37 changed files with 190 additions and 74 deletions

View File

@@ -26,7 +26,7 @@ go test ./internal/sni -run TestExtract
## Architecture ## Architecture
- **Module path**: `git.wntrmute.dev/kyle/mc-proxy` - **Module path**: `git.wntrmute.dev/mc/mc-proxy`
- **Go with CGO_ENABLED=0**, statically linked, Alpine containers - **Go with CGO_ENABLED=0**, statically linked, Alpine containers
- **Dual mode, per-route** — L4 (passthrough) and L7 (TLS-terminating HTTP/2 reverse proxy) coexist on the same listener - **Dual mode, per-route** — L4 (passthrough) and L7 (TLS-terminating HTTP/2 reverse proxy) coexist on the same listener
- **PROXY protocol** — listeners accept v1/v2; routes send v2. Enables edge→origin deployments over Tailscale - **PROXY protocol** — listeners accept v1/v2; routes send v2. Enables edge→origin deployments over Tailscale

View File

@@ -21,8 +21,8 @@ lint:
golangci-lint run ./... golangci-lint run ./...
proto: proto:
protoc --go_out=. --go_opt=module=git.wntrmute.dev/kyle/mc-proxy \ protoc --go_out=. --go_opt=module=git.wntrmute.dev/mc/mc-proxy \
--go-grpc_out=. --go-grpc_opt=module=git.wntrmute.dev/kyle/mc-proxy \ --go-grpc_out=. --go-grpc_opt=module=git.wntrmute.dev/mc/mc-proxy \
proto/mc_proxy/v1/*.proto proto/mc_proxy/v1/*.proto
proto-lint: proto-lint:

View File

@@ -187,6 +187,56 @@ grpcurl -cacert ca.pem -cert client.pem -key client-key.pem \
-d '{"rule": {"type": "FIREWALL_RULE_TYPE_IP", "value": "203.0.113.50"}}' -d '{"rule": {"type": "FIREWALL_RULE_TYPE_IP", "value": "203.0.113.50"}}'
``` ```
## Deployment with MCP
mc-proxy runs on rift as a single container managed by MCP. The service
definition lives at `~/.config/mcp/services/mc-proxy.toml` on rift (reference
copy at `deploy/mc-proxy-rift.toml` in this repo). The container mounts
`/srv/mc-proxy` which holds the config file, SQLite database, GeoIP database,
and TLS certificates for backends. It runs as `--user 0:0` under rootless
podman.
Listeners: `:443` (L7 terminating), `:8443` (L4 passthrough), `:9443` (L4
passthrough).
### Deploy or Update
```bash
mcp deploy mc-proxy
```
### Restart / Stop
```bash
mcp restart mc-proxy
mcp stop mc-proxy
```
### Check Status
```bash
mcp ps
mcp status mc-proxy
```
### View Logs
```bash
ssh rift 'doas su - mcp -s /bin/sh -c "podman logs mc-proxy"'
```
### Update Routes
Edit the config at `/srv/mc-proxy/mc-proxy.toml` on rift, then restart:
```bash
mcp restart mc-proxy
```
Routes added at runtime via the gRPC admin API are persisted in the database
and survive restarts. Editing the TOML config is only necessary for changing
listener definitions or static seed routes.
## Incident Procedures ## Incident Procedures
### Proxy Not Starting ### Proxy Not Starting

View File

@@ -10,7 +10,7 @@ import (
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
healthpb "google.golang.org/grpc/health/grpc_health_v1" healthpb "google.golang.org/grpc/health/grpc_health_v1"
pb "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1" pb "git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1"
) )
// Client provides access to the mc-proxy admin API. // Client provides access to the mc-proxy admin API.

View File

@@ -15,12 +15,12 @@ import (
healthpb "google.golang.org/grpc/health/grpc_health_v1" healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/test/bufconn" "google.golang.org/grpc/test/bufconn"
pb "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1" pb "git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/db" "git.wntrmute.dev/mc/mc-proxy/internal/db"
"git.wntrmute.dev/kyle/mc-proxy/internal/firewall" "git.wntrmute.dev/mc/mc-proxy/internal/firewall"
"git.wntrmute.dev/kyle/mc-proxy/internal/grpcserver" "git.wntrmute.dev/mc/mc-proxy/internal/grpcserver"
"git.wntrmute.dev/kyle/mc-proxy/internal/server" "git.wntrmute.dev/mc/mc-proxy/internal/server"
) )
func setupTestClient(t *testing.T) *Client { func setupTestClient(t *testing.T) *Client {

View File

@@ -11,12 +11,12 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/db" "git.wntrmute.dev/mc/mc-proxy/internal/db"
"git.wntrmute.dev/kyle/mc-proxy/internal/firewall" "git.wntrmute.dev/mc/mc-proxy/internal/firewall"
"git.wntrmute.dev/kyle/mc-proxy/internal/grpcserver" "git.wntrmute.dev/mc/mc-proxy/internal/grpcserver"
"git.wntrmute.dev/kyle/mc-proxy/internal/metrics" "git.wntrmute.dev/mc/mc-proxy/internal/metrics"
"git.wntrmute.dev/kyle/mc-proxy/internal/server" "git.wntrmute.dev/mc/mc-proxy/internal/server"
) )
func serverCmd() *cobra.Command { func serverCmd() *cobra.Command {

View File

@@ -9,8 +9,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/db" "git.wntrmute.dev/mc/mc-proxy/internal/db"
) )
func snapshotCmd() *cobra.Command { func snapshotCmd() *cobra.Command {

View File

@@ -9,8 +9,8 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
pb "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1" pb "git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
) )
func statusCmd() *cobra.Command { func statusCmd() *cobra.Command {

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" "git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
) )
func firewallCmd() *cobra.Command { func firewallCmd() *cobra.Command {

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" "git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
) )
func healthCmd() *cobra.Command { func healthCmd() *cobra.Command {

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
mcproxy "git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" mcproxy "git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
) )
func policiesCmd() *cobra.Command { func policiesCmd() *cobra.Command {

View File

@@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" "git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
) )
const defaultSocketPath = "/srv/mc-proxy/mc-proxy.sock" const defaultSocketPath = "/srv/mc-proxy/mc-proxy.sock"

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
mcproxy "git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" mcproxy "git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
) )
func routesCmd() *cobra.Command { func routesCmd() *cobra.Command {

View File

@@ -10,7 +10,7 @@
let let
system = "x86_64-linux"; system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
version = "0.1.0"; version = "1.1.0";
in in
{ {
packages.${system} = { packages.${system} = {

View File

@@ -1449,7 +1449,7 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" +
"\x0eListL7Policies\x12\".mc_proxy.v1.ListL7PoliciesRequest\x1a#.mc_proxy.v1.ListL7PoliciesResponse\x12P\n" + "\x0eListL7Policies\x12\".mc_proxy.v1.ListL7PoliciesRequest\x1a#.mc_proxy.v1.ListL7PoliciesResponse\x12P\n" +
"\vAddL7Policy\x12\x1f.mc_proxy.v1.AddL7PolicyRequest\x1a .mc_proxy.v1.AddL7PolicyResponse\x12Y\n" + "\vAddL7Policy\x12\x1f.mc_proxy.v1.AddL7PolicyRequest\x1a .mc_proxy.v1.AddL7PolicyResponse\x12Y\n" +
"\x0eRemoveL7Policy\x12\".mc_proxy.v1.RemoveL7PolicyRequest\x1a#.mc_proxy.v1.RemoveL7PolicyResponse\x12J\n" + "\x0eRemoveL7Policy\x12\".mc_proxy.v1.RemoveL7PolicyRequest\x1a#.mc_proxy.v1.RemoveL7PolicyResponse\x12J\n" +
"\tGetStatus\x12\x1d.mc_proxy.v1.GetStatusRequest\x1a\x1e.mc_proxy.v1.GetStatusResponseB:Z8git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1;mcproxyv1b\x06proto3" "\tGetStatus\x12\x1d.mc_proxy.v1.GetStatusRequest\x1a\x1e.mc_proxy.v1.GetStatusResponseB8Z6git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1;mcproxyv1b\x06proto3"
var ( var (
file_proto_mc_proxy_v1_admin_proto_rawDescOnce sync.Once file_proto_mc_proxy_v1_admin_proto_rawDescOnce sync.Once

4
go.mod
View File

@@ -1,9 +1,9 @@
module git.wntrmute.dev/kyle/mc-proxy module git.wntrmute.dev/mc/mc-proxy
go 1.25.7 go 1.25.7
require ( require (
git.wntrmute.dev/kyle/mcdsl v1.0.0 git.wntrmute.dev/mc/mcdsl v1.2.0
github.com/oschwald/maxminddb-golang v1.13.1 github.com/oschwald/maxminddb-golang v1.13.1
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2

4
go.sum
View File

@@ -1,5 +1,5 @@
git.wntrmute.dev/kyle/mcdsl v1.0.0 h1:YB7dx4gdNYKKcVySpL6UkwHqdCJ9Nl1yS0+eHk0hNtk= git.wntrmute.dev/mc/mcdsl v1.2.0 h1:41hep7/PNZJfN0SN/nM+rQpyF1GSZcvNNjyVG81DI7U=
git.wntrmute.dev/kyle/mcdsl v1.0.0/go.mod h1:wo0tGfUAxci3XnOe4/rFmR0RjUElKdYUazc+Np986sg= git.wntrmute.dev/mc/mcdsl v1.2.0/go.mod h1:lXYrAt74ZUix6rx9oVN8d2zH1YJoyp4uxPVKQ+SSxuM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"strings" "strings"
mcdslconfig "git.wntrmute.dev/kyle/mcdsl/config" mcdslconfig "git.wntrmute.dev/mc/mcdsl/config"
) )
// Duration is an alias for the mcdsl config.Duration type, which wraps // Duration is an alias for the mcdsl config.Duration type, which wraps

View File

@@ -4,7 +4,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
mcdsldb "git.wntrmute.dev/kyle/mcdsl/db" mcdsldb "git.wntrmute.dev/mc/mcdsl/db"
) )
// Store wraps a SQLite database connection for mc-proxy persistence. // Store wraps a SQLite database connection for mc-proxy persistence.

View File

@@ -4,7 +4,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
) )
func openTestDB(t *testing.T) *Store { func openTestDB(t *testing.T) *Store {

View File

@@ -1,7 +1,7 @@
package db package db
import ( import (
mcdsldb "git.wntrmute.dev/kyle/mcdsl/db" mcdsldb "git.wntrmute.dev/mc/mcdsl/db"
) )
// Migrations is the ordered list of schema migrations for mc-proxy. // Migrations is the ordered list of schema migrations for mc-proxy.

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
) )
// Seed populates the database from TOML config data. Only called when the // Seed populates the database from TOML config data. Only called when the

View File

@@ -1,7 +1,7 @@
package db package db
import ( import (
mcdsldb "git.wntrmute.dev/kyle/mcdsl/db" mcdsldb "git.wntrmute.dev/mc/mcdsl/db"
) )
// Snapshot creates a consistent backup of the database using VACUUM INTO. // Snapshot creates a consistent backup of the database using VACUUM INTO.

View File

@@ -17,10 +17,10 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
pb "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1" pb "git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/db" "git.wntrmute.dev/mc/mc-proxy/internal/db"
"git.wntrmute.dev/kyle/mc-proxy/internal/server" "git.wntrmute.dev/mc/mc-proxy/internal/server"
) )
var countryCodeRe = regexp.MustCompile(`^[A-Z]{2}$`) var countryCodeRe = regexp.MustCompile(`^[A-Z]{2}$`)

View File

@@ -15,11 +15,11 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn" "google.golang.org/grpc/test/bufconn"
pb "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1" pb "git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/db" "git.wntrmute.dev/mc/mc-proxy/internal/db"
"git.wntrmute.dev/kyle/mc-proxy/internal/firewall" "git.wntrmute.dev/mc/mc-proxy/internal/firewall"
"git.wntrmute.dev/kyle/mc-proxy/internal/server" "git.wntrmute.dev/mc/mc-proxy/internal/server"
) )
// testEnv bundles all the objects needed for a grpcserver test. // testEnv bundles all the objects needed for a grpcserver test.

View File

@@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"git.wntrmute.dev/kyle/mc-proxy/internal/metrics" "git.wntrmute.dev/mc/mc-proxy/internal/metrics"
) )
// PolicyRule defines an L7 blocking policy. // PolicyRule defines an L7 blocking policy.

View File

@@ -15,8 +15,8 @@ import (
"strconv" "strconv"
"time" "time"
"git.wntrmute.dev/kyle/mc-proxy/internal/metrics" "git.wntrmute.dev/mc/mc-proxy/internal/metrics"
"git.wntrmute.dev/kyle/mc-proxy/internal/proxyproto" "git.wntrmute.dev/mc/mc-proxy/internal/proxyproto"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -186,16 +186,16 @@ func newTransport(route RouteConfig) (http.RoundTripper, error) {
}, nil }, nil
} }
// h2c: HTTP/2 over plaintext TCP. // Plain HTTP backend. Use standard http.Transport which speaks
return &http2.Transport{ // HTTP/1.1 by default and can upgrade to h2c if the backend
AllowHTTP: true, // supports it. This handles backends like Gitea that only speak
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { // HTTP/1.1.
conn, err := dialBackend(ctx, network, addr, connectTimeout, route) return &http.Transport{
if err != nil { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return nil, err return dialBackend(ctx, network, addr, connectTimeout, route)
}
return conn, nil
}, },
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}, nil }, nil
} }

View File

@@ -11,13 +11,13 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/firewall" "git.wntrmute.dev/mc/mc-proxy/internal/firewall"
"git.wntrmute.dev/kyle/mc-proxy/internal/l7" "git.wntrmute.dev/mc/mc-proxy/internal/l7"
"git.wntrmute.dev/kyle/mc-proxy/internal/metrics" "git.wntrmute.dev/mc/mc-proxy/internal/metrics"
"git.wntrmute.dev/kyle/mc-proxy/internal/proxy" "git.wntrmute.dev/mc/mc-proxy/internal/proxy"
"git.wntrmute.dev/kyle/mc-proxy/internal/proxyproto" "git.wntrmute.dev/mc/mc-proxy/internal/proxyproto"
"git.wntrmute.dev/kyle/mc-proxy/internal/sni" "git.wntrmute.dev/mc/mc-proxy/internal/sni"
) )
// L7PolicyRule is an L7 blocking policy attached to a route. // L7PolicyRule is an L7 blocking policy attached to a route.

View File

@@ -24,9 +24,9 @@ import (
"testing" "testing"
"time" "time"
"git.wntrmute.dev/kyle/mc-proxy/internal/config" "git.wntrmute.dev/mc/mc-proxy/internal/config"
"git.wntrmute.dev/kyle/mc-proxy/internal/firewall" "git.wntrmute.dev/mc/mc-proxy/internal/firewall"
"git.wntrmute.dev/kyle/mc-proxy/internal/proxyproto" "git.wntrmute.dev/mc/mc-proxy/internal/proxyproto"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
) )

View File

@@ -2,7 +2,7 @@ syntax = "proto3";
package mc_proxy.v1; package mc_proxy.v1;
option go_package = "git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1;mcproxyv1"; option go_package = "git.wntrmute.dev/mc/mc-proxy/gen/mc_proxy/v1;mcproxyv1";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";

View File

@@ -34,7 +34,7 @@ import (
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"git.wntrmute.dev/kyle/mcdsl/auth" "git.wntrmute.dev/mc/mcdsl/auth"
) )
// Base contains the configuration sections common to all Metacircular // Base contains the configuration sections common to all Metacircular
@@ -144,6 +144,8 @@ func Load[T any](path string, envPrefix string) (*T, error) {
applyEnvToStruct(reflect.ValueOf(&cfg).Elem(), envPrefix) applyEnvToStruct(reflect.ValueOf(&cfg).Elem(), envPrefix)
} }
applyPortEnv(&cfg)
applyBaseDefaults(&cfg) applyBaseDefaults(&cfg)
if err := validateBase(&cfg); err != nil { if err := validateBase(&cfg); err != nil {
@@ -239,6 +241,70 @@ func findBase(cfg any) *Base {
return nil return nil
} }
// applyPortEnv overrides ServerConfig.ListenAddr and ServerConfig.GRPCAddr
// from $PORT and $PORT_GRPC respectively. These environment variables are
// set by the MCP agent to assign authoritative port bindings, so they take
// precedence over both TOML values and generic env overrides.
func applyPortEnv(cfg any) {
sc := findServerConfig(cfg)
if sc == nil {
return
}
if port, ok := os.LookupEnv("PORT"); ok {
sc.ListenAddr = ":" + port
}
if port, ok := os.LookupEnv("PORT_GRPC"); ok {
sc.GRPCAddr = ":" + port
}
}
// findServerConfig returns a pointer to the ServerConfig in the config
// struct. It first checks for an embedded Base (which contains Server),
// then walks the struct tree via reflection to find any ServerConfig field
// directly (e.g., the Metacrypt pattern where ServerConfig is embedded
// without Base).
func findServerConfig(cfg any) *ServerConfig {
if base := findBase(cfg); base != nil {
return &base.Server
}
return findServerConfigReflect(reflect.ValueOf(cfg))
}
// findServerConfigReflect walks the struct tree to find a ServerConfig field.
func findServerConfigReflect(v reflect.Value) *ServerConfig {
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil
}
scType := reflect.TypeOf(ServerConfig{})
t := v.Type()
for i := range t.NumField() {
field := t.Field(i)
fv := v.Field(i)
if field.Type == scType {
sc, ok := fv.Addr().Interface().(*ServerConfig)
if ok {
return sc
}
}
// Recurse into embedded or nested structs.
if fv.Kind() == reflect.Struct && field.Type != scType {
if sc := findServerConfigReflect(fv); sc != nil {
return sc
}
}
}
return nil
}
// applyEnvToStruct recursively walks a struct and overrides field values // applyEnvToStruct recursively walks a struct and overrides field values
// from environment variables. The env variable name is built from the // from environment variables. The env variable name is built from the
// prefix and the toml tag: PREFIX_SECTION_FIELD (uppercased). // prefix and the toml tag: PREFIX_SECTION_FIELD (uppercased).

8
vendor/modules.txt vendored
View File

@@ -1,8 +1,8 @@
# git.wntrmute.dev/kyle/mcdsl v1.0.0 # git.wntrmute.dev/mc/mcdsl v1.2.0
## explicit; go 1.25.7 ## explicit; go 1.25.7
git.wntrmute.dev/kyle/mcdsl/auth git.wntrmute.dev/mc/mcdsl/auth
git.wntrmute.dev/kyle/mcdsl/config git.wntrmute.dev/mc/mcdsl/config
git.wntrmute.dev/kyle/mcdsl/db git.wntrmute.dev/mc/mcdsl/db
# github.com/beorn7/perks v1.0.1 # github.com/beorn7/perks v1.0.1
## explicit; go 1.11 ## explicit; go 1.11
github.com/beorn7/perks/quantile github.com/beorn7/perks/quantile