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>
This commit is contained in:
108
docs/reference-repos/adainrivers/app/src/services/management.ts
Normal file
108
docs/reference-repos/adainrivers/app/src/services/management.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import type {
|
||||
ClusterDto,
|
||||
CommandSpec,
|
||||
CronPreviewResult,
|
||||
DumpPruneItem,
|
||||
DumpPruneResult,
|
||||
DumpPruneTarget,
|
||||
HealthDto,
|
||||
HistoryDto,
|
||||
ItemDto,
|
||||
LogDto,
|
||||
ManagementConnRequest,
|
||||
ManagementInstallRequest,
|
||||
ManagementInstallResult,
|
||||
ManagementServiceStatus,
|
||||
PlayerDto,
|
||||
PlayerLocationDto,
|
||||
PublishResultDto,
|
||||
RunDto,
|
||||
JourneyNodeDto,
|
||||
ScheduleConfig,
|
||||
ScheduleConfigUpdate,
|
||||
SkillModuleDto,
|
||||
VehicleDto,
|
||||
WelcomeGrantDto,
|
||||
XpEventTagDto,
|
||||
} from "../types/management";
|
||||
|
||||
export const managementService = {
|
||||
install: (req: ManagementInstallRequest) =>
|
||||
invoke<ManagementInstallResult>("install_management_service", { request: req }),
|
||||
uninstall: (req: ManagementConnRequest) =>
|
||||
invoke<void>("uninstall_management_service", { request: req }),
|
||||
status: (req: ManagementConnRequest) =>
|
||||
invoke<ManagementServiceStatus>("management_service_status", { request: req }),
|
||||
bundledVersion: () => invoke<string>("management_service_bundled_version"),
|
||||
restart: (req: ManagementConnRequest) =>
|
||||
invoke<void>("restart_management_service", { request: req }),
|
||||
};
|
||||
|
||||
export const managementApi = {
|
||||
health: (tunnelId: string) => invoke<HealthDto>("ms_health", { tunnelId }),
|
||||
listRuns: (tunnelId: string, limit?: number, task?: string) =>
|
||||
invoke<RunDto[]>("ms_list_runs", { tunnelId, limit, task }),
|
||||
listLogs: (tunnelId: string, limit?: number, runId?: number) =>
|
||||
invoke<LogDto[]>("ms_list_logs", { tunnelId, limit, runId }),
|
||||
triggerRun: (tunnelId: string, task: string, options?: Record<string, unknown>) =>
|
||||
invoke<{ ok: boolean; task: string }>("ms_trigger_run", { tunnelId, task, options }),
|
||||
listCommands: (tunnelId: string) =>
|
||||
invoke<CommandSpec[]>("ms_list_commands", { tunnelId }),
|
||||
searchItems: (tunnelId: string, q: string, limit?: number) =>
|
||||
invoke<ItemDto[]>("ms_search_items", { tunnelId, q, limit }),
|
||||
searchVehicles: (tunnelId: string, q: string, limit?: number) =>
|
||||
invoke<VehicleDto[]>("ms_search_vehicles", { tunnelId, q, limit }),
|
||||
searchSkillModules: (tunnelId: string, q: string, limit?: number) =>
|
||||
invoke<SkillModuleDto[]>("ms_search_skill_modules", { tunnelId, q, limit }),
|
||||
searchJourneyNodes: (tunnelId: string, q: string, limit?: number) =>
|
||||
invoke<JourneyNodeDto[]>("ms_search_journey_nodes", { tunnelId, q, limit }),
|
||||
searchXpEventTags: (tunnelId: string, q: string, limit?: number) =>
|
||||
invoke<XpEventTagDto[]>("ms_search_xp_event_tags", { tunnelId, q, limit }),
|
||||
getConfig: (tunnelId: string) => invoke<ScheduleConfig>("ms_get_config", { tunnelId }),
|
||||
setConfig: (tunnelId: string, config: ScheduleConfigUpdate) =>
|
||||
invoke<{ ok: boolean }>("ms_set_config", { tunnelId, config }),
|
||||
listTimezones: (tunnelId: string) => invoke<string[]>("ms_list_timezones", { tunnelId }),
|
||||
cronPreview: (tunnelId: string, expr: string, count?: number) =>
|
||||
invoke<CronPreviewResult>("ms_cron_preview", { tunnelId, expr, count }),
|
||||
dumpPrunePreview: (tunnelId: string) =>
|
||||
invoke<DumpPruneItem[]>("ms_dump_prune_preview", { tunnelId }),
|
||||
dumpPruneExecute: (tunnelId: string, items: DumpPruneTarget[]) =>
|
||||
invoke<DumpPruneResult>("ms_dump_prune_execute", { tunnelId, items }),
|
||||
searchPlayers: (tunnelId: string, q: string, limit?: number) =>
|
||||
invoke<PlayerDto[]>("ms_search_players", { tunnelId, q, limit }),
|
||||
playerLocation: (tunnelId: string, flsId: string) =>
|
||||
invoke<PlayerLocationDto>("ms_player_location", { tunnelId, flsId }),
|
||||
cluster: (tunnelId: string) => invoke<ClusterDto>("ms_cluster", { tunnelId }),
|
||||
history: (tunnelId: string, limit?: number) =>
|
||||
invoke<HistoryDto[]>("ms_history", { tunnelId, limit }),
|
||||
welcomeGrants: (tunnelId: string, limit?: number) =>
|
||||
invoke<WelcomeGrantDto[]>("ms_welcome_grants", { tunnelId, limit }),
|
||||
retryWelcomeGrant: (
|
||||
tunnelId: string,
|
||||
playerId: string,
|
||||
packageVersion: string,
|
||||
accountId: number,
|
||||
) =>
|
||||
invoke<{ ok: boolean; removed: number }>("ms_welcome_grant_retry", {
|
||||
tunnelId,
|
||||
playerId,
|
||||
packageVersion,
|
||||
accountId,
|
||||
}),
|
||||
sendWelcomeWhisper: (
|
||||
tunnelId: string,
|
||||
recipientPlayerId: string,
|
||||
sourcePlayerId: string,
|
||||
message: string,
|
||||
) =>
|
||||
invoke<PublishResultDto>("ms_welcome_whisper", {
|
||||
tunnelId,
|
||||
recipientPlayerId,
|
||||
sourcePlayerId,
|
||||
message,
|
||||
}),
|
||||
publish: (tunnelId: string, command: string, fields: Record<string, unknown>) =>
|
||||
invoke<PublishResultDto>("ms_publish", { tunnelId, command, fields }),
|
||||
};
|
||||
142
docs/reference-repos/adainrivers/app/src/services/storage.ts
Normal file
142
docs/reference-repos/adainrivers/app/src/services/storage.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import type { RemoteServerRecord } from "../types/server";
|
||||
import type { CustomTunnelDef } from "../types/tunnel";
|
||||
import type { ActivePage, ServerSubPage } from "../types/ui";
|
||||
import { SERVER_SUB_PAGES } from "../types/ui";
|
||||
|
||||
const remoteServersStorageKey = "dune-manager.remote-servers";
|
||||
const activePageStorageKey = "dune-manager.active-page";
|
||||
const logSidebarStorageKey = "dune-manager.log-sidebar";
|
||||
|
||||
export function isRemoteServerRecord(value: unknown): value is RemoteServerRecord {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const record = value as Partial<RemoteServerRecord>;
|
||||
return (
|
||||
record.type === "ubuntu" &&
|
||||
typeof record.id === "string" &&
|
||||
typeof record.name === "string" &&
|
||||
typeof record.host === "string" &&
|
||||
typeof record.keyPath === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function readRemoteServers(): RemoteServerRecord[] {
|
||||
const text = window.localStorage.getItem(remoteServersStorageKey);
|
||||
if (!text) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed.filter(isRemoteServerRecord);
|
||||
} catch {
|
||||
window.localStorage.removeItem(remoteServersStorageKey);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeRemoteServers(
|
||||
current: RemoteServerRecord[],
|
||||
incoming: RemoteServerRecord[],
|
||||
): RemoteServerRecord[] {
|
||||
const byId = new Map(current.map((server) => [server.id, server]));
|
||||
for (const server of incoming) {
|
||||
byId.set(server.id, { ...byId.get(server.id), ...server });
|
||||
}
|
||||
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
export function persistRemoteServers(servers: RemoteServerRecord[]): RemoteServerRecord[] {
|
||||
const unique = mergeRemoteServers([], servers);
|
||||
window.localStorage.setItem(remoteServersStorageKey, JSON.stringify(unique));
|
||||
return unique;
|
||||
}
|
||||
|
||||
export function upsertRemoteServer(
|
||||
servers: RemoteServerRecord[],
|
||||
server: RemoteServerRecord,
|
||||
): RemoteServerRecord[] {
|
||||
return mergeRemoteServers(servers, [server]);
|
||||
}
|
||||
|
||||
type PersistedActivePage = { activeServerId?: string; activeSub?: ServerSubPage };
|
||||
|
||||
function isServerSubPage(value: unknown): value is ServerSubPage {
|
||||
return typeof value === "string" && (SERVER_SUB_PAGES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
export function readActivePage(attachedServerIds: string[]): ActivePage {
|
||||
const text = window.localStorage.getItem(activePageStorageKey);
|
||||
if (!text) return { kind: "servers" };
|
||||
try {
|
||||
const parsed = JSON.parse(text) as PersistedActivePage;
|
||||
const id = parsed?.activeServerId;
|
||||
if (!id || !attachedServerIds.includes(id)) return { kind: "servers" };
|
||||
const sub = isServerSubPage(parsed?.activeSub) ? parsed.activeSub : "dashboard";
|
||||
return { kind: "server", serverId: id, sub };
|
||||
} catch {
|
||||
window.localStorage.removeItem(activePageStorageKey);
|
||||
return { kind: "servers" };
|
||||
}
|
||||
}
|
||||
|
||||
export function writeActivePage(page: ActivePage): void {
|
||||
if (page.kind === "servers") {
|
||||
window.localStorage.removeItem(activePageStorageKey);
|
||||
return;
|
||||
}
|
||||
const payload: PersistedActivePage = { activeServerId: page.serverId, activeSub: page.sub };
|
||||
window.localStorage.setItem(activePageStorageKey, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
type PersistedLogSidebar = { collapsed?: boolean; scopeToActiveServer?: boolean };
|
||||
|
||||
export function readLogSidebar(): PersistedLogSidebar {
|
||||
const text = window.localStorage.getItem(logSidebarStorageKey);
|
||||
if (!text) return {};
|
||||
try {
|
||||
const parsed = JSON.parse(text) as PersistedLogSidebar;
|
||||
return {
|
||||
collapsed: typeof parsed.collapsed === "boolean" ? parsed.collapsed : undefined,
|
||||
scopeToActiveServer:
|
||||
typeof parsed.scopeToActiveServer === "boolean" ? parsed.scopeToActiveServer : undefined,
|
||||
};
|
||||
} catch {
|
||||
window.localStorage.removeItem(logSidebarStorageKey);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function writeLogSidebar(state: PersistedLogSidebar): void {
|
||||
window.localStorage.setItem(logSidebarStorageKey, JSON.stringify(state));
|
||||
}
|
||||
|
||||
function customTunnelsKey(serverId: string): string {
|
||||
return `dune-manager.custom-tunnels.${serverId}`;
|
||||
}
|
||||
|
||||
function isCustomTunnelDef(value: unknown): value is CustomTunnelDef {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const d = value as Partial<CustomTunnelDef>;
|
||||
return (
|
||||
typeof d.id === "string" &&
|
||||
typeof d.name === "string" &&
|
||||
(d.protocol === "http" || d.protocol === "https" || d.protocol === "postgresql") &&
|
||||
typeof d.remotePort === "number" &&
|
||||
typeof d.localPort === "number"
|
||||
);
|
||||
}
|
||||
|
||||
export function readCustomTunnels(serverId: string): CustomTunnelDef[] {
|
||||
const text = window.localStorage.getItem(customTunnelsKey(serverId));
|
||||
if (!text) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed.filter(isCustomTunnelDef);
|
||||
} catch {
|
||||
window.localStorage.removeItem(customTunnelsKey(serverId));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function writeCustomTunnels(serverId: string, defs: CustomTunnelDef[]): void {
|
||||
window.localStorage.setItem(customTunnelsKey(serverId), JSON.stringify(defs));
|
||||
}
|
||||
170
docs/reference-repos/adainrivers/app/src/services/tauri.ts
Normal file
170
docs/reference-repos/adainrivers/app/src/services/tauri.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
||||
import { open as openShell } from "@tauri-apps/plugin-shell";
|
||||
import { relaunch as relaunchProcess } from "@tauri-apps/plugin-process";
|
||||
|
||||
import type {
|
||||
RemoteComponentLogResult,
|
||||
RemoteComponentRestartResult,
|
||||
} from "../types/component";
|
||||
import type {
|
||||
RemoteServerComponent,
|
||||
RemoteServerKind,
|
||||
RemoteServerRecord,
|
||||
RemoteServerStatus,
|
||||
} from "../types/server";
|
||||
import type { CustomTunnelStartRequest, ServerTunnelStartRequest, ServerTunnelStatus } from "../types/tunnel";
|
||||
|
||||
type RemoteActionRequest = {
|
||||
serverType: RemoteServerKind;
|
||||
host: string;
|
||||
user: string;
|
||||
keyPath?: string;
|
||||
port?: number;
|
||||
namespace: string;
|
||||
battlegroupName: string;
|
||||
};
|
||||
|
||||
type DetectRemoteServersRequest = {
|
||||
host: string;
|
||||
keyPath: string;
|
||||
serverType: RemoteServerKind;
|
||||
user: string;
|
||||
port?: number;
|
||||
};
|
||||
|
||||
type RemoteComponentLogRequest = {
|
||||
serverType: RemoteServerKind;
|
||||
host: string;
|
||||
user: string;
|
||||
keyPath?: string;
|
||||
port?: number;
|
||||
namespace: string;
|
||||
component: string;
|
||||
tail: number;
|
||||
};
|
||||
|
||||
type RemoteComponentRestartRequest = {
|
||||
serverType: RemoteServerKind;
|
||||
host: string;
|
||||
user: string;
|
||||
keyPath?: string;
|
||||
port?: number;
|
||||
namespace: string;
|
||||
component: string;
|
||||
};
|
||||
|
||||
export async function detectRemoteUbuntuServers(
|
||||
request: DetectRemoteServersRequest,
|
||||
): Promise<RemoteServerRecord[]> {
|
||||
return invoke<RemoteServerRecord[]>("detect_remote_ubuntu_servers", { request });
|
||||
}
|
||||
|
||||
export async function getRemoteServerStatus(request: RemoteActionRequest): Promise<RemoteServerStatus> {
|
||||
return invoke<RemoteServerStatus>("remote_server_status", { request });
|
||||
}
|
||||
|
||||
export async function getRemoteServerComponents(
|
||||
request: RemoteActionRequest,
|
||||
): Promise<RemoteServerComponent[]> {
|
||||
return invoke<RemoteServerComponent[]>("remote_server_components", { request });
|
||||
}
|
||||
|
||||
export async function startRemoteBattlegroup(request: RemoteActionRequest): Promise<RemoteServerStatus> {
|
||||
return invoke<RemoteServerStatus>("start_remote_battlegroup", { request });
|
||||
}
|
||||
|
||||
export async function stopRemoteBattlegroup(request: RemoteActionRequest): Promise<RemoteServerStatus> {
|
||||
return invoke<RemoteServerStatus>("stop_remote_battlegroup", { request });
|
||||
}
|
||||
|
||||
export async function updateRemoteBattlegroup(request: RemoteActionRequest): Promise<RemoteServerStatus> {
|
||||
return invoke<RemoteServerStatus>("update_remote_battlegroup", { request });
|
||||
}
|
||||
|
||||
export async function restartRemoteBattlegroup(request: RemoteActionRequest): Promise<RemoteServerStatus> {
|
||||
return invoke<RemoteServerStatus>("restart_remote_battlegroup", { request });
|
||||
}
|
||||
|
||||
export async function startServerTunnel(request: ServerTunnelStartRequest): Promise<ServerTunnelStatus> {
|
||||
return invoke<ServerTunnelStatus>("start_server_tunnel", { request });
|
||||
}
|
||||
|
||||
export async function startCustomTunnel(request: CustomTunnelStartRequest): Promise<ServerTunnelStatus> {
|
||||
return invoke<ServerTunnelStatus>("start_custom_tunnel", { request });
|
||||
}
|
||||
|
||||
export async function stopServerTunnel(tunnelId: string): Promise<void> {
|
||||
await invoke("stop_server_tunnel", { request: { tunnelId } });
|
||||
}
|
||||
|
||||
export async function serverTunnelStatus(tunnelId: string): Promise<ServerTunnelStatus | null> {
|
||||
return invoke<ServerTunnelStatus | null>("server_tunnel_status", { request: { tunnelId } });
|
||||
}
|
||||
|
||||
export async function stopAllTunnels(): Promise<void> {
|
||||
await invoke("stop_all_tunnels");
|
||||
}
|
||||
|
||||
export async function remoteComponentLogTail(
|
||||
request: RemoteComponentLogRequest,
|
||||
): Promise<RemoteComponentLogResult> {
|
||||
return invoke<RemoteComponentLogResult>("remote_component_log_tail", { request });
|
||||
}
|
||||
|
||||
export async function restartRemoteComponent(
|
||||
request: RemoteComponentRestartRequest,
|
||||
): Promise<RemoteComponentRestartResult> {
|
||||
return invoke<RemoteComponentRestartResult>("restart_remote_component", { request });
|
||||
}
|
||||
|
||||
export function listenToEvent<T>(
|
||||
channel: string,
|
||||
handler: (payload: T) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<T>(channel, (event) => handler(event.payload));
|
||||
}
|
||||
|
||||
export async function openFileDialog(title: string): Promise<string | null> {
|
||||
const selected = await openDialog({ directory: false, multiple: false, title });
|
||||
return typeof selected === "string" ? selected : null;
|
||||
}
|
||||
|
||||
export async function openExternal(url: string): Promise<void> {
|
||||
await openShell(url);
|
||||
}
|
||||
|
||||
export async function relaunch(): Promise<void> {
|
||||
await relaunchProcess();
|
||||
}
|
||||
|
||||
export type PreflightCheck = {
|
||||
sshOk: boolean;
|
||||
sudoToDuneOk: boolean;
|
||||
duneNopasswdOk: boolean;
|
||||
isDuneLogin: boolean;
|
||||
rawOutput: string;
|
||||
};
|
||||
|
||||
export async function checkRemoteSudo(request: {
|
||||
host: string;
|
||||
user: string;
|
||||
keyPath: string;
|
||||
port?: number;
|
||||
}): Promise<PreflightCheck> {
|
||||
return invoke<PreflightCheck>("check_remote_sudo", { request });
|
||||
}
|
||||
|
||||
export async function recordOperationLog(level: string, scope: string, message: string): Promise<void> {
|
||||
await invoke("record_operation_log", { level, scope, message });
|
||||
}
|
||||
|
||||
export async function getLogsFolder(): Promise<string> {
|
||||
return invoke<string>("get_logs_folder");
|
||||
}
|
||||
|
||||
export async function openLogsFolder(): Promise<void> {
|
||||
const path = await getLogsFolder();
|
||||
if (path) await openShell(path);
|
||||
}
|
||||
15
docs/reference-repos/adainrivers/app/src/services/updater.ts
Normal file
15
docs/reference-repos/adainrivers/app/src/services/updater.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater";
|
||||
|
||||
export type { DownloadEvent, Update } from "@tauri-apps/plugin-updater";
|
||||
|
||||
export async function checkForUpdate(timeoutMs = 15_000): Promise<Update | null> {
|
||||
return check({ timeout: timeoutMs });
|
||||
}
|
||||
|
||||
export async function downloadAndInstallUpdate(
|
||||
update: Update,
|
||||
onEvent: (event: DownloadEvent) => void,
|
||||
timeoutMs = 120_000,
|
||||
): Promise<void> {
|
||||
await update.downloadAndInstall(onEvent, { timeout: timeoutMs });
|
||||
}
|
||||
Reference in New Issue
Block a user