docs(reference): import Dune: Awakening server-manager references
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

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>
This commit is contained in:
Vantz Stockwell
2026-06-11 21:08:05 -04:00
parent 0715492ddf
commit 651a35d4be
1334 changed files with 238971 additions and 0 deletions

View File

@@ -0,0 +1,481 @@
package main
import (
"encoding/json"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
httpSwagger "github.com/swaggo/http-swagger/v2"
)
var allowedOrigins []string
func init() {
raw := envOr("ALLOWED_ORIGINS", "https://dune-admin.layout.tools,http://localhost:5173")
for _, o := range strings.Split(raw, ",") {
if o = strings.TrimSpace(o); o != "" {
allowedOrigins = append(allowedOrigins, o)
}
}
}
func originAllowed(origin string) bool {
for _, o := range allowedOrigins {
if o == origin {
return true
}
}
return false
}
// newDirectorProxy builds the /director/ reverse-proxy handler for target. It
// strips the /director prefix before forwarding and routes upstream connections
// through dial (the executor tunnel), so the director is reachable from
// wherever the executor runs rather than the dune-admin host.
func newDirectorProxy(target *url.URL, dial func(network, addr string) (net.Conn, error)) http.HandlerFunc {
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Transport = httpTransportVia(dial)
return func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/director")
if r.URL.Path == "" {
r.URL.Path = "/"
}
r.Host = target.Host
proxy.ServeHTTP(w, r)
}
}
// originAllowedForRequest applies the explicit allowlist AND a same-host
// exception: a browser requesting from `http://172.16.12.59:9090/` against the
// dune-admin server running on the same host should not be considered cross-
// origin and never needs to be added to ALLOWED_ORIGINS.
//
// When Origin is absent (non-browser WebSocket clients), the request is allowed
// only if the TCP connection originates from a loopback address. r.RemoteAddr
// is used — not r.Host, which is a client-controlled header and can be spoofed.
func originAllowedForRequest(r *http.Request) bool {
origin := r.Header.Get("Origin")
if origin == "" {
// No Origin header: allow only actual loopback TCP connections.
remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return false
}
ip := net.ParseIP(remoteHost)
return ip != nil && ip.IsLoopback()
}
if u, err := url.Parse(origin); err == nil && u.Host == r.Host {
return true
}
return originAllowed(origin)
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
w.Header().Set("Vary", "Origin")
if origin != "" && originAllowedForRequest(r) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Private-Network", "true")
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func startServer(addr string) {
mux := http.NewServeMux()
// ── status ────────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/status", handleStatus)
mux.HandleFunc("POST /api/v1/reconnect", handleReconnect)
mux.HandleFunc("GET /api/v1/config", handleGetConfig)
mux.HandleFunc("POST /api/v1/config", handleSaveConfig)
mux.HandleFunc("GET /api/v1/update/check", handleUpdateCheck)
mux.HandleFunc("POST /api/v1/update/apply", handleUpdateApply)
// ── server settings (UserGame.ini / UserOverrides.ini) ────────────────
mux.HandleFunc("GET /api/v1/server-settings", handleGetServerSettings)
mux.HandleFunc("PUT /api/v1/server-settings", handleUpdateServerSettings)
mux.HandleFunc("PUT /api/v1/server-settings/raw", handleUpdateRawSection)
// ── director config (Battlegroup Director / map persistence — AMP) ────────
mux.HandleFunc("GET /api/v1/director-config", handleGetDirectorConfig)
mux.HandleFunc("PUT /api/v1/director-config", handleUpdateDirectorConfig)
// ── scheduled restarts ────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/scheduled-restarts", handleGetScheduledRestarts)
mux.HandleFunc("PUT /api/v1/scheduled-restarts", handleUpdateScheduledRestarts)
mux.HandleFunc("POST /api/v1/scheduled-restarts/skip-next", handleSkipNextRestart)
// Database backups (#150) — AMP-native pg_dump/restore + scheduling.
mux.HandleFunc("GET /api/v1/db-backups", handleDBBackupList)
mux.HandleFunc("POST /api/v1/db-backups", handleDBBackupCreate)
mux.HandleFunc("DELETE /api/v1/db-backups", handleDBBackupDelete)
mux.HandleFunc("GET /api/v1/db-backups/download", handleDBBackupDownload)
mux.HandleFunc("POST /api/v1/db-backups/restore", handleDBBackupRestore)
mux.HandleFunc("GET /api/v1/scheduled-backups", handleGetScheduledBackups)
mux.HandleFunc("PUT /api/v1/scheduled-backups", handleUpdateScheduledBackups)
// Web interfaces (#155) — operator-configurable links for the Server Health card.
mux.HandleFunc("GET /api/v1/web-interfaces", handleGetWebInterfaces)
mux.HandleFunc("PUT /api/v1/web-interfaces", handleUpdateWebInterfaces)
// ── battlegroup ───────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/battlegroup/status", handleBGStatus)
mux.HandleFunc("POST /api/v1/battlegroup/exec", handleBGExec)
mux.HandleFunc("GET /api/v1/battlegroup/pods", handleBGPods)
mux.HandleFunc("GET /api/v1/battlegroup/backup-files", handleBGBackupFiles)
mux.HandleFunc("GET /api/v1/battlegroup/backup-files/download", handleBGBackupDownload)
mux.HandleFunc("POST /api/v1/battlegroup/backup-files/upload", handleBGBackupUpload)
mux.HandleFunc("POST /api/v1/battlegroup/restore", handleBGRestore)
// ── players ───────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/players", handleGetPlayers)
mux.HandleFunc("GET /api/v1/players/online", handleGetOnlineState)
mux.HandleFunc("GET /api/v1/players/currency", handleGetCurrency)
mux.HandleFunc("GET /api/v1/players/factions", handleGetFactions)
mux.HandleFunc("GET /api/v1/players/specs", handleGetSpecs)
mux.HandleFunc("GET /api/v1/players/summary", handleGetPlayerSummary)
mux.HandleFunc("GET /api/v1/players/faction-trends", handleGetFactionTrends)
mux.HandleFunc("GET /api/v1/players/templates", handleGetTemplates)
mux.HandleFunc("GET /api/v1/players/{id}/inventory", handleGetInventory)
mux.HandleFunc("GET /api/v1/players/{id}/journey", handleGetJourney)
mux.HandleFunc("POST /api/v1/players/give-item", handleGiveItem)
mux.HandleFunc("POST /api/v1/players/give-items", handleGiveItems)
mux.HandleFunc("POST /api/v1/players/give-currency", handleGiveCurrency)
mux.HandleFunc("POST /api/v1/players/grant-live", handleGrantLive)
mux.HandleFunc("POST /api/v1/players/give-faction-rep", handleGiveFactionRep)
mux.HandleFunc("POST /api/v1/players/give-scrip", handleGiveScrip)
mux.HandleFunc("POST /api/v1/players/award-xp", handleAwardXP)
mux.HandleFunc("POST /api/v1/players/award-char-xp", handleAwardCharXP)
mux.HandleFunc("POST /api/v1/players/award-intel", handleAwardIntel)
mux.HandleFunc("POST /api/v1/players/rename", handleRenameCharacter)
mux.HandleFunc("GET /api/v1/players/{id}/tags", handleGetPlayerTags)
mux.HandleFunc("POST /api/v1/players/update-tags", handleUpdatePlayerTags)
mux.HandleFunc("POST /api/v1/players/returning-player-award", handleGrantReturningPlayerAward)
mux.HandleFunc("POST /api/v1/players/dismiss-returning-player-award", handleDismissReturningPlayerAward)
mux.HandleFunc("GET /api/v1/players/{id}/export", handleCharacterExport)
mux.HandleFunc("POST /api/v1/players/delete-account", handleDeleteAccount)
mux.HandleFunc("DELETE /api/v1/players/item/{id}", handleDeleteItem)
mux.HandleFunc("POST /api/v1/players/reset-spec", handleResetSpec)
mux.HandleFunc("POST /api/v1/players/set-faction-tier", handleSetFactionTier)
mux.HandleFunc("POST /api/v1/players/progression-unlock", handleProgressionUnlock)
mux.HandleFunc("POST /api/v1/players/progression-reverse", handleProgressionReverse)
mux.HandleFunc("GET /api/v1/progression/presets", handleListProgressionPresets)
mux.HandleFunc("POST /api/v1/players/progression/apply-preset", handleApplyProgressionPreset)
mux.HandleFunc("POST /api/v1/players/journey/complete", handleJourneyComplete)
mux.HandleFunc("POST /api/v1/players/journey/reset", handleJourneyReset)
mux.HandleFunc("POST /api/v1/players/journey/wipe", handleJourneyWipe)
mux.HandleFunc("POST /api/v1/players/contract/complete", handleCompleteContract)
mux.HandleFunc("POST /api/v1/players/contracts/complete", handleCompleteContracts)
mux.HandleFunc("POST /api/v1/players/contracts/reverse", handleReverseContracts)
mux.HandleFunc("POST /api/v1/players/grant-job-skills", handleGrantJobSkills)
mux.HandleFunc("POST /api/v1/players/reset-job-skills", handleResetJobSkills)
mux.HandleFunc("POST /api/v1/players/set-starter-class", handleSetStarterClass)
mux.HandleFunc("GET /api/v1/contracts", handleListContracts)
mux.HandleFunc("POST /api/v1/players/delete-tutorials", handleDeleteTutorials)
mux.HandleFunc("POST /api/v1/players/wipe-codex", handleWipeCodex)
mux.HandleFunc("GET /api/v1/players/{id}/char-xp", handleGetCharXP)
mux.HandleFunc("GET /api/v1/players/{id}/specs", handleGetPlayerSpecs)
mux.HandleFunc("GET /api/v1/players/{id}/keystones", handleGetPlayerKeystones)
mux.HandleFunc("POST /api/v1/players/grant-all-keystones", handleGrantAllKeystones)
mux.HandleFunc("POST /api/v1/players/reset-all-keystones", handleResetAllKeystones)
mux.HandleFunc("POST /api/v1/players/grant-max-spec", handleGrantMaxSpec)
mux.HandleFunc("GET /api/v1/players/{id}/vehicles", handleGetPlayerVehicles)
mux.HandleFunc("POST /api/v1/players/repair-item", handleRepairItem)
mux.HandleFunc("POST /api/v1/players/repair-gear", handleRepairPlayerGear)
mux.HandleFunc("POST /api/v1/players/repair-vehicle", handleRepairVehicle)
mux.HandleFunc("POST /api/v1/players/refuel-vehicle", handleRefuelVehicle)
mux.HandleFunc("GET /api/v1/players/partitions", handleGetPartitions)
mux.HandleFunc("POST /api/v1/players/teleport", handleTeleportPlayer)
mux.HandleFunc("POST /api/v1/players/teleport-coords", handleTeleportCoords)
mux.HandleFunc("GET /api/v1/players/{id}/position", handleGetPlayerPosition)
mux.HandleFunc("POST /api/v1/players/teleport-to-player", handleTeleportToPlayer)
mux.HandleFunc("GET /api/v1/players/{id}/events", handleGetPlayerEvents)
mux.HandleFunc("GET /api/v1/players/{id}/dungeons", handleGetPlayerDungeons)
mux.HandleFunc("GET /api/v1/players/{id}/stats", handleGetPlayerStats)
mux.HandleFunc("GET /api/v1/players/{id}/solaris-history", handleGetSolarisHistory)
mux.HandleFunc("GET /api/v1/players/{id}/session-history", handleGetSessionHistory)
mux.HandleFunc("GET /api/v1/players/{id}/stat-snapshot-history", handleGetStatSnapshotHistory)
// ── database ──────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/database/tables", handleDBTables)
mux.HandleFunc("GET /api/v1/database/describe", handleDBDescribe)
mux.HandleFunc("GET /api/v1/database/sample", handleDBSample)
mux.HandleFunc("GET /api/v1/database/search", handleDBSearch)
mux.HandleFunc("POST /api/v1/database/sql", handleDBSQL)
// ── locations (editable teleport/spawn points) ───────────────────────────
mux.HandleFunc("GET /api/v1/locations", handleListLocations)
mux.HandleFunc("POST /api/v1/locations", handleUpsertLocation)
mux.HandleFunc("PUT /api/v1/locations", handleRenameLocation)
mux.HandleFunc("DELETE /api/v1/locations", handleDeleteLocation)
// ── live map ────────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/map/markers", handleGetMapMarkers)
// ── logs ──────────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/logs/pods", handleLogPods)
mux.HandleFunc("GET /api/v1/logs/stream", handleLogStream)
mux.HandleFunc("GET /api/v1/logs/cheats", handleGetCheatLog)
// ── notifications ────────────────────────────────────────────────────────
mux.HandleFunc("POST /api/v1/notify", handleNotify)
// ── server commands (RabbitMQ, fire-and-forget) ───────────────────────────
mux.HandleFunc("POST /api/v1/players/kick", handleRMQKickPlayer)
mux.HandleFunc("POST /api/v1/players/fill-water", handleRMQFillWater)
mux.HandleFunc("POST /api/v1/players/set-skill-points", handleRMQSetSkillPoints)
mux.HandleFunc("POST /api/v1/players/clean-inventory", handleRMQCleanInventory)
mux.HandleFunc("POST /api/v1/players/reset-progression", handleRMQResetProgression)
mux.HandleFunc("POST /api/v1/players/set-skill-module", handleRMQSetSkillModule)
mux.HandleFunc("POST /api/v1/players/give-item-live", handleRMQGiveItem)
mux.HandleFunc("POST /api/v1/players/cheat-script", handleRMQCheatScript)
mux.HandleFunc("POST /api/v1/vehicles/spawn", handleRMQSpawnVehicle)
mux.HandleFunc("POST /api/v1/broadcast", handleRMQBroadcast)
mux.HandleFunc("POST /api/v1/broadcast/shutdown", handleRMQBroadcastShutdown)
mux.HandleFunc("POST /api/v1/chat/whisper", handleRMQWhisper)
mux.HandleFunc("GET /api/v1/players/{id}/player-ids", handlePlayerIDDebug)
// ── storage ───────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/storage", handleListStorage)
mux.HandleFunc("GET /api/v1/storage/{id}/items", handleGetStorageItems)
mux.HandleFunc("POST /api/v1/storage/{id}/give-item", handleGiveItemToStorage)
mux.HandleFunc("POST /api/v1/storage/{id}/give-items", handleGiveItemsToStorage)
mux.HandleFunc("GET /api/v1/storage/{id}/owner-debug", handleStorageOwnerDebug)
// ── blueprints ────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/blueprints", handleListBlueprints)
mux.HandleFunc("GET /api/v1/blueprints/{id}/export", handleExportBlueprint)
mux.HandleFunc("POST /api/v1/blueprints/import", handleImportBlueprint)
// ── bases ─────────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/bases", handleListBases)
mux.HandleFunc("GET /api/v1/bases/{id}/export", handleExportBase)
// ── guilds ──────────────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/guilds", handleListGuilds)
mux.HandleFunc("GET /api/v1/guilds/{id}", handleGetGuild)
mux.HandleFunc("PATCH /api/v1/guilds/{id}", handleUpdateGuild)
mux.HandleFunc("PUT /api/v1/guilds/{id}/members/{pid}/role", handleSetGuildMemberRole)
// ── landsraad (read-only) ─────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/landsraad", handleGetLandsraad)
// ── static data files (Go-first, CDN fallback on the frontend) ──────────
mux.HandleFunc("GET /api/v1/data/{file}", handleGetDataFile)
// ── market board ─────────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/market/items", handleMarketItems)
mux.HandleFunc("GET /api/v1/market/listings", handleMarketListings)
mux.HandleFunc("GET /api/v1/market/sales", handleMarketSales)
mux.HandleFunc("GET /api/v1/market/stats", handleMarketStats)
mux.HandleFunc("GET /api/v1/market/categories", handleMarketCategories)
mux.HandleFunc("GET /api/v1/market/catalog", handleMarketCatalog)
// ── market bot control ────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/market-bot/status", handleMarketBotStatus)
mux.HandleFunc("GET /api/v1/market-bot/config", handleMarketBotConfig)
mux.HandleFunc("PUT /api/v1/market-bot/config", handleMarketBotConfig)
mux.HandleFunc("POST /api/v1/market-bot/exec", handleMarketBotExec)
mux.HandleFunc("POST /api/v1/market-bot/cleanup", handleMarketBotCleanup)
mux.HandleFunc("GET /api/v1/market-bot/logs-ready", handleMarketBotLogsReady)
mux.HandleFunc("GET /api/v1/market-bot/logs", handleMarketBotLogs)
// ── welcome package ───────────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/welcome-package/config", handleGetWelcomeConfig)
mux.HandleFunc("PUT /api/v1/welcome-package/config", handlePutWelcomeConfig)
mux.HandleFunc("GET /api/v1/welcome-package/grants", handleGetWelcomeGrants)
mux.HandleFunc("POST /api/v1/welcome-package/retry", handleRetryWelcomeGrant)
mux.HandleFunc("POST /api/v1/welcome-package/revoke", handleRevokeWelcomeGrant)
mux.HandleFunc("POST /api/v1/welcome-package/run", handleRunWelcomePackage)
// ── give-items packs (operator-configurable pack library) ─────────────────
mux.HandleFunc("GET /api/v1/give-packs/config", handleGetGivePacksConfig)
mux.HandleFunc("PUT /api/v1/give-packs/config", handlePutGivePacksConfig)
// ── swagger UI ────────────────────────────────────────────────────────────
mux.Handle("/swagger/", httpSwagger.WrapHandler)
// ── director reverse proxy (universal, opt-in) ──────────────────────────
if loadedConfig.DirectorURL != "" {
if target, err := url.Parse(loadedConfig.DirectorURL); err == nil {
mux.HandleFunc("/director/", newDirectorProxy(target, dialThroughExecutor))
log.Printf("Proxying /director/ → %s", loadedConfig.DirectorURL)
}
}
// SPA frontend: prefer the embedded FS (release builds with -tags=embed),
// then fall back to a local dist directory for dev/AMP deployments.
if fsys := embeddedSPAFS(); fsys != nil {
log.Println("Serving frontend from embedded assets")
mux.Handle("/", spaHandlerFS(fsys))
} else {
for _, dir := range []string{"./dist", "./web/dist"} {
if info, err := os.Stat(dir); err == nil && info.IsDir() {
log.Printf("Serving frontend from %s", dir)
mux.Handle("/", spaHandler(dir))
break
}
}
}
srv := &http.Server{
Addr: addr,
Handler: corsMiddleware(mux),
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 10 * time.Minute, // backup/restore/download can take several minutes
IdleTimeout: 60 * time.Second,
}
log.Printf("dune-admin listening on %s", addr)
log.Fatal(srv.ListenAndServe())
}
// spaHandler serves static files from distDir, falling back to index.html
// for any path that does not match a real file (client-side routing).
func spaHandler(distDir string) http.Handler {
fileServer := http.FileServer(http.Dir(distDir))
cleanDist := filepath.Clean(distDir)
sep := string(filepath.Separator)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := filepath.Join(cleanDist, filepath.FromSlash(r.URL.Path))
if p != cleanDist && !strings.HasPrefix(p, cleanDist+sep) {
http.NotFound(w, r)
return
}
if _, err := os.Stat(p); err == nil { // #nosec G703 -- path validated against cleanDist prefix above
fileServer.ServeHTTP(w, r)
return
}
http.ServeFile(w, r, filepath.Join(cleanDist, "index.html"))
})
}
// spaHandlerFS serves an embedded http.FileSystem as a SPA, falling back to
// index.html for any path that does not map to a real file.
//
// Note: we open index.html directly instead of routing through http.FileServer
// because FileServer always 301-redirects "/index.html" → "/" which creates an
// infinite redirect loop (ERR_TOO_MANY_REDIRECTS) in browsers.
func spaHandlerFS(fsys http.FileSystem) http.Handler {
fileServer := http.FileServer(fsys)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && isRegularFile(fsys, r.URL.Path) {
fileServer.ServeHTTP(w, r)
return
}
f, err := fsys.Open("/index.html")
if err != nil {
http.NotFound(w, r)
return
}
defer func() { _ = f.Close() }()
fi, err := f.Stat()
if err != nil {
http.NotFound(w, r)
return
}
http.ServeContent(w, r, "index.html", fi.ModTime(), f)
})
}
func isRegularFile(fsys http.FileSystem, path string) bool {
f, err := fsys.Open(path)
if err != nil {
return false
}
defer func() { _ = f.Close() }()
fi, err := f.Stat()
return err == nil && !fi.IsDir()
}
// ── JSON helpers ──────────────────────────────────────────────────────────────
func jsonOK(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(v)
}
func jsonErr(w http.ResponseWriter, err error, code int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
}
func decode(r *http.Request, v any) error {
return json.NewDecoder(r.Body).Decode(v)
}
// handleStatus returns connection state and provider info.
//
// @Summary Return connection state and build info
// @Tags status
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/status [get]
func handleStatus(w http.ResponseWriter, r *http.Request) {
executorType := "none"
controlName := "none"
if globalExecutor != nil {
executorType = globalExecutor.Type()
}
if globalControl != nil {
controlName = globalControl.Name()
}
jsonOK(w, map[string]any{
"executor": executorType,
"control": controlName,
"ssh_connected": globalSSH != nil,
"db_connected": globalDB != nil,
"pod_ns": globalPodNS,
"pod_ip": globalPodIP,
"ssh_host": sshHost,
"db_host": dbHost,
"version": AppVersion,
"commit": GitCommit,
"build_time": BuildTime,
"director_url": loadedConfig.DirectorURL,
"listen_addr": loadedConfig.ListenAddr,
})
}
// handleReconnect tears down and re-establishes all connections.
//
// @Summary Tear down and re-establish all backend connections
// @Tags status
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Router /api/v1/reconnect [post]
func handleReconnect(w http.ResponseWriter, r *http.Request) {
if globalDB != nil {
globalDB.Close()
globalDB = nil
}
if globalExecutor != nil {
globalExecutor.Close()
globalExecutor = nil
}
globalSSH = nil
globalControl = nil
if err := connectAll(); err != nil {
jsonErr(w, err, 500)
return
}
handleStatus(w, r)
}