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,271 @@
[CmdletBinding()]
param(
[string]$VmUser = "dune",
[string]$VmHost = "192.168.0.72",
[string]$SshKeyPath = "",
[string]$KubeconfigPath = "$HOME/.kube/dune-external.yaml",
[string]$Image = "",
[string]$Namespace = "dune-admin",
[string]$Manifest = "deploy/k8s/dune-admin.rendered.yaml",
[switch]$SkipKubeconfig,
[switch]$SkipBuild,
[switch]$SkipImageImport,
[switch]$PortForward,
[switch]$NoPortForward
)
$ErrorActionPreference = "Stop"
function Require-Command([string]$Name) {
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "Missing required command: $Name"
}
}
function Invoke-Step([string]$Label, [scriptblock]$Action) {
Write-Host "==> $Label"
& $Action
}
function Get-SshOptionArgs([string]$KeyPath) {
$args = @(
"-o", "PreferredAuthentications=publickey,password",
"-o", "PubkeyAuthentication=yes",
"-o", "PasswordAuthentication=yes"
)
if ($KeyPath) {
$args += @("-i", $KeyPath, "-o", "IdentitiesOnly=yes")
}
return $args
}
$repoRoot = Split-Path -Parent $PSCommandPath
Set-Location $repoRoot
Require-Command kubectl
Require-Command ssh
Require-Command scp
Require-Command docker
Require-Command make
if (-not $SshKeyPath) {
$localKey = Join-Path $repoRoot "sshKey"
if (Test-Path $localKey) {
$SshKeyPath = $localKey
}
}
if (-not $Image) {
if ($SkipBuild -or $SkipImageImport) {
$Image = "dune-admin:local"
} else {
$Image = "dune-admin:local-$(Get-Date -Format 'yyyyMMddHHmmss')"
}
}
if ($SshKeyPath -and -not (Test-Path $SshKeyPath)) {
throw "SSH key not found: $SshKeyPath"
}
if ($SshKeyPath) {
Write-Host "Using SSH key: $SshKeyPath (fallback to password enabled)"
} else {
Write-Host "No SSH key provided/found; using password auth (or agent) for SSH."
}
$sshOpts = Get-SshOptionArgs -KeyPath $SshKeyPath
if (-not $SkipKubeconfig) {
Invoke-Step "Pulling kubeconfig from $VmUser@$VmHost" {
$dir = Split-Path -Parent $KubeconfigPath
if (-not (Test-Path $dir)) {
New-Item -Path $dir -ItemType Directory -Force | Out-Null
}
& ssh @sshOpts "$VmUser@$VmHost" "sudo cat /etc/rancher/k3s/k3s.yaml" | Out-File -FilePath $KubeconfigPath -Encoding utf8NoBOM
$raw = Get-Content -Path $KubeconfigPath -Raw
$raw.Replace("127.0.0.1", $VmHost) | Set-Content -Path $KubeconfigPath -Encoding utf8NoBOM
}
}
$env:KUBECONFIG = $KubeconfigPath
Write-Host "Using KUBECONFIG=$($env:KUBECONFIG)"
kubectl get nodes
if (-not $SkipBuild) {
Invoke-Step "Building image $Image" {
$AppVersion = if (Test-Path "$PSScriptRoot/VERSION") { Get-Content "$PSScriptRoot/VERSION" -Raw } else { "unknown" }
$AppVersion = $AppVersion.Trim()
$GitCommitRaw = git -C $PSScriptRoot rev-parse --short HEAD 2>$null
$GitCommit = if ($GitCommitRaw) { $GitCommitRaw } else { "unknown" }
$BuildTime = [System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
docker buildx build --platform linux/amd64 -f deploy/Dockerfile `
--build-arg APP_VERSION=$AppVersion `
--build-arg GIT_COMMIT=$GitCommit `
--build-arg BUILD_TIME=$BuildTime `
-t $Image --load .
}
}
if (-not $SkipImageImport) {
Invoke-Step "Importing image into k3s runtime on $VmHost" {
$tmpTar = Join-Path $env:TEMP "dune-admin-image.tar"
if (Test-Path $tmpTar) { Remove-Item $tmpTar -Force }
docker save -o $tmpTar $Image
& scp @sshOpts $tmpTar "$VmUser@${VmHost}:/tmp/dune-admin-image.tar"
& ssh @sshOpts "$VmUser@$VmHost" "sudo k3s ctr images import /tmp/dune-admin-image.tar && rm -f /tmp/dune-admin-image.tar"
Remove-Item $tmpTar -Force
}
}
Invoke-Step "Rendering manifest" {
make render-k8s
}
if (-not (Test-Path $Manifest)) {
throw "Manifest not found: $Manifest"
}
$manifestText = Get-Content -Path $Manifest -Raw
# Patch all image: fields that reference a dune-admin image (main container
# and seed-binary init container both need the same locally-built image tag).
$patched = [regex]::Replace($manifestText, '(?m)^(\s*image:\s*)(?:ghcr\.io/icehunter/dune-admin|dune-admin)\S*$', "`$1$Image")
if ($patched -eq $manifestText) {
throw "No dune-admin image: field found to patch in manifest"
}
$dbHostOverride = ""
$controlNsMatch = [regex]::Match($patched, '(?m)^\s*control_namespace:\s*"?([^\s"]+)"?\s*$')
if ($controlNsMatch.Success) {
$controlNs = $controlNsMatch.Groups[1].Value
$svcRows = kubectl -n $controlNs get svc -o jsonpath='{range .items[*]}{.metadata.name}{"`t"}{range .spec.ports[*]}{.port}{" "}{end}{"`n"}{end}' 2>$null
$dbSvcRow = $svcRows | Where-Object { $_ -match '(^|[ \t])15432([ \t]|$)' } | Select-Object -First 1
if ($dbSvcRow) {
$dbSvc = ($dbSvcRow -split '\s+')[0]
if ($dbSvc) {
$dbHostOverride = "$dbSvc.$controlNs.svc.cluster.local"
Write-Host "Using in-cluster DB host: $dbHostOverride"
}
}
}
$patched = [regex]::Replace($patched, '(?m)^(\s*CONTROL:\s*).*$', '${1}"local"')
$patched = [regex]::Replace($patched, '(?m)^\s*cmd_(status|start|stop|restart):\s*.*\r?\n', '')
$patched = [regex]::Replace($patched, '(?m)^(\s*control:\s*).*$', '${1}local')
if ($dbHostOverride) {
$patched = [regex]::Replace($patched, '(?m)^(\s*DB_HOST:\s*).*$', '${1}"' + $dbHostOverride + '"')
$patched = [regex]::Replace($patched, '(?m)^(\s*db_host:\s*).*$', '${1}' + $dbHostOverride)
}
$patched = [regex]::Replace($patched, '(?m)^\s*ssh_host:\s*.*\r?\n', '')
$patched = [regex]::Replace($patched, '(?m)^\s*ssh_user:\s*.*\r?\n', '')
$patched = [regex]::Replace($patched, '(?m)^\s*ssh_key:\s*.*\r?\n', '')
$patched = [regex]::Replace($patched, '(?m)^(\s*MARKET_BOT_ENABLED:\s*).*$', '${1}"true"')
$patched = [regex]::Replace($patched, '(?m)^(\s*market_bot_enabled:\s*).*$', '${1}true')
$patched = [regex]::Replace($patched, '(?m)^(\s*market_bot_item_data:\s*).*$', '${1}/app/item-data.json')
$patched = [regex]::Replace($patched, '(?m)^(\s*market_bot_cache_db:\s*).*$', '${1}/data/market-bot-cache.db')
$patched | Set-Content -Path $Manifest -Encoding utf8NoBOM
Invoke-Step "Applying manifest" {
kubectl apply -f $Manifest
if ($controlNsMatch.Success) {
$rbac = @"
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dune-admin-runtime
namespace: $controlNs
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "endpoints", "persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create", "get"]
- apiGroups: ["igw.funcom.com"]
resources: ["battlegroups", "serverstats"]
verbs: ["get", "list", "watch", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dune-admin-runtime
namespace: $controlNs
subjects:
- kind: ServiceAccount
name: default
namespace: $Namespace
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dune-admin-runtime
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dune-admin-operators-logs
namespace: funcom-operators
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dune-admin-operators-logs
namespace: funcom-operators
subjects:
- kind: ServiceAccount
name: default
namespace: $Namespace
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dune-admin-operators-logs
"@
$rbac | kubectl apply -f -
}
kubectl -n $Namespace rollout restart deploy/dune-admin
kubectl -n $Namespace rollout status deploy/dune-admin
kubectl -n $Namespace get pods,svc
}
Invoke-Step "In-cluster health checks (fast-fail, no pod waits)" {
$stalePods = kubectl -n $Namespace get pods --no-headers 2>$null | `
Where-Object { $_ -match '^(curl|curl-check-)' } | `
ForEach-Object { ($_ -split '\s+')[0] }
foreach ($p in $stalePods) {
if ($p) { kubectl -n $Namespace delete pod $p --ignore-not-found | Out-Null }
}
$statusPath = "/api/v1/namespaces/$Namespace/services/http:dune-admin:8080/proxy/api/v1/status"
$botPath = "/api/v1/namespaces/$Namespace/services/http:dune-admin:8080/proxy/api/v1/market-bot/status"
$bgPath = "/api/v1/namespaces/$Namespace/services/http:dune-admin:8080/proxy/api/v1/battlegroup/status"
$healthOk = $false
$lastStatus = ""
$lastBot = ""
$lastBg = ""
for ($i = 1; $i -le 30; $i++) {
$lastStatus = (kubectl --request-timeout=5s get --raw $statusPath 2>$null)
$lastBot = (kubectl --request-timeout=5s get --raw $botPath 2>$null)
$lastBg = (kubectl --request-timeout=5s get --raw $bgPath 2>$null)
$botOk = $lastBot -match '"enabled":true'
$bgOk = $lastBg -notmatch "does not support GetStatus"
if ($lastStatus -and $botOk -and $bgOk) {
Write-Host $lastBot
$healthOk = $true
break
}
if (($i % 5) -eq 0) {
Write-Host "Health check retry $i/30..."
}
Start-Sleep -Seconds 1
}
if (-not $healthOk) {
throw "health check failed: API or embedded market-bot not ready`nlast /api/v1/status: $lastStatus`nlast /api/v1/market-bot/status: $lastBot`nlast /api/v1/battlegroup/status: $lastBg"
}
}
if ($PortForward -and -not $NoPortForward) {
Write-Host "Opening API port-forward at http://127.0.0.1:8080 ..."
kubectl -n $Namespace port-forward svc/dune-admin 8080:8080
} else {
Write-Host "Deploy complete. Run ./listen.ps1 to open API port-forward."
}