panel public release

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 01:13:02 -07:00
commit 8a94ffd58f
2165 changed files with 301493 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# Panel production compose — copy to deploy/.env and edit.
# NOTE: values containing '#' or '$' must be quoted.
# REQUIRED — Postgres password for the "panel" DB user.
PANEL_DB_PASSWORD=change-me
# Optional — stable admin password for admin@panel.local on first boot.
# Leave empty to get a generated one printed in `docker compose logs controller`.
PANEL_ADMIN_PASSWORD=
# Optional — host ports for the dashboard and agent gRPC.
PANEL_HTTP_PORT=8080
PANEL_GRPC_PORT=8443
# Optional — image/version tag baked into the binaries (ldflags).
PANEL_VERSION=dev
# Optional — host directory for agent instance data. MUST be an absolute
# path that exists on the host; it is mounted at the SAME path inside the
# agent container (game-server bind mounts require host==container path).
PANEL_AGENT_DATA=/opt/panel/data
+36
View File
@@ -0,0 +1,36 @@
# Panel agent — production image.
# Build from the repo root: docker build -f deploy/Dockerfile.agent .
#
# ┌─────────────────────────────── SECURITY ───────────────────────────────┐
# │ The agent container mounts the HOST Docker socket (/var/run/docker.sock)│
# │ to create/manage game-server containers. Access to the docker socket │
# │ is equivalent to root on the host. Only run this on a machine you │
# │ fully control, and never expose the socket beyond this container. │
# └─────────────────────────────────────────────────────────────────────────┘
FROM golang:1.26 AS build
ARG VERSION=dev
WORKDIR /src
COPY . .
ENV CGO_ENABLED=0
RUN go build -trimpath \
-ldflags "-s -w -X github.com/dbledeez/panel/pkg/version.Version=${VERSION}" \
-o /out/agent ./agent/cmd/agent
# Not distroless: game-module install hooks may shell out (tar, unzip).
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates tar unzip xz-utils \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/panel
COPY --from=build /out/agent /opt/panel/bin/agent
COPY --from=build /src/modules /opt/panel/modules
# Instance data, sidecar metadata, backups, pairing certs — persist this.
VOLUME ["/opt/panel/data"]
ENTRYPOINT ["/opt/panel/bin/agent"]
CMD ["--controller", "controller:8443", \
"--modules-dir", "./modules", \
"--data-root", "./data/instances", \
"--meta-dir", "./data/instance-meta", \
"--backup-dir", "./data/backups", \
"--cert-dir", "./data/certs"]
+29
View File
@@ -0,0 +1,29 @@
# Panel controller — production image.
# Build from the repo root: docker build -f deploy/Dockerfile.controller .
# (the Makefile "docker" target does exactly this, injecting VERSION)
FROM golang:1.26 AS build
ARG VERSION=dev
WORKDIR /src
# The repo is a Go workspace (go.work) spanning controller/agent/pkg/proto —
# copy the whole tree so workspace resolution works.
COPY . .
ENV CGO_ENABLED=0
RUN go build -trimpath \
-ldflags "-s -w -X github.com/dbledeez/panel/pkg/version.Version=${VERSION}" \
-o /out/controller ./controller/cmd/controller \
&& go build -trimpath \
-ldflags "-s -w -X github.com/dbledeez/panel/pkg/version.Version=${VERSION}" \
-o /out/panelctl ./controller/cmd/panelctl
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /opt/panel
COPY --from=build /out/controller /opt/panel/bin/controller
COPY --from=build /out/panelctl /opt/panel/bin/panelctl
# Module manifests: the controller's port allocator reads ./modules.
COPY --from=build /src/modules /opt/panel/modules
# CA, admin-token, cluster stores — persist this volume.
VOLUME ["/opt/panel/data"]
EXPOSE 8080 8443
ENTRYPOINT ["/opt/panel/bin/controller"]
CMD ["-http", ":8080", "-grpc", ":8443", "-ca-dir", "./data/ca"]
+90
View File
@@ -0,0 +1,90 @@
# Panel — PRODUCTION single-box compose (postgres + controller + agent).
# (Dev Postgres-only compose lives at ../docker-compose.dev.yml.)
#
# Usage, from the repo root:
# cp deploy/.env.example deploy/.env # then edit deploy/.env
# 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 create a pair token in the UI
# (or via POST /api/admin/pair-tokens) — the agent pairs itself on start
# only if you exec the pairing step; see README / install.sh for the flow.
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: panel
POSTGRES_PASSWORD: ${PANEL_DB_PASSWORD:?set PANEL_DB_PASSWORD in deploy/.env}
POSTGRES_DB: panel
volumes:
- panel_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U panel -d panel"]
interval: 5s
timeout: 3s
retries: 10
controller:
build:
context: ..
dockerfile: deploy/Dockerfile.controller
args:
VERSION: ${PANEL_VERSION:-dev}
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
PANEL_DB_URL: postgres://panel:${PANEL_DB_PASSWORD}@postgres:5432/panel?sslmode=disable
# Optional: stable first-boot admin password instead of a generated one.
PANEL_ADMIN_PASSWORD: ${PANEL_ADMIN_PASSWORD:-}
ports:
- "${PANEL_HTTP_PORT:-8080}:8080" # dashboard + REST API
- "${PANEL_GRPC_PORT:-8443}:8443" # agent gRPC (mTLS)
volumes:
- panel_controller_data:/opt/panel/data
agent:
build:
context: ..
dockerfile: deploy/Dockerfile.agent
args:
VERSION: ${PANEL_VERSION:-dev}
restart: unless-stopped
depends_on:
- controller
# ┌────────────────────────────── SECURITY ──────────────────────────────┐
# │ The agent mounts the HOST Docker socket to create and manage game- │
# │ server containers. Docker-socket access is ROOT-EQUIVALENT on the │
# │ host: anyone who can reach this container's filesystem or process │
# │ can own the machine. Run this stack only on a host you fully trust, │
# │ keep the panel dashboard behind auth/TLS, and never re-export the │
# │ socket from this container. │
# └───────────────────────────────────────────────────────────────────────┘
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# IMPORTANT: the agent passes instance paths to the HOST docker daemon
# as bind mounts, so the host path and in-container path MUST match.
# Keep both sides of this mapping identical.
- ${PANEL_AGENT_DATA:-/opt/panel/data}:${PANEL_AGENT_DATA:-/opt/panel/data}
environment:
PANEL_GRPC_PORT: "8443"
command:
- "--controller"
- "controller:8443"
- "--modules-dir"
- "./modules"
- "--data-root"
- "${PANEL_AGENT_DATA:-/opt/panel/data}/instances"
- "--meta-dir"
- "${PANEL_AGENT_DATA:-/opt/panel/data}/instance-meta"
- "--backup-dir"
- "${PANEL_AGENT_DATA:-/opt/panel/data}/backups"
- "--cert-dir"
- "${PANEL_AGENT_DATA:-/opt/panel/data}/certs"
volumes:
panel_pgdata:
panel_controller_data:
+45
View File
@@ -0,0 +1,45 @@
# REFERENCE ONLY — install.sh GENERATES the real unit at install time,
# substituting the actual PANEL_DIR/ports/user (see write_unit in install.sh).
# This static copy assumes the /opt/panel defaults; edit it by hand if you wire
# the units up yourself (e.g. via compose) instead of running install.sh.
[Unit]
Description=Panel agent (Docker runtime for game-server instances)
Documentation=https://github.com/dbledeez/panel
After=network-online.target docker.service panel-controller.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=simple
# The agent user MUST be able to talk to the Docker daemon
# (member of the "docker" group, or root). install.sh handles this.
User=panel
Group=panel
SupplementaryGroups=docker
WorkingDirectory=/opt/panel
# Defaults first; /opt/panel/panel.env overrides (applied later).
Environment=PANEL_GRPC_PORT=8443
EnvironmentFile=-/opt/panel/panel.env
# Pairing (one-time cert issuance) is done by install.sh / the operator:
# /opt/panel/bin/agent --pair-url http://localhost:8080 --pair-token <token> --cert-dir ./data/certs
ExecStart=/opt/panel/bin/agent \
--controller localhost:${PANEL_GRPC_PORT} \
--modules-dir ./modules \
--data-root ./data/instances \
--meta-dir ./data/instance-meta \
--backup-dir ./data/backups \
--cert-dir ./data/certs
Restart=on-failure
RestartSec=3
# Hardening is intentionally lighter than the controller's: the agent
# manages Docker containers and bind-mounts instance data, so it needs
# the docker socket and its data tree.
NoNewPrivileges=true
ProtectHome=true
ReadWritePaths=/opt/panel
PrivateTmp=true
LockPersonality=true
[Install]
WantedBy=multi-user.target
+45
View File
@@ -0,0 +1,45 @@
# REFERENCE ONLY — install.sh GENERATES the real unit at install time,
# substituting the actual PANEL_DIR/ports/user (see write_unit in install.sh).
# This static copy assumes the /opt/panel defaults; edit it by hand if you wire
# the units up yourself (e.g. via compose) instead of running install.sh.
[Unit]
Description=Panel controller (HTTP dashboard :8080 + agent gRPC :8443)
Documentation=https://github.com/dbledeez/panel
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=simple
# install.sh creates this user; change to taste. The controller does NOT
# need docker access — only the agent does.
User=panel
Group=panel
# The controller resolves ./data (CA, admin-token, cluster stores) and
# ./modules relative to its working directory — keep them together here.
WorkingDirectory=/opt/panel
# Defaults first; /opt/panel/panel.env (PANEL_DB_URL, PANEL_HTTP_PORT,
# PANEL_ADMIN_PASSWORD, ...) overrides them because it is applied later.
Environment=PANEL_HTTP_PORT=8080
Environment=PANEL_GRPC_PORT=8443
EnvironmentFile=-/opt/panel/panel.env
ExecStart=/opt/panel/bin/controller \
-http :${PANEL_HTTP_PORT} \
-grpc :${PANEL_GRPC_PORT} \
-ca-dir ./data/ca
Restart=on-failure
RestartSec=3
# Hardening — the controller only needs its own tree + the network.
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/panel
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
[Install]
WantedBy=multi-user.target