Files
Vantz Stockwell 651a35d4be
All checks were successful
CI / backend-types (push) Successful in 10s
CI / frontend-build (push) Successful in 15s
CI / agent-tests (push) Successful in 39s
CI / integration (push) Successful in 22s
docs(reference): import Dune: Awakening server-manager references
Phase 2 references for the host-agent Dune adapter, moved out of volatile /tmp
into docs/reference-repos/ (per Commander). Three upstream projects, .git +
node_modules + compiled binaries stripped (16MB source). Nested AI-instruction
files (.claude/, CLAUDE.md) removed so they don't pollute Corrosion sessions.

- icehunter/    dune-admin (Go+React) — 4 control planes; SETUP_DOCKER.md is the
                closest analog to our agent's Dune docker control plane (compose
                lifecycle, docker logs, RabbitMQ-via-exec, dune Postgres schema)
- adainrivers/  Rust/Tauri desktop — SSH+k8s BattleGroup control, maintenance
                daemon, in-game admin console (Rust idiom reference)
- the4rchangel/ Node web UI replacing battlegroup.bat — matches the Commander's
                Hyper-V self-host path + game-config schema

See docs/reference-repos/README.md for the full index + how we use each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:08:05 -04:00

204 lines
6.0 KiB
Go

package main
import (
"context"
"fmt"
"log"
"net"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"golang.org/x/crypto/ssh"
)
var (
// Legacy globals kept for K8s path (globalSSH/globalPod*) and for the
// shared DB pool (globalDB). New code should use globalExecutor/globalControl.
globalSSH *ssh.Client
globalDB *pgxpool.Pool
globalPodIP string
globalPodNS string
globalPod string
globalExecutor Executor
globalControl ControlPlane
)
// resolveControl returns the effective control plane name based on config,
// defaulting to "kubectl" when SSH is configured and "local" otherwise.
func resolveControl() string {
if controlPlane != "" {
return controlPlane
}
if sshHost != "" {
return "kubectl"
}
return "local"
}
// connectAll creates the executor, control plane, and DB connection, then sets
// all globals. Called from main() and handleReconnect.
func connectAll() error {
ctrl := resolveControl()
// Start from the full loaded config so provider-specific fields
// (docker_*, cmd_*) that have no flag/env equivalent are preserved.
cfg := loadedConfig
cfg.SSHHost = sshHost
cfg.SSHUser = sshUser
cfg.SSHKey = resolveKeyPath()
cfg.DBHost = dbHost
cfg.DBPort = dbPort
cfg.DBUser = dbUser
cfg.DBPass = dbPass
cfg.DBName = dbName
cfg.DBSchema = dbSchema
cfg.Control = ctrl
cfg.ControlNamespace = controlNS
cfg.BrokerGameAddr = brokerGameAddr
cfg.BrokerAdminAddr = brokerAdminAddr
cfg.BrokerTLS = brokerTLS
cfg.BackupDir = backupDir
cfg.ServerIniDir = serverIniDir
exec, err := newExecutor(cfg.SSHHost, cfg.SSHUser, cfg.SSHKey)
if err != nil {
return fmt.Errorf("executor: %w", err)
}
// AMP mode wraps the executor to elevate WriteFile through sudo.
// Applies regardless of whether the inner executor is local or SSH.
if ctrl == "amp" {
user := cfg.AmpUser
if user == "" {
user = "amp"
}
exec = &ampExecutor{Executor: exec, ampUser: user}
}
globalExecutor = exec
// kubectl needs DB-pod discovery (via the executor, not the DB) to learn the
// namespace before the control plane and DB connect. A discovery failure is
// fatal — without a namespace there is nothing to drive the control plane.
if ctrl == "kubectl" {
ns, pod, podIP, err := discoverDBPod(exec)
if err != nil {
exec.Close()
globalExecutor = nil
return fmt.Errorf("DB pod discovery: %w", err)
}
globalPodNS = ns
globalPod = pod
globalPodIP = podIP
// Propagate discovered namespace so kubectlControl can use it.
if cfg.ControlNamespace == "" {
cfg.ControlNamespace = ns
controlNS = ns
}
if s, ok := exec.(*sshExecutor); ok {
globalSSH = s.client
}
}
// The control plane (logs, battlegroup, server control) does not depend on
// the database. Establish it before connecting the DB so a DB outage never
// disables it — the DB can be re-established later via /api/v1/reconnect
// without losing control-plane functionality.
globalControl = newControlPlane(ctrl, cfg)
// DB connect is best-effort: on failure keep the executor + control plane
// intact and return the error so the caller can surface it (main starts the
// server anyway; the systemd watchdog or a manual reconnect retries the DB).
var pool *pgxpool.Pool
if ctrl == "kubectl" {
pool, err = connectDB(context.Background(), cfg.DBUser, cfg.DBPass)
} else {
pool, err = connectDBDirect(context.Background(), cfg)
}
if err != nil {
globalDB = nil
return fmt.Errorf("DB connect: %w", err)
}
globalDB = pool
// Best-effort: ensure the GM/Server chat persona exists for admin messaging
// (whisper, map announce). Idempotent (ON CONFLICT DO NOTHING); a failure here
// must never block startup or reconnect, so it is logged and swallowed.
if err := cmdEnsureGMIdentity(context.Background()); err != nil {
log.Printf("connectAll: ensure GM identity: %v", err)
}
return nil
}
// cmdConnect wraps connectAll in the legacy Msg return type.
func cmdConnect() Msg {
if err := connectAll(); err != nil {
return msgConnect{err: err}
}
return msgConnect{}
}
// connectDBDirect opens a pgxpool without SSH tunnelling, routing TCP through
// the executor's Dial (which is net.Dial for local, SSH tunnel for SSH).
func connectDBDirect(ctx context.Context, cfg appConfig) (*pgxpool.Pool, error) {
connStr := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPass, cfg.DBName)
poolCfg, err := pgxpool.ParseConfig(connStr)
if err != nil {
return nil, err
}
poolCfg.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
_, err := conn.Exec(ctx, fmt.Sprintf(`SET search_path TO %s, public`, pgx.Identifier{cfg.DBSchema}.Sanitize()))
return err
}
if globalExecutor != nil {
addr := fmt.Sprintf("%s:%d", cfg.DBHost, cfg.DBPort)
poolCfg.ConnConfig.DialFunc = func(ctx context.Context, _, _ string) (net.Conn, error) {
return globalExecutor.Dial("tcp", addr)
}
}
pool, err := pgxpool.NewWithConfig(ctx, poolCfg)
if err != nil {
return nil, err
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, err
}
dbUser = cfg.DBUser
dbPass = cfg.DBPass
return pool, nil
}
func connectDB(ctx context.Context, user, pass string) (*pgxpool.Pool, error) {
connStr := fmt.Sprintf(
"host=127.0.0.1 port=%d user=%s password=%s dbname=%s sslmode=disable",
dbPort, user, pass, dbName)
poolCfg, err := pgxpool.ParseConfig(connStr)
if err != nil {
return nil, err
}
poolCfg.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
_, err := conn.Exec(ctx, fmt.Sprintf(`SET search_path TO %s, public`, pgx.Identifier{dbSchema}.Sanitize()))
return err
}
poolCfg.ConnConfig.LookupFunc = func(_ context.Context, _ string) ([]string, error) {
return []string{globalPodIP}, nil
}
poolCfg.ConnConfig.DialFunc = func(_ context.Context, _, _ string) (net.Conn, error) {
return globalSSH.Dial("tcp", fmt.Sprintf("%s:%d", globalPodIP, dbPort))
}
pool, err := pgxpool.NewWithConfig(ctx, poolCfg)
if err != nil {
return nil, err
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, err
}
dbUser = user
dbPass = pass
return pool, nil
}