Administration

Overview

Server administration via CLI or web dashboard.

Admin Capabilities

Time Control

Action Description
Set tick rate Change ticks per second (0.1 - 100.0 Hz)
Set time scale Change game speed multiplier (0.1 - 100.0x)
Toggle time sync Enable/disable game time synchronization to wall-clock UTC
Pause Stop tick processing immediately
Resume Continue tick processing

Time Sync

When enabled (default), the tick engine adjusts effective time scale by up to ±5% to converge game time to wall-clock UTC. Only active when admin time_scale ≈ 1.0. See Game Time Synchronization for the correction formula.

Setting tick rate to 0 is not allowed — use pause instead. Invalid rates return an error.

Pause Behavior

  • Pause takes effect immediately, even during catch-up
  • Tick counter freezes at current value
  • Player connections remain open, but no state updates sent
  • When resumed, catch-up continues from where it left off

Registration Control

Admins can enable or disable new player registrations at runtime. When disabled, the registration endpoint returns error E020 (“Registration is currently closed”). Existing players can still log in.

Action Endpoint Description
Enable registrations POST /api/admin/registrations/enable Allow new player registrations
Disable registrations POST /api/admin/registrations/disable Block new player registrations
Get status GET /api/admin/registrations Check current registration status
Toggle (legacy) POST /api/admin/registrations Set via {"open": true/false} body

Redis flag: game:registration_open (default "true" when absent)

Behavior:

  • Registrations are open by default (key absent = open)
  • Only the /api/auth/register endpoint is affected; login is never blocked
  • The registration status is included in GET /api/admin/status and GET /api/status responses as registration_open

Server Management

Action Description
Start Start the game server
Stop Gracefully stop the server
Status View server health and metrics

Game State

Action Description
Reset Factory reset — wipe all game state (see below)
Snapshot Create state backup
Restore Restore from snapshot

Admin Reset vs Player Reset

Aspect Player Reset Admin Reset
Scope Single ship Entire game
Ships Returns to LEO, fuel refilled All ships removed from Redis
Celestial bodies Unchanged Reset to ephemeris for current UTC
Tick counter Unchanged Reset to 0
Game time Unchanged Reset to current UTC
Player accounts Unchanged All deleted (including admin)
Automation rules Unchanged All deleted
Maneuver state Unchanged All deleted (Redis + PostgreSQL)
Game config Unchanged All deleted from PostgreSQL
Facilities Unchanged All deleted (Redis + PostgreSQL)
Snapshots Unchanged All deleted
total_spawns counter Incremented Reset to 0
Backup N/A Created before destruction
Service restart N/A Required after reset

Admin reset is a full factory reset — all player accounts, ships, facilities, automation, maneuvers, and game config are wiped. A backup is created in the reset_backups table before destruction. Services should be restarted after reset for clean in-memory state. The bootstrap admin account is automatically re-created by the reset endpoint immediately after the gRPC call succeeds, using GALAXY_ADMIN_USERNAME/GALAXY_ADMIN_PASSWORD env vars — no api-gateway restart is needed for admin access.

Admin Reset Flow for Connected Players

When admin triggers a factory reset, connected players experience the following:

Step Action
1 Admin calls POST /api/admin/game/reset
2 api-gateway calls tick-engine.ResetGame() via gRPC (120s timeout)
3 tick-engine pauses tick processing
4 tick-engine creates backup (Redis BGSAVE + PostgreSQL data → reset_backups table)
5 tick-engine calls physics.ClearAllShips() via gRPC
6 physics deletes all ship:* keys, resets game:total_spawns to 0
7 physics publishes ship.removed events for each deleted ship
8 tick-engine calls physics.ClearAllStations() via gRPC
9 tick-engine calls physics.ClearAllJumpGates() via gRPC
10 tick-engine deletes player Redis keys (player:*, automation:*, maneuver:*, maneuver_log:*, maneuver_history:*, connections:online, game:registration_open, game:maneuver_logging)
11 tick-engine deletes facility Redis keys (facility:*, player:*:facilities)
12 tick-engine resets game state keys, re-initializes (tick=0, time=current UTC)
13 tick-engine deletes PostgreSQL data (maneuver_events, facilities, ships, players, game_config, snapshots)
14 tick-engine re-initializes physics (bodies from ephemeris)
15 tick-engine resumes tick processing
16 api-gateway re-bootstraps admin account from env vars (immediate, no restart needed)
17 api-gateway returns backup_id and restart_required: true
18 CLI/dashboard restarts remaining game deployments via kubectl rollout restart

