Self-hostable game-server control panel: controller + agent + 26 game modules. One-line install (prebuilt release, no Go required): curl -fsSL https://git.pdxtechs.com/dbledeez/panel/raw/branch/main/install.sh | sudo bash Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.0 KiB
Admin accounts, password reset, and Steam
First-admin bootstrap
On every start the controller checks the users table
(controller/cmd/controller/auth.go, bootstrapAdmin). If it's
empty, it creates:
- email:
admin@panel.local - role:
admin - password: randomly generated and printed to the log inside a loud
ADMIN BOOTSTRAPbanner (systemd:journalctl -u panel-controller; compose:docker compose logs controller). Log in once and change it via the UI.
To pre-seed a stable password instead (dev, CI, unattended installs),
set PANEL_ADMIN_PASSWORD in the controller's environment before first
boot. The banner then says the password came from the env var and never
prints it.
Sessions are cookie-based (panel_session); passwords are stored as
argon2id hashes.
Resetting a lost admin password
Use the reset-admin tool (controller/cmd/reset-admin). It writes the
DB directly, so it works even when you can't log in:
go build -o bin/reset-admin ./controller/cmd/reset-admin # or use a prebuilt copy
./bin/reset-admin --email admin@panel.local --password 'NewS3cret' \
--db-url 'postgres://panel:...@127.0.0.1:5432/panel?sslmode=disable'
# → reset-admin: password updated for admin@panel.local
--db-url falls back to $PANEL_DB_URL, then to the controller's dev
default DSN. The controller does not need a restart — the next login
verifies against the new hash. Existing sessions stay valid; log out /
delete rows from sessions if you want them revoked.
Hash-only mode (no DB access from the tool — e.g. you only have psql):
./bin/reset-admin 'NewS3cret'
# → $argon2id$v=19$m=65536,t=3,p=2$...$...
then apply it manually:
UPDATE users
SET password_hash = '$argon2id$v=19$m=65536,t=3,p=2$...$...',
updated_at = NOW()
WHERE email = 'admin@panel.local';
(Quote the hash in single quotes — it contains $.)
With the managed panel-postgres container:
docker exec -it panel-postgres psql -U panel -d panel
Sign in with Steam (operator login)
Optional OpenID login for the dashboard. Two controller flags:
-public-url https://panel.example.com— the public base URL of the dashboard. Steam's OpenID flow redirects back to<public-url>/api/auth/steam/callback, so this must be the URL your browser actually uses (reverse-proxied name included).-initial-admin-steam-id 7656119…— your 17-digit SteamID64.- Empty DB → the bootstrap admin is created with this Steam ID linked; "Sign in with Steam" works from the first boot.
- Existing DB → the first admin gets retro-linked, if it has no Steam ID yet (an existing different link is never clobbered).
Users can also link/unlink Steam on their own account from the UI
(/api/auth/steam/link-start). Steam-linked users may sign in with
either method; Steam-only users (created by an admin via
POST /api/users) have no password at all.
Find your SteamID64 at steamid.io or via your profile URL.
Steam credentials for paid games (requires_steam_login)
Some dedicated servers refuse SteamCMD's +login anonymous — DayZ
(app 223350) is the shipped example; its provider declares
requires_steam_login: true in modules/dayz/module.yaml. Those
downloads need a real Steam account that owns the game.
How you supply them — you don't pre-configure anything:
- Click Update (or create) on such an instance.
- No cached credential → the API answers
{"steam_login_required": true, ...}and the dashboard pops the Steam login modal. - Enter Steam username + password. If the account has Steam Guard,
the first attempt returns
{"need_guard": true}and the modal re-prompts for the emailed / mobile-authenticator code. - The controller runs a one-shot SteamCMD sidecar to validate the login, then re-runs your original update with the now-cached credential.
Storage (see the header of controller/cmd/controller/steamauth.go):
- The password is encrypted at rest with AES-GCM; the key is
HKDF-derived from the internal CA private key (so restoring
data/ca+ the Postgres dump restores decryptability — no separate key file). - The Steam Guard sentry blob is stored and mounted into every later SteamCMD run, so 2FA is a one-time ceremony per account.
- The password is never logged and never sent back to the UI.
Managing stored accounts: GET /api/steam/accounts lists them (no
secrets), DELETE /api/steam/accounts removes one. Use a throwaway
account that owns only the games you host if you're uncomfortable
storing your main account's credential.
Out-of-band admin (dashboard is broken)
A separate token-authed HTTP surface (/admin/v1/*) plus
panelctl admin exist for repair when the UI is down — see the
"Admin HTTP" section of the README. Auth is loopback
bypass on the controller host, or the X-Panel-Admin-Token header with
the token from <data-dir>/admin-token (auto-generated, mode 0600;
panelctl admin token-show prints it).