feat(server): real agent credentials + agent.toml setup; per-game config honesty
Server page Host-agent panel now fetches GET /api/servers/agent- credentials and renders the real agent.toml (license UUID, nats_user, nats_password) instead of the broken LICENSE_ID=license_key env commands that would never connect. Password masked by default with a reveal toggle; copy-to-clipboard uses the real value. Setup commands point at --config /etc/corrosion/agent.toml. Configuration panel: World size / Current seed (Rust-only Facepunch concepts) gated behind isRust; Conan/Soulmask/Dune get an honest note pointing to the File Manager for their real config files instead of fake Rust fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useServerStore } from '@/stores/server'
|
import { useServerStore } from '@/stores/server'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
|
||||||
import { useToastStore } from '@/stores/toast'
|
import { useToastStore } from '@/stores/toast'
|
||||||
import { useThemeGame } from '@/composables/useThemeGame'
|
import { useThemeGame } from '@/composables/useThemeGame'
|
||||||
import { useGameProfile } from '@/config/gameProfiles'
|
import { useGameProfile } from '@/config/gameProfiles'
|
||||||
import type { DeploymentConfig, DeploymentStatus } from '@/types'
|
import type { DeploymentConfig, DeploymentStatus } from '@/types'
|
||||||
import { useWebSocket } from '@/composables/useWebSocket'
|
import { useWebSocket } from '@/composables/useWebSocket'
|
||||||
|
import { useApi } from '@/composables/useApi'
|
||||||
import Panel from '@/components/ds/data/Panel.vue'
|
import Panel from '@/components/ds/data/Panel.vue'
|
||||||
import Button from '@/components/ds/core/Button.vue'
|
import Button from '@/components/ds/core/Button.vue'
|
||||||
import Badge from '@/components/ds/core/Badge.vue'
|
import Badge from '@/components/ds/core/Badge.vue'
|
||||||
@@ -19,7 +19,6 @@ import Switch from '@/components/ds/forms/Switch.vue'
|
|||||||
import Tabs from '@/components/ds/navigation/Tabs.vue'
|
import Tabs from '@/components/ds/navigation/Tabs.vue'
|
||||||
|
|
||||||
const server = useServerStore()
|
const server = useServerStore()
|
||||||
const auth = useAuthStore()
|
|
||||||
const toast = useToastStore()
|
const toast = useToastStore()
|
||||||
const { activeGame } = useThemeGame()
|
const { activeGame } = useThemeGame()
|
||||||
|
|
||||||
@@ -66,6 +65,18 @@ const deployLoading = ref(false)
|
|||||||
const oxideStatus = ref<{ stage: string; progress: number; message: string; error?: string } | null>(null)
|
const oxideStatus = ref<{ stage: string; progress: number; message: string; error?: string } | null>(null)
|
||||||
const isInstallingOxide = ref(false)
|
const isInstallingOxide = ref(false)
|
||||||
|
|
||||||
|
// Agent credentials (fetched from /api/servers/agent-credentials on mount)
|
||||||
|
interface AgentCreds {
|
||||||
|
license_id: string
|
||||||
|
nats_user: string
|
||||||
|
nats_password: string
|
||||||
|
nats_url: string
|
||||||
|
}
|
||||||
|
const agentCreds = ref<AgentCreds | null>(null)
|
||||||
|
const showCreds = ref(false)
|
||||||
|
// Ref for the TOML block copy button
|
||||||
|
const tomlCopied = ref(false)
|
||||||
|
|
||||||
const deployForm = ref<DeploymentConfig>({
|
const deployForm = ref<DeploymentConfig>({
|
||||||
server_name: 'My Rust Server',
|
server_name: 'My Rust Server',
|
||||||
max_players: 100,
|
max_players: 100,
|
||||||
@@ -97,25 +108,62 @@ const agentLastSeenLabel = computed(() => {
|
|||||||
return d.toLocaleDateString()
|
return d.toLocaleDateString()
|
||||||
})
|
})
|
||||||
|
|
||||||
const licenseKey = computed(() => auth.license?.license_key || 'YOUR-LICENSE-KEY')
|
|
||||||
|
|
||||||
const linuxCommands = computed(() => `# Download the agent
|
const linuxCommands = computed(() => `# Download the agent
|
||||||
curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64
|
curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64
|
||||||
chmod +x corrosion-host-agent-linux-amd64
|
chmod +x corrosion-host-agent-linux-amd64
|
||||||
|
|
||||||
# Start with your license key
|
# Write /etc/corrosion/agent.toml (see config block below), then run:
|
||||||
export LICENSE_ID="${licenseKey.value}"
|
sudo mkdir -p /etc/corrosion
|
||||||
export NATS_URL="nats://nats.corrosionmgmt.com:4222"
|
sudo ./corrosion-host-agent-linux-amd64 --config /etc/corrosion/agent.toml`)
|
||||||
./corrosion-host-agent-linux-amd64`)
|
|
||||||
|
|
||||||
const windowsCommands = computed(() => `# Requires PowerShell (not Command Prompt)
|
const windowsCommands = computed(() => `# Requires PowerShell (not Command Prompt)
|
||||||
# Download the agent
|
# Download the agent
|
||||||
Invoke-WebRequest -Uri "https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe" -OutFile "corrosion-host-agent-windows-amd64.exe"
|
Invoke-WebRequest -Uri "https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe" -OutFile "corrosion-host-agent-windows-amd64.exe"
|
||||||
|
|
||||||
# Start with your license key
|
# Write C:\\ProgramData\\Corrosion\\agent.toml (see config block below), then run:
|
||||||
$env:LICENSE_ID="${licenseKey.value}"
|
New-Item -ItemType Directory -Force -Path "C:\\ProgramData\\Corrosion"
|
||||||
$env:NATS_URL="nats://nats.corrosionmgmt.com:4222"
|
.\\corrosion-host-agent-windows-amd64.exe --config "C:\\ProgramData\\Corrosion\\agent.toml"`)
|
||||||
.\\corrosion-host-agent-windows-amd64.exe`)
|
|
||||||
|
const agentTomlConfig = computed(() => {
|
||||||
|
const c = agentCreds.value
|
||||||
|
const licenseId = c?.license_id ?? 'YOUR-LICENSE-ID'
|
||||||
|
const natsUrl = c?.nats_url ?? 'nats://nats.corrosionmgmt.com:4222'
|
||||||
|
const natsUser = c?.nats_user ?? 'YOUR-LICENSE-ID'
|
||||||
|
const natsPassword = c ? (showCreds.value ? c.nats_password : '••••••••') : 'YOUR-AGENT-TOKEN'
|
||||||
|
return `[agent]
|
||||||
|
license_id = "${licenseId}"
|
||||||
|
nats_url = "${natsUrl}"
|
||||||
|
nats_user = "${natsUser}"
|
||||||
|
nats_password = "${natsPassword}"
|
||||||
|
heartbeat_seconds = 60
|
||||||
|
|
||||||
|
[[instance]]
|
||||||
|
id = "rust-main"
|
||||||
|
game = "rust"
|
||||||
|
root = "/opt/rustserver"
|
||||||
|
label = "My Server"`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Returns the raw (unmasked) TOML for clipboard — always use actual password if available
|
||||||
|
const agentTomlConfigRaw = computed(() => {
|
||||||
|
const c = agentCreds.value
|
||||||
|
const licenseId = c?.license_id ?? 'YOUR-LICENSE-ID'
|
||||||
|
const natsUrl = c?.nats_url ?? 'nats://nats.corrosionmgmt.com:4222'
|
||||||
|
const natsUser = c?.nats_user ?? 'YOUR-LICENSE-ID'
|
||||||
|
const natsPassword = c?.nats_password ?? 'YOUR-AGENT-TOKEN'
|
||||||
|
return `[agent]
|
||||||
|
license_id = "${licenseId}"
|
||||||
|
nats_url = "${natsUrl}"
|
||||||
|
nats_user = "${natsUser}"
|
||||||
|
nats_password = "${natsPassword}"
|
||||||
|
heartbeat_seconds = 60
|
||||||
|
|
||||||
|
[[instance]]
|
||||||
|
id = "rust-main"
|
||||||
|
game = "rust"
|
||||||
|
root = "/opt/rustserver"
|
||||||
|
label = "My Server"`
|
||||||
|
})
|
||||||
|
|
||||||
async function copySetupCommands() {
|
async function copySetupCommands() {
|
||||||
try {
|
try {
|
||||||
@@ -133,6 +181,16 @@ async function copySetupCommands() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyTomlConfig() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(agentTomlConfigRaw.value)
|
||||||
|
tomlCopied.value = true
|
||||||
|
setTimeout(() => { tomlCopied.value = false }, 2000)
|
||||||
|
} catch {
|
||||||
|
// Clipboard API unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function startDeploy() {
|
async function startDeploy() {
|
||||||
if (!deployForm.value.rcon_password || deployForm.value.rcon_password.length < 6) return
|
if (!deployForm.value.rcon_password || deployForm.value.rcon_password.length < 6) return
|
||||||
deployLoading.value = true
|
deployLoading.value = true
|
||||||
@@ -297,6 +355,14 @@ onMounted(async () => {
|
|||||||
await server.fetchServer()
|
await server.fetchServer()
|
||||||
loadFormFromConfig()
|
loadFormFromConfig()
|
||||||
|
|
||||||
|
// Fetch agent credentials for the TOML config block (leave null on error — honest fallback)
|
||||||
|
try {
|
||||||
|
const creds = await useApi().get<AgentCreds | null>('/servers/agent-credentials')
|
||||||
|
agentCreds.value = creds
|
||||||
|
} catch {
|
||||||
|
agentCreds.value = null
|
||||||
|
}
|
||||||
|
|
||||||
const ws = useWebSocket()
|
const ws = useWebSocket()
|
||||||
ws.subscribe((msg) => {
|
ws.subscribe((msg) => {
|
||||||
if (msg.type === 'event' && msg.event === 'deploy_status') {
|
if (msg.type === 'event' && msg.event === 'deploy_status') {
|
||||||
@@ -463,10 +529,9 @@ onMounted(async () => {
|
|||||||
<p class="sv__cmt"># Download the agent</p>
|
<p class="sv__cmt"># Download the agent</p>
|
||||||
<p>curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64</p>
|
<p>curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64</p>
|
||||||
<p>chmod +x corrosion-host-agent-linux-amd64</p>
|
<p>chmod +x corrosion-host-agent-linux-amd64</p>
|
||||||
<p class="sv__cmt sv__mt"># Start with your license key</p>
|
<p class="sv__cmt sv__mt"># Write /etc/corrosion/agent.toml (see config block below), then run:</p>
|
||||||
<p>export LICENSE_ID=<span class="sv__accent">"{{ licenseKey }}"</span></p>
|
<p>sudo mkdir -p /etc/corrosion</p>
|
||||||
<p>export NATS_URL=<span class="sv__accent">"nats://nats.corrosionmgmt.com:4222"</span></p>
|
<p>sudo ./corrosion-host-agent-linux-amd64 --config <span class="sv__accent">/etc/corrosion/agent.toml</span></p>
|
||||||
<p>./corrosion-host-agent-linux-amd64</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Windows commands -->
|
<!-- Windows commands -->
|
||||||
@@ -474,11 +539,38 @@ onMounted(async () => {
|
|||||||
<p class="sv__cmt"># Requires PowerShell (not Command Prompt)</p>
|
<p class="sv__cmt"># Requires PowerShell (not Command Prompt)</p>
|
||||||
<p class="sv__cmt"># Download the agent</p>
|
<p class="sv__cmt"># Download the agent</p>
|
||||||
<p>Invoke-WebRequest -Uri <span class="sv__accent">"https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe"</span> -OutFile <span class="sv__accent">"corrosion-host-agent-windows-amd64.exe"</span></p>
|
<p>Invoke-WebRequest -Uri <span class="sv__accent">"https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe"</span> -OutFile <span class="sv__accent">"corrosion-host-agent-windows-amd64.exe"</span></p>
|
||||||
<p class="sv__cmt sv__mt"># Start with your license key</p>
|
<p class="sv__cmt sv__mt"># Write C:\ProgramData\Corrosion\agent.toml (see config block below), then run:</p>
|
||||||
<p>$env:LICENSE_ID=<span class="sv__accent">"{{ licenseKey }}"</span></p>
|
<p>New-Item -ItemType Directory -Force -Path <span class="sv__accent">"C:\ProgramData\Corrosion"</span></p>
|
||||||
<p>$env:NATS_URL=<span class="sv__accent">"nats://nats.corrosionmgmt.com:4222"</span></p>
|
<p>.\corrosion-host-agent-windows-amd64.exe --config <span class="sv__accent">"C:\ProgramData\Corrosion\agent.toml"</span></p>
|
||||||
<p>.\corrosion-host-agent-windows-amd64.exe</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Agent configuration (agent.toml) -->
|
||||||
|
<div class="sv__section-head sv__mt">
|
||||||
|
<Icon name="file-text" :size="14" />
|
||||||
|
<span>Agent configuration (agent.toml)</span>
|
||||||
|
</div>
|
||||||
|
<div class="sv__setup-head">
|
||||||
|
<div class="sv__toml-reveal">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
:icon="showCreds ? 'eye-off' : 'eye'"
|
||||||
|
@click="showCreds = !showCreds"
|
||||||
|
>{{ showCreds ? 'Hide credentials' : 'Reveal credentials' }}</Button>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
:icon="tomlCopied ? 'check' : 'copy'"
|
||||||
|
@click="copyTomlConfig"
|
||||||
|
>{{ tomlCopied ? 'Copied' : 'Copy' }}</Button>
|
||||||
|
</div>
|
||||||
|
<div class="sv__codeblock">
|
||||||
|
<pre class="sv__pre">{{ agentTomlConfig }}</pre>
|
||||||
|
</div>
|
||||||
|
<Alert v-if="!agentCreds" tone="warn" class="sv__mt">
|
||||||
|
Could not load credentials from server. Copy this config and replace the placeholders with values from your Corrosion dashboard settings.
|
||||||
|
</Alert>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<!-- Deploy Server — Rust only (SteamCMD path). Other games use docker-compose or external tooling. -->
|
<!-- Deploy Server — Rust only (SteamCMD path). Other games use docker-compose or external tooling. -->
|
||||||
@@ -778,15 +870,21 @@ onMounted(async () => {
|
|||||||
<div class="sv__field-label">Max players</div>
|
<div class="sv__field-label">Max players</div>
|
||||||
<div class="sv__field-val sv__field-val--mono">{{ server.config?.max_players ?? '—' }}</div>
|
<div class="sv__field-val sv__field-val--mono">{{ server.config?.max_players ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sv__field">
|
<!-- Rust-only: world size and seed are Facepunch/procgen concepts -->
|
||||||
|
<div v-if="isRust" class="sv__field">
|
||||||
<div class="sv__field-label">World size</div>
|
<div class="sv__field-label">World size</div>
|
||||||
<div class="sv__field-val sv__field-val--mono">{{ server.config?.world_size ?? '—' }}</div>
|
<div class="sv__field-val sv__field-val--mono">{{ server.config?.world_size ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sv__field">
|
<div v-if="isRust" class="sv__field">
|
||||||
<div class="sv__field-label">Current seed</div>
|
<div class="sv__field-label">Current seed</div>
|
||||||
<div class="sv__field-val sv__field-val--mono">{{ server.config?.current_seed ?? '—' }}</div>
|
<div class="sv__field-val sv__field-val--mono">{{ server.config?.current_seed ?? '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Non-Rust: game-specific settings live in config files on the host -->
|
||||||
|
<Alert v-if="!editMode && !isRust" tone="neutral" class="sv__mt">
|
||||||
|
Game-specific settings for {{ profile.label }} live in config files on the host — manage them in the
|
||||||
|
<Button variant="ghost" size="sm" icon="folder" @click="$router.push('/files')">File Manager</Button>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<!-- Edit mode -->
|
<!-- Edit mode -->
|
||||||
<form v-else @submit.prevent="saveConfig" class="sv__form">
|
<form v-else @submit.prevent="saveConfig" class="sv__form">
|
||||||
@@ -803,7 +901,9 @@ onMounted(async () => {
|
|||||||
type="number"
|
type="number"
|
||||||
:mono="true"
|
:mono="true"
|
||||||
/>
|
/>
|
||||||
|
<!-- Rust-only: world size and seed are Facepunch/procgen concepts -->
|
||||||
<Input
|
<Input
|
||||||
|
v-if="isRust"
|
||||||
:model-value="String(form.world_size)"
|
:model-value="String(form.world_size)"
|
||||||
@update:model-value="v => { form.world_size = Number(v) }"
|
@update:model-value="v => { form.world_size = Number(v) }"
|
||||||
label="World size"
|
label="World size"
|
||||||
@@ -811,6 +911,7 @@ onMounted(async () => {
|
|||||||
:mono="true"
|
:mono="true"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
v-if="isRust"
|
||||||
:model-value="String(form.current_seed)"
|
:model-value="String(form.current_seed)"
|
||||||
@update:model-value="v => { form.current_seed = Number(v) }"
|
@update:model-value="v => { form.current_seed = Number(v) }"
|
||||||
label="Current seed"
|
label="Current seed"
|
||||||
@@ -818,6 +919,11 @@ onMounted(async () => {
|
|||||||
:mono="true"
|
:mono="true"
|
||||||
class="sv__col-span2"
|
class="sv__col-span2"
|
||||||
/>
|
/>
|
||||||
|
<!-- Non-Rust: redirect to file manager for game-specific config -->
|
||||||
|
<Alert v-if="!isRust" tone="neutral" class="sv__col-span2">
|
||||||
|
Game-specific settings for {{ profile.label }} live in config files on the host — manage them in the
|
||||||
|
<Button variant="ghost" size="sm" icon="folder" @click="$router.push('/files')">File Manager</Button>
|
||||||
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Panel>
|
</Panel>
|
||||||
@@ -931,6 +1037,12 @@ onMounted(async () => {
|
|||||||
/* Setup head */
|
/* Setup head */
|
||||||
.sv__setup-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
.sv__setup-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
||||||
|
|
||||||
|
/* TOML reveal row */
|
||||||
|
.sv__toml-reveal { display: flex; align-items: center; }
|
||||||
|
|
||||||
|
/* Pre inside codeblock — preserve whitespace, no extra margin */
|
||||||
|
.sv__pre { margin: 0; white-space: pre; }
|
||||||
|
|
||||||
/* Code block */
|
/* Code block */
|
||||||
.sv__codeblock {
|
.sv__codeblock {
|
||||||
background: var(--surface-inset); border-radius: var(--radius-md);
|
background: var(--surface-inset); border-radius: var(--radius-md);
|
||||||
|
|||||||
Reference in New Issue
Block a user