Impact on connected players:

Aspect Behavior
Connections Remain open (not disconnected)
Ship state Deleted; next control command returns E006
Error shown “Ship not found” (E006)
Recovery Player must re-register (account was deleted)
Game time Resets to current UTC in next state update

Client experience:

  1. Player sees game time reset to current UTC
  2. Player’s HUD shows no ship data (or stale data)
  3. Player attempts any control → receives E006 error
  4. Services restart → WebSocket connections close
  5. Player must re-register (account was deleted in reset)
  6. Normal gameplay resumes

Use admin reset for:

  • Starting a fresh game
  • Recovering from corrupted state
  • Testing/development

Snapshot Restore Behavior

When admin restores from a snapshot:

Step Action
1 Admin calls POST /api/admin/snapshots/{id}/restore
2 tick-engine pauses tick processing
3 tick-engine loads snapshot from PostgreSQL
4 tick-engine replaces all Redis state (bodies, ships, game:tick, etc.)
5 tick-engine resumes tick processing
6 Next tick broadcasts new state to all connected clients

Impact on connected players:

Aspect Behavior
Connections Remain open (not disconnected)
Ship positions May jump to snapshot position
Notification No special message; next state update shows new positions
Controls Continue working normally
Ships not in snapshot Removed from Redis; player sees E006 on next control

Edge cases:

Scenario Behavior
Player registered after snapshot Ship not in snapshot; removed on restore; player must reset
Player deleted after snapshot Ship restored; player can’t control (no account)
Mid-tick restore Current tick completes, restore happens before next tick

Recovery for affected players:

  • Players whose ships were removed can use the “reset” service to spawn a new ship
  • Players see their ship at the snapshot position on next state update

Interfaces

CLI (admin-cli)

galaxy-admin status
galaxy-admin tick-rate set 2.0
galaxy-admin pause
galaxy-admin resume
galaxy-admin snapshot create
galaxy-admin snapshot restore <snapshot-id>
galaxy-admin players list
galaxy-admin players delete <player-id>

CLI Authentication

The admin CLI uses the same REST API as the dashboard.

Credential sources (in priority order):

Priority Source Description
1 Cached token ~/.galaxy/admin-token
2 Environment variables GALAXY_ADMIN_USER, GALAXY_ADMIN_PASSWORD
3 Interactive prompt Prompts for username/password

Authentication flow:

  1. Check for cached token in ~/.galaxy/admin-token
  2. If token exists and not expired, use it
  3. If token expired or missing:
    • Check GALAXY_ADMIN_USER and GALAXY_ADMIN_PASSWORD environment variables
    • If not set, prompt interactively for username and password
  4. Call POST /api/admin/auth/login with credentials
  5. Cache new token to ~/.galaxy/admin-token
  6. Token cached with 600 file permissions (owner read/write only)

Token expiry handling:

  • Token expires after 24 hours
  • CLI detects 401 response and re-authenticates automatically
  • Re-authentication uses same priority order (env vars, then prompt)

Non-interactive mode:

For scripts and automation, set environment variables:

export GALAXY_ADMIN_USER=admin
export GALAXY_ADMIN_PASSWORD=secret
galaxy-admin status

Web Dashboard (retired — now integrated into web-client)

The standalone admin-dashboard service has been retired. All admin functionality is now available through the web client’s built-in admin view (View > Admin View).

  • Real-time server metrics (tick rate, time scale, energy drift, momentum drift)
  • Visual controls for time/state management
  • Player management with promote/demote
  • Infrastructure management (stations, jump gates)
  • Maneuver debugging

