POST /servers/install-oxide endpoint, NATS bridge for oxide.status,
server store installOxide method, ServerView Install Oxide card with
progress tracker matching the Deploy card pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New oxide package downloads latest Oxide.Rust release from GitHub,
extracts over the server directory, and restarts the game server.
Progress published to NATS (corrosion.{license_id}.oxide.status).
Heartbeat now reports oxide_installed status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone C# uMod plugin that provides a full-screen CUI teleport
interface for Rust game servers, integrating with NTeleportation.
Features:
- /tpgui chat command opens tabbed overlay (Teleport, Homes, Warps, Settings)
- 4x5 player grid with search filtering and pagination for TPR
- Home management (teleport, set, delete) via NTeleportation API
- Server warp list with teleport buttons
- Incoming TPR accept/deny popup with 30s auto-dismiss
- Settings tab showing cooldowns, limits, NTeleportation status
- Oxide-orange color scheme matching Corrosion brand
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Seven endpoints for managing NTeleportation configs: list summaries,
get full config, create, update, delete, deploy to server via NATS,
and import live config from server. Follows loot module pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the complete frontend for BetterLoot profile management:
- Pinia store (loot.ts) with CRUD, import/export, apply-to-server actions
- LootBuilderView orchestrator with profile bar, modals, two-column layout
- LootContainerSidebar with categorized container list, search, config indicators
- LootItemEditor for per-container item settings and ungrouped item table
- LootItemPicker modal with searchable/filterable Rust item grid
- LootGroupEditor for reusable loot group management
- Router integration at /loot-builder
- Sidebar nav item with Crosshair icon and loot.view permission
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire gorilla/websocket into the Go companion agent to send arbitrary
console commands (e.g. oxide.reload BetterLoot) to the Rust Dedicated
Server's WebRCON endpoint. Adds RCON_PORT and RCON_PASSWORD env vars,
a new "command" action on the existing cmd.server NATS subject, and
the internal/rcon package that handles the JSON-over-WebSocket protocol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prev/Next buttons at top and bottom of results table. New search
resets to page 1. Buttons disable at bounds and during loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The md:static approach wasn't reliably removing fixed positioning,
causing the sidebar to overlay the main content. Changed to keep
sidebar fixed (better for dashboards — no scroll) and offset main
content with md:pl-64 instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VueFinder requires app.use(VueFinderPlugin) to provide its internal
context (i18n, features, config stores). Without plugin registration,
the store returned null during setup, causing Object.keys to throw
TypeError on undefined.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Installs VueFinder and wires it to the backend /api/files endpoint with
JWT Bearer auth. Adds /files route, File Manager nav item (files.view
permission-gated, FolderOpen icon), and imports VueFinder CSS globally.
Driver token is computed reactively so it tracks token refreshes automatically.
Uses midnight theme to match the dark admin panel aesthetic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements companion-agent/internal/filemanager with full installDir jail
enforcement (Clean + EvalSymlinks + HasPrefix on every path). Handles all
VueFinder operations: list, delete, rename, copy, move, mkdir, mkfile,
search, preview, save, upload. Wires into daemon.go as a 6th NATS
subscription on corrosion.{license_id}.files.cmd.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements GET/POST /api/files proxy for all VueFinder operations
(list, search, preview, download, delete, rename, move, copy, mkdir,
new-file, save, upload). Routes via NATS request-reply to the companion
agent on corrosion.{license_id}.files.cmd with 30s timeout. Gated
behind files.view (GET) and files.manage (POST) permissions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- GET /plugins/browse proxies uMod search.json filtered to Rust category,
with 5-minute in-memory Map cache to avoid hammering the upstream API
- POST /plugins/upload accepts .cs files up to 5 MB via multipart, persists
to plugin_registry, and dispatches plugin_upload action over NATS so the
companion agent can write the file to the game server
- Legacy GET /plugins/search stub preserved (now directs callers to /browse)
- FileInterceptor + @UploadedFile follow the existing maps upload pattern
Frontend:
- useApi composable gains upload() method for multipart/form-data requests
(omits Content-Type so the browser sets the correct multipart boundary)
- plugins store adds browseUmod() calling GET /plugins/browse and
uploadPlugin() calling POST /plugins/upload with FormData;
UmodPlugin and UmodBrowseResult TypeScript interfaces exported
- PluginsView Browse tab now calls browseUmod() through the backend proxy
(no cross-origin requests to uMod directly); results show title,
downloads_shortened, and latest_release_version_formatted from the
real uMod payload
- New Upload Custom tab: drag-and-drop or click file input for .cs files,
client-side extension/size validation, spinner during upload, success
toast + auto-switch to Installed tab on completion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ServerView: automation toggles (crash recovery, auto-update, force wipe eligible)
now call updateConfig() on click with toast success/error; all catch blocks get
toast feedback instead of silent swallow
- PluginsView: Browse uMod tab wired to /plugins/search backend endpoint with
debounced search, results table, and Install button that calls installPlugin();
shows install state and marks already-installed plugins
- WipesView: dry-run results now displayed in a collapsible panel (would_delete /
would_preserve lists + estimated duration); schedule enable/disable toggle wired
to PUT /wipes/schedules/:id with loading state and toast feedback
- AnalyticsView: catch blocks now show toast errors instead of console.log;
Player Retention placeholder replaced with intentional placeholder cards
- TeamView, ChatLogView, PlayersView, NotificationsView, MapsView: all empty
catch blocks and '// API not wired yet' comments replaced with toast.error()
calls; Notifications save now shows success toast
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- schedules/: Add schedule executor in SchedulesService — polls every 60s for
tasks where next_run <= now, dispatches NATS commands per task_type
(restart, announcement, command, plugin_reload). Calculates next_run from
cron expression on create/update/toggle. Bootstraps missing next_run values
on startup. Wire NatsService into SchedulesModule.
- alerts/: Add alert evaluator in AlertsService — polls every 90s, loads all
alert_config rows, queries latest server_stats per license, evaluates FPS
degradation and population drop thresholds. Fires alert_history records on
breach. Enforces 10-minute in-memory cooldown per alert type per license to
prevent flooding. Wire ServerStats repo into AlertsModule.
- wipes/: Replace hardcoded dry-run mock with profile-aware simulation.
Resolves actual WipeProfile by ID (cross-tenant protected), builds
would_delete/would_preserve lists from wipe_type, factors pre_wipe_config
(backup, countdown warnings) and post_wipe_config (health checks, retry
attempts) into estimated_duration_seconds. Returns profile_name and notes.
- store/: Fix installModule stub — creates a real module_installations record
with status='installed' and installed_at timestamp. Idempotent on retry,
resets failed installations. Wire ModuleInstallation repo into StoreModule.
getMyModules now returns real installation data instead of filtered purchases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- players: Primary data from player_sessions (online status, playtime aggregates);
ban/unban status overlaid from player_actions latest action per steam_id.
Register PlayerSession entity in PlayersModule. Extend NATS forwarding to
include 'unban' alongside kick and ban.
- analytics: Fix retention period boundary bug — sessions were queried with only
a lower-bound filter (MoreThan), causing all future cycles to bleed into earlier
wipe windows. Replaced with Between(wipeDate, endDate) for correct isolation.
- status: Replace hardcoded player_count=0/max_players=0 with live data from
most-recent server_stats row per license. Register ServerStats entity in
StatusModule. Falls back to 0 gracefully when no stats exist yet.
- maps: File buffer was computed and discarded — never written to disk.
Now writes to /app/map_data/{licenseId}/{timestamp}_{filename} (tenant-isolated,
docker volume map_data). Creates directories with mkdirSync(recursive:true).
Logs success/failure via NestJS Logger. Throws 500 on disk write failure
instead of silently losing data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- servers.service: sendCommand() now throws InternalServerErrorException on
NATS failure instead of silently succeeding; returns { success, message }
instead of the legacy { output } shape; adds NestJS Logger
- plugins.service: installPlugin() dispatches plugin_install to
corrosion.{license_id}.cmd.server after DB save; NATS failure is logged
but non-fatal so the DB record is preserved regardless
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The JWT was missing license_id, causing @CurrentTenant() to throw 401
on every protected route. Now generateTokens() accepts a licenseId
param, and all three callers (register, login, refresh) look up the
user's license and pass it through.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NATS has no auth configured, so NATS_TOKEN was a placeholder that
confused users. Made it optional in the Go agent (default empty) and
removed it from Quick Setup commands. Also removed GAME_SERVER_PATH
since one-click deploy handles server installation. License key already
auto-populates from auth store after previous commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- admin.service.ts: createLicense() now uses CORR-XXXX-XXXX-XXXX format
instead of raw hex hash
- admin.service.ts: getLicenses() flattens owner_email in response to
match frontend expected shape
- auth.service.ts: Login/register responses now include full license
object so frontend can populate auth store
- auth.service.ts: Email lookups are case-insensitive (LOWER()) to
prevent duplicate accounts from case variations
- LoginView/RegisterView: Call setLicense() after setAuth()
- AdminLicenses: Handle null expires_at (was showing Dec 31, 1969),
fix nullable types, fix query param name (per_page → limit)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend uses native WebSocket API, backend was using socket.io which
speaks an incompatible protocol. Switched to @nestjs/platform-ws so
both sides speak native WebSocket. Also fixed JWT TTL override in
docker-compose.yml (was hardcoded to 900s, now 14400s/4h).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Named after Carl Brashear — first Black U.S. Navy Master Diver.
Every Opus instance that boots on this project knows who it is
and what standard it's held to.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The refresh endpoint only returned access_token, causing the frontend to
set refreshToken=undefined after first refresh — breaking the entire
token chain. Now returns both tokens (rotating refresh). Access token
default bumped from 15min to 4h (14400s) for practical server setup
sessions. Also fixed empty license_key for super admin via DB update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Go deployment orchestrator with platform-specific SteamCMD install,
Rust server download, server.cfg generation, and service registration.
Wire deploy command subscription in daemon, make GameServerPath optional,
add InstallDir config with OS-aware defaults. Fix unused imports and
WebSocket subscribe API in ServerView.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds DeploymentConfig/DeploymentStatus types, deployment state management
in the server store, tabbed Linux/Windows quick setup commands, and a
Deploy Rust Server card with progress tracker and configuration form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add deploy endpoint, DTO, NATS command publisher, and WebSocket bridge
subscription to support the one-click server deployment feature via the
companion agent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DashboardView: Add non-null assertion on upcoming[0] (guarded by length check)
- EarlyAccessView: Add missing `computed` import from Vue
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI pipeline now uploads binaries to cdn.corrosionmgmt.com:
- /companion/latest/ (always current, overwritten each release)
- /companion/v1.x.x/ (versioned archive)
Frontend download links updated from Gitea releases to CDN.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend:
- Add Companion Agent card to ServerView (status, download links, setup instructions)
- Shows agent connection status, last heartbeat, license key for copy
- Download buttons for Linux/Windows amd64 from Gitea releases
CI/CD:
- Fix build-companion.yml: replace actions/github-script with Gitea API curl
- Inject version from git tag via ldflags (-X main.version)
- Add VERSION variable to Makefile with ldflags injection
- Change main.go version from const to var for ldflags compatibility
Deployment:
- Add systemd service file (deployment/corrosion-companion.service)
- Add .gitignore for bin/ (binaries should come from CI, not repo)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pin NATS image to nats:2.10-alpine for reproducible builds
- Add nginx healthcheck using wget (curl not present in alpine)
- Upgrade nginx depends_on to use condition: service_started
- Add proxy buffer directives to http block (prevents JWT/large-header truncation)
- Add X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, and
Referrer-Policy security headers to all SPA location blocks across
all five server blocks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend expects { subscriptions: [{ owner_email, module_name, license_id }] }
but backend returned raw WebstoreSubscription entities. Shaped the response
with license.owner join for owner_email and plan_id mapped to module_name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- HttpExceptionFilter: Log actual error details for non-HttpExceptions (was silently swallowing 500s)
- ServersService: Return null fields instead of 404 for new licenses without servers
- NotificationsController: Wrap config responses as { config } to match frontend expectations
- WebstoreController: Wrap config responses as { config } to match frontend expectations
- ChatController: Replace ParseIntPipe with manual parseInt (400 on missing optional param)
- WipesController: Same ParseIntPipe fix for history limit param
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause of all remaining 500s: TypeORM entities were scaffolded with
"ideal" column names that don't match the Postgres columns created by
the Rust migrations. Every query generated SQL referencing non-existent
columns.
Entity fixes:
- notifications_config: email_enabled→email_alerts_enabled, removed
6 phantom columns (email_address, notify_on_start, notify_on_stop,
notify_on_player_threshold, player_threshold), renamed 4 notify
columns to match DB (notify_server_crash, notify_wipe_start, etc),
added 3 missing columns (notify_server_offline, notify_store_purchase,
notify_player_report)
- team_members: joined_at→accepted_at (nullable, matches DB)
- roles: removed description column (doesn't exist in DB)
- scheduled_tasks: is_enabled→is_active, removed phantom last_run
- wipe_profiles: pre/post_wipe_config nullable→NOT NULL with default
Service/DTO fixes:
- Updated all property references across notifications, team, schedules
services and DTOs to match corrected entity names
- Added is_active to UpdateTaskDto (frontend sends it, was being
rejected by forbidNonWhitelisted validation)
- Removed description from CreateRoleDto
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>