panel v0.9.1 — open-source game server manager
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
# 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 BOOTSTRAP` banner (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:
|
||||
|
||||
```bash
|
||||
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`):
|
||||
|
||||
```bash
|
||||
./bin/reset-admin 'NewS3cret'
|
||||
# → $argon2id$v=19$m=65536,t=3,p=2$...$...
|
||||
```
|
||||
|
||||
then apply it manually:
|
||||
|
||||
```sql
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
1. Click **Update** (or create) on such an instance.
|
||||
2. No cached credential → the API answers
|
||||
`{"steam_login_required": true, ...}` and the dashboard pops the
|
||||
**Steam login modal**.
|
||||
3. 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.
|
||||
4. 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](../README.md). 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).
|
||||
Reference in New Issue
Block a user