Integration Health Metrics

The Game Status card displays energy drift (dE/E0) and momentum drift (dL/L0) with color coding:

Color Drift Level Meaning
Green < 1e-6 Normal
Yellow 1e-6 to 1e-3 Elevated
Red >= 1e-3 Concerning

Logging

All game events logged for analytics and debugging.

Log Format

JSON structured logs:

{
  "timestamp": "2025-01-15T10:30:00Z",
  "level": "info",
  "event": "player.login",
  "player_id": "uuid",
  "details": {}
}

Events Logged

Event Level Details
player.registered info username
player.login info player_id
player.logout info player_id
ship.spawned info ship_id, player_id, position
ship.service.fuel info ship_id
ship.service.reset info ship_id
tick.completed debug tick_number, duration_ms
tick.behind warn ticks_behind
error.validation warn player_id, message
error.internal error message, stack

Log Storage

  • Stdout in JSON format (container best practice)
  • Aggregated via Kubernetes logging (e.g., Loki, ELK)
  • Retention: 30 days (configurable)

Metrics

Format and Export

  • Format: Prometheus
  • Endpoint: /metrics on each service (HTTP)
  • Scrape interval: 15 seconds (configurable in Prometheus)
Service Metrics Endpoint
api-gateway http://api-gateway:8000/metrics
tick-engine http://tick-engine:8001/metrics
physics http://physics:8002/metrics
players http://players:8003/metrics
galaxy http://galaxy:8004/metrics

Prometheus Metric Names

Metrics follow Prometheus naming conventions (galaxy_ prefix):

Tick Processing

Metric Type Description
galaxy_tick_current Gauge Current tick number
galaxy_tick_rate_configured Gauge Configured ticks per second
galaxy_tick_rate_actual Gauge Actual ticks per second achieved
galaxy_tick_duration_seconds Histogram Processing time per tick
galaxy_tick_behind_total Gauge Number of ticks behind schedule (0 = healthy)

API Gateway

Metric Type Labels Description
galaxy_api_requests_total Counter method, path Total HTTP requests (path uses route template)
galaxy_connections_active Gauge WebSocket connections

Game State

Metric Type Description
galaxy_celestial_bodies_total Gauge Number of bodies in n-body simulation
galaxy_ships_total Gauge Number of active ships
galaxy_players_registered_total Counter Total registered players

System Health

Metric Type Description
galaxy_redis_up Gauge Redis connection health (1 = up, 0 = down)
galaxy_postgres_up Gauge PostgreSQL connection health (1 = up, 0 = down)

Kubernetes Integration

# ServiceMonitor for Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: galaxy-services
spec:
  selector:
    matchLabels:
      app.kubernetes.io/part-of: galaxy
  endpoints:
    - port: metrics
      interval: 15s

Authentication

Unified Auth Model

Admin and player identities share the players table. An is_admin boolean flag distinguishes admin accounts from regular players. This replaces the previous separate admins table.

Key properties:

  • Admin accounts are rows in players with is_admin = TRUE
  • Admins may have ship_id = NULL (ship-less observation mode)
  • Regular player registration always creates a ship (is_admin = FALSE)
  • A player can be promoted to admin (or demoted) by toggling is_admin
  • JWT tokens include an is_admin claim for authorization checks

Token claims (unified):

Claim Type Description
sub string Player ID (UUID)
username string Username
ship_id string Ship UUID, or empty string for ship-less admins
is_admin bool True if admin
iat int Issued at (Unix timestamp)
exp int Expiry (Unix timestamp, 24 hours)

The previous type: "admin" claim is no longer used. Admin authorization checks is_admin instead.

Ship-less Admin Mode

