panel v0.9.1 — open-source game server manager

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 23:37:25 -07:00
commit 582b5a6b08
2161 changed files with 300950 additions and 0 deletions
+340
View File
@@ -0,0 +1,340 @@
# Installing panel
Four supported paths, in order of least effort:
1. [Linux one-liner](#linux-one-liner) — `install.sh` (recommended)
2. [Windows one-liner](#windows-one-liner) — `install.ps1`
3. [docker-compose](#docker-compose) — controller + agent + Postgres as containers
4. [From source](#from-source) — full manual control
Then: [Postgres provisioning](#postgres-provisioning),
[flags reference](#controller-flags), [systemd](#systemd),
[reverse proxy + TLS](#reverse-proxy--real-tls), [firewall](#firewall-basics).
---
## Linux one-liner
Debian, Ubuntu, Fedora, CentOS/RHEL, Raspbian. Run as root:
```bash
curl -fsSL https://raw.githubusercontent.com/dbledeez/panel/main/install.sh | sudo bash
```
What it does (idempotent — safe to re-run; existing containers, units,
CA, DB and passwords are kept, binaries and modules refreshed):
1. Installs Docker via `get.docker.com` if missing.
2. Starts a managed `panel-postgres` container (unless you point it at
an existing DB with `PANEL_DB_URL`).
3. Builds (or downloads, with `PANEL_PREBUILT_URL`) `controller`,
`agent`, and `panelctl` into `/opt/panel/bin/`.
4. Boots the controller once to bootstrap the internal CA and the admin
account, then pairs the local agent over mTLS.
5. Installs and enables `panel-controller.service` and
`panel-agent.service`.
Tunables (env vars, all optional — from `install.sh`'s header):
| Env var | Meaning | Default |
|---|---|---|
| `PANEL_REPO_URL` | git/https base of the panel source | github repo |
| `PANEL_PREBUILT_URL` | tarball with prebuilt `bin/{controller,agent,panelctl}` + `modules/` (skips the Go build) | — |
| `PANEL_VERSION` | version string baked into the binaries | `git describe \|\| dev` |
| `PANEL_DIR` | install prefix | `/opt/panel` |
| `PANEL_DATA_DIR` | data directory | `$PANEL_DIR/data` |
| `PANEL_HTTP_PORT` | dashboard port | `8080` |
| `PANEL_GRPC_PORT` | agent gRPC port | `8443` |
| `PANEL_ADMIN_PASSWORD` | stable first-boot admin password | generated by the controller |
| `PANEL_DB_URL` | use an EXISTING Postgres instead of the managed container | — |
| `PANEL_DB_PASSWORD` | password for the managed `panel-postgres` container | generated |
| `PANEL_SKIP_AGENT=1` | controller only (add agents on other boxes later — see [PAIRING.md](PAIRING.md)) | `0` |
Example — controller-only box on a custom port with a pinned admin password:
```bash
PANEL_SKIP_AGENT=1 PANEL_HTTP_PORT=9090 PANEL_ADMIN_PASSWORD='s3cret' \
bash install.sh
```
## Windows one-liner
PowerShell 5+, run **as Administrator**:
```powershell
irm https://git.pdxtechs.com/dbledeez/panel/raw/branch/main/install.ps1 | iex
```
**Docker Desktop** is required (it runs Postgres + your game servers). If it
isn't installed, the installer offers to install it for you via `winget`.
Because Docker Desktop needs WSL2/virtualization and usually a **reboot**, the
Windows install is effectively two steps the first time:
1. Run the command above. If Docker is missing it installs Docker Desktop and
then asks you to reboot + start Docker Desktop (wait for the whale icon to
settle).
2. Re-run the exact same command — it picks up from there and finishes.
To skip the prompt: `-InstallDocker` (or `PANEL_INSTALL_DOCKER=1`) auto-installs
Docker without asking; `-SkipDockerInstall` keeps the old fail-if-missing
behavior. On a box that already has Docker Desktop running, it's a genuine
one-liner.
> **Windows note:** the controller runs great on Windows. Running game
> *servers* on Windows uses Docker Desktop, where the agent automatically
> switches modules from host networking to published bridge ports so they're
> reachable — but the smoothest, fully-proven game-hosting path is a Linux
> agent. See [NETWORKING.md](NETWORKING.md).
Parameters (mirror the Linux env vars): `-RepoUrl`, `-PrebuiltUrl`,
`-Version`, `-InstallDir` (default `C:\Panel`), `-HttpPort` (8080),
`-GrpcPort` (8443), `-AdminEmail`, `-AdminPassword`, `-DbUrl`, `-DbPassword`,
`-DbContainer`, `-DbPort`, `-DbVolume`, `-SkipAgent`, `-InstallDocker`,
`-SkipDockerInstall`. The same `PANEL_*` env vars are honored as defaults.
When run in a real console it prompts you to choose the admin email + password
up front (no log-reading); when piped (`irm | iex`) it uses env/param values or
generates a password.
The controller and agent are registered as **logon-time Scheduled
Tasks**. For a hardened always-on Windows service, wrap the same command
lines with [nssm](https://nssm.cc):
`nssm install panel-controller C:\Panel\bin\controller.exe ...`.
## docker-compose
A production single-box stack (Postgres + controller + agent) lives at
`deploy/docker-compose.yml`. From the repo root:
```bash
cp deploy/.env.example deploy/.env # then edit deploy/.env (PANEL_DB_PASSWORD is required)
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
```
First run: watch `docker compose logs controller` for the
`ADMIN BOOTSTRAP` banner (generated admin password), then mint a pair
token in the UI and pair the agent — see [PAIRING.md](PAIRING.md).
> **Security note (from the compose file itself):** the agent container
> mounts the **host** Docker socket to manage game-server containers.
> Docker-socket access is root-equivalent on the host. Run this stack
> only on a host you fully trust and keep the dashboard behind auth/TLS.
## From source
Prereqs: Go 1.26+, Docker, [`buf`](https://buf.build/docs/installation/)
on PATH for proto stubs.
```bash
buf generate # proto stubs (once per clone / .proto change)
go build -o bin/controller ./controller/cmd/controller
go build -o bin/agent ./agent/cmd/agent
go build -o bin/panelctl ./controller/cmd/panelctl
# 1) Postgres (dev container) — or see "Postgres provisioning" below
docker compose -f docker-compose.dev.yml up -d
# 2) Controller — first run prints the ADMIN BOOTSTRAP banner
./bin/controller
# 3) Agent (same box, dev): plaintext is fine locally
./bin/agent --insecure --modules-dir=./modules \
--data-root=./data/instances --meta-dir=./data/instance-meta \
--backup-dir=./data/backups
```
For production, pair the agent instead of `--insecure`
([PAIRING.md](PAIRING.md)) and run both under systemd (below).
There is also a root `Makefile` (`make build`, `make test`, `make vet`,
`make install-local`, ...).
---
## Postgres provisioning
The controller needs Postgres (17 is what the managed container and
compose stack use; the schema is plain SQL with embedded migrations, run
automatically at controller start).
Bring your own instance:
```sql
CREATE ROLE panel LOGIN PASSWORD 'choose-a-password';
CREATE DATABASE panel OWNER panel;
```
Point the controller at it via either:
- `--db-url 'postgres://panel:...@dbhost:5432/panel?sslmode=disable'`
- `PANEL_DB_URL` env var (honored when `--db-url` is left at its default)
The default (dev) DSN is
`postgres://panel:panel_dev@127.0.0.1:5432/panel?sslmode=disable`.
Or let `install.sh` run the `panel-postgres` container for you.
---
## Controller flags
From `controller/cmd/controller/main.go`:
| Flag | Default | Meaning |
|---|---|---|
| `-grpc` | `:8443` | gRPC listen address (both Agent + Panel services) |
| `-http` | `:8080` | HTTP dashboard + REST API listen address |
| `-ca-dir` | `./data/ca` | directory holding internal CA + server cert (auto-generated on first run) |
| `-tls` | `auto` | `auto` \| `on` \| `off``auto` enables mTLS if CA + server cert present |
| `-tls-hostnames` | — | comma-separated SAN DNS names to add to the server cert (e.g. `panel.example.com`) |
| `-log-level` | `info` | `debug`, `info`, `warn`, `error` |
| `-db-url` | dev DSN | Postgres connection string (env `PANEL_DB_URL` also honored) |
| `-public-url` | — | public base URL of the dashboard (needed for Steam OpenID sign-in — see [ADMIN.md](ADMIN.md)) |
| `-initial-admin-steam-id` | — | 17-digit SteamID64 linked to the first admin, enabling Sign in with Steam ([ADMIN.md](ADMIN.md)) |
Env vars: `PANEL_DB_URL`, `PANEL_ADMIN_PASSWORD` (first-boot admin
password preseed).
## Agent flags
From `agent/cmd/agent/main.go`:
| Flag | Default | Meaning |
|---|---|---|
| `--controller` | `localhost:8443` | controller gRPC address `host:port` |
| `--agent-id` | mTLS cert CN, or hostname when `--insecure` | agent identifier |
| `--modules-dir` | `./modules` | path to modules directory |
| `--data-root` | `./data/instances` | root directory for instance data |
| `--meta-dir` | `./data/instance-meta` | per-instance sidecar metadata (survives agent restart) |
| `--backup-dir` | `./data/backups` | where instance backup archives are written |
| `--cert-dir` | `./data/certs` | holds `agent.crt` + `agent.key` + `ca.crt` after pairing |
| `--heartbeat` | `10s` | heartbeat interval |
| `--insecure` | `false` | force plaintext (no TLS) — dev/local only |
| `--pair-token` | — | pair mode: fetch CA + signed cert from `--pair-url` and exit ([PAIRING.md](PAIRING.md)) |
| `--pair-url` | `http://localhost:8080` | controller HTTP URL for the pair endpoints |
| `--log-level` | `info` | `debug`, `info`, `warn`, `error` |
## systemd
Reference units live in `deploy/systemd/` (`panel-controller.service`,
`panel-agent.service`) — `install.sh` installs them for you. Highlights:
- Both run as the `panel` user from `WorkingDirectory=/opt/panel`; the
agent additionally gets `SupplementaryGroups=docker`.
- `/opt/panel/panel.env` (optional `EnvironmentFile`) overrides
`PANEL_HTTP_PORT`, `PANEL_GRPC_PORT`, `PANEL_DB_URL`,
`PANEL_ADMIN_PASSWORD`, ...
- The controller unit is hardened (`ProtectSystem=strict`,
`ReadWritePaths=/opt/panel`); the agent's hardening is intentionally
lighter because it drives the Docker socket.
```bash
sudo cp deploy/systemd/*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now panel-controller panel-agent
journalctl -u panel-controller -f # ADMIN BOOTSTRAP banner lives here
```
## Do you even need a reverse proxy?
**Not necessarily.** The panel serves its own dashboard on `:8080` and works
fine with **no proxy at all**:
- **Home / LAN use** — just browse to `http://<your-box-ip>:8080`. Steam
sign-in works too (Steam returns you to that same address). Nothing else
to set up.
- **You just want HTTPS and a domain** — a reverse proxy (Caddy is the
one-file option below) gives you a real cert. Optional, but nice.
You **do** want a proxy the moment the panel is internet-facing (real TLS,
not plain HTTP over the internet). If you add one, read the Steam note below
— a misconfigured proxy is the one thing that quietly breaks Steam login.
## Reverse proxy + real TLS
The dashboard speaks plain HTTP on `:8080`; put a reverse proxy with a
real certificate in front of it for anything internet-facing. **Only
proxy the HTTP port** — agents talk to gRPC `:8443` directly (mTLS,
LAN/VPN is fine; see [PAIRING.md](PAIRING.md)).
> **Steam login behind a proxy — read this.** When a proxy terminates TLS,
> the controller must know its real public HTTPS URL or Steam's return trip
> lands on the wrong origin and login fails. Do **one** of:
> - start the controller with `--public-url https://panel.example.com`
> (most reliable — set it and forget it), **or**
> - make sure your proxy forwards the `X-Forwarded-Proto` header (the Caddy
> and nginx examples below already do the right thing).
>
> If you skip both, the controller logs a warning at first Steam-login
> attempt telling you exactly this. With **no proxy** (direct IP/LAN), none
> of this applies — Steam login just works.
The dashboard uses SSE (`/api/events`) — disable proxy buffering on
that path or the live console will lag.
### Caddy
```
panel.example.com {
reverse_proxy 127.0.0.1:8080 {
flush_interval -1 # SSE: stream immediately
}
}
```
That's the whole file — Caddy handles Let's Encrypt automatically.
### nginx
```nginx
server {
listen 443 ssl;
server_name panel.example.com;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
client_max_body_size 512m; # scenario/mod zip uploads
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/events { # SSE — no buffering
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1h;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
```
If the proxy terminates TLS with a public hostname and agents pair
through it, add that hostname to the controller cert SANs:
`-tls-hostnames panel.example.com`.
## Firewall basics
On the **controller** box:
| Port | Proto | Who needs it | Expose to internet? |
|---|---|---|---|
| 8080 (HTTP) | tcp | browsers, `panelctl admin`, pairing agents | only via the reverse proxy above |
| 8443 (gRPC) | tcp | agents + `panelctl` | no — LAN/VPN preferred; mTLS protects it if you must |
| 5432 (Postgres) | tcp | controller only | never |
On each **agent** box: no inbound panel ports at all — the agent dials
out to the controller. You only open the **game ports** of the instances
it hosts; see [NETWORKING.md](NETWORKING.md) for the per-game table.
Example (ufw, controller on a LAN):
```bash
sudo ufw allow from 192.168.1.0/24 to any port 8080 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 8443 proto tcp
```