Admins with ship_id = NULL can connect via WebSocket for observation:

  • Welcome message includes full game config and is_admin: true
  • State broadcasts include all bodies, ships, and stations (but ship field is null)
  • Ship-dependent messages (control, service, attitude_*, automation_*, rendezvous_*, target_select, ship_rename) return error E030 “No ship assigned”
  • Non-ship messages (ping/pong, chat) work normally

Constant-Time Authentication

Admin authentication must be constant-time to prevent timing-based username enumeration. When a username is not found in the database, the server must still perform a bcrypt verification against a dummy hash to ensure the response time is indistinguishable from a valid-username-wrong-password attempt.

Implementation:

  • Generate a dummy bcrypt hash at module load time: _DUMMY_HASH = bcrypt.hashpw(b"dummy", bcrypt.gensalt()).decode()
  • When the database query returns no row for the given username, call verify_password(password, _DUMMY_HASH) before returning None
  • This ensures both code paths (user found, user not found) execute bcrypt verification

Initial Admin Setup

  1. On first startup, api-gateway checks for existing admin accounts in players table (WHERE is_admin = TRUE)
  2. If none exist, creates bootstrap admin from Kubernetes Secret:
    • GALAXY_ADMIN_USERNAME (default: “admin”)
    • GALAXY_ADMIN_PASSWORD (required, no default)
    • Inserts into players with is_admin = TRUE, ship_id = NULL
  3. Bootstrap admin can create additional admins via dashboard or in-game admin view
  4. Bootstrap admin should change password after first login

Missing password behavior:

If GALAXY_ADMIN_PASSWORD is not set and no admin accounts exist:

  • Server logs fatal error: “GALAXY_ADMIN_PASSWORD required for initial admin setup”
  • Server exits with non-zero status code (prevents startup)
  • Kubernetes will show pod in CrashLoopBackOff until secret is configured

This prevents running without admin access and avoids security vulnerabilities from empty/default passwords.

Admin Account Management

Action Access Endpoint
Create admin Existing admin via CreateAdminAccount gRPC or REST endpoint
Promote player to admin Existing admin via admin view or REST PUT /api/admin/players/{player_id}/role
Demote admin to player Existing admin via admin view or REST (cannot demote self) PUT /api/admin/players/{player_id}/role
Change password Self or other admin

Promote/Demote endpoint: PUT /api/admin/players/{player_id}/role

Field Type Description
is_admin bool true to promote, false to demote

Constraints:

  • Self-demotion is blocked — admins cannot change their own role (returns 400 “Cannot change own admin role”)
  • Promoted players keep their existing ship
  • Demoted admins with no ship cannot log in normally until a ship is assigned

Note: “Deleting” an admin now means setting is_admin = FALSE (demotion), not deleting the player row. If the demoted admin had no ship, they cannot log in normally until a ship is assigned.

Player Account Management

Action Access
Reset player password Admin via dashboard or admin view
Delete player account Admin via dashboard or admin view
View player list Admin via dashboard or admin view

Password reset generates a temporary password that admin provides to player out-of-band.

Deleting Connected Players

When admin deletes a player who is currently connected:

Step Action
1 Admin initiates delete via dashboard
2 players service deletes account from PostgreSQL
3 players service calls physics.RemoveShip
4 physics service removes ship from Redis
5 ship.removed event published to Redis Stream
6 api-gateway receives event, finds active connection
7 api-gateway sends E014 “Account deleted” to client
8 api-gateway closes WebSocket connection
9 Client displays “Your account has been deleted”

The deletion is immediate — no grace period or confirmation to the connected player.

Edge cases:

Scenario Behavior
In-flight commands Any commands queued at api-gateway are discarded after WebSocket closes
Ship removal timing Immediate — removed from Redis before ship.removed event published
Cached JWT reconnect api-gateway validates token, calls players.GetPlayer, receives “not found”, returns E005
Mid-tick deletion If physics is processing a tick, ship may appear in that tick’s broadcast but not subsequent ones
Concurrent control input ApplyControl calls after deletion return E006 “Ship not found”

In-Game Admin View

The web client includes a full admin dashboard accessible to admin users. The standalone admin-dashboard service has been retired; all admin functionality is now in the web client.

Access

  • Visible only to users with is_admin = TRUE in their JWT
  • Menu item: View > Admin View (hidden for non-admins)
  • Ship-less admins default to admin view on login
  • Admins with ships can toggle between cockpit/map/admin views

Layout

Full-screen overlay (below menu bar, above canvas) with a centered card grid:

Panel Width Description
Game Status 1 col Tick, rates, drift, pause state, registration
Tick Rate 1 col Input + set button
Time Sync 1 col Toggle, drift, manual scale input
Snapshots 1 col Create + scrollable list (last 10)
Danger Zone 1 col Factory reset button (red border)
Infrastructure 1 col Station/jumpgate spawn forms + lists
Maneuver Debug 1 col Logging level, per-ship debug toggle
NPC Management 1 col Placeholder — “Coming soon”
Players full width Table: username, status, ship, created, actions

Floating Admin Windows

In addition to the full-screen admin view, admin panels are available as individual draggable floating windows that overlay the cockpit. This allows admins to monitor and control the game while playing.

Admin Menu

A top-level “Admin” menu appears between Ship and Help in the menu bar (hidden for non-admins). Menu items:

Item Action
Game Control Toggle game control floating window
State Management Toggle state management floating window
Player Management Toggle player management floating window
Infrastructure Toggle infrastructure floating window
Diagnostics Toggle diagnostics floating window
Show All Show all 5 floating windows
Hide All Hide all 5 floating windows

Window Definitions

Window ID Panels Merged Default Width
admin-game-control-window Game Status + Tick Rate + Time Sync 320px
admin-state-mgmt-window Snapshots + Danger Zone 340px
admin-players-window Players table 600px
admin-infrastructure-window Stations + Jump Gates 380px
admin-diagnostics-window Maneuver Debug 340px

Dual-Mode Behavior

  • Full-screen admin view and floating windows coexist independently
  • Rendering functions are shared — both modes call the same render logic with different DOM element sets
  • Floating window elements use fw-admin- prefixed IDs to avoid conflicts with full-screen elements
  • Buttons and inputs in floating windows also use fw- prefixed IDs

Polling

Polling is unified across both modes:

  • Polling starts when either full-screen admin view activates OR any floating window becomes visible
  • Polling stops only when full-screen admin view is deactivated AND no floating windows are visible
  • Both modes receive the same data from the same polling intervals

Position Persistence

Floating window positions are saved to settings.windowPositions (localStorage) and restored on init. Draggable only — no resize.

Polling Intervals

Data is polled via REST endpoints while admin view or any admin floating window is active:

Data Interval Endpoint
Game status 2 seconds GET /api/admin/status
Players 10 seconds GET /api/admin/players
Snapshots 30 seconds GET /api/admin/snapshots
Stations 30 seconds GET /api/admin/stations
Jump gates 30 seconds GET /api/admin/jumpgates

Polling starts on view activation and stops on deactivation.

Authentication

Uses the existing state.token (JWT with is_admin claim). No separate admin login flow. 401 responses are handled gracefully (token expired).

Keyboard Input Handling

Input fields within the admin view stop keyboard event propagation to prevent cockpit shortcuts (M, P, H, etc.) from firing while typing. This is done via stopPropagation() on keydown events for admin input elements.

Confirmation Dialogs

Destructive operations require browser confirm() / prompt() dialogs:

Action Confirmation
Time scale change confirm() — changing time scale can destabilize physics
Restore snapshot confirm() — current state will be lost
Factory reset Single confirm() — irreversible, deletes all accounts
Delete player confirm() with username shown
Promote player confirm() with username shown
Demote admin confirm() with username shown
Remove station confirm() with station name shown
Remove jump gate confirm() with gate name shown
Reset password prompt() for new password (min 8 chars)

Back to top

Galaxy — Kubernetes-based multiplayer space game

This site uses Just the Docs, a documentation theme for Jekyll.