Panel — public source drop (v0.9.0)

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>
This commit is contained in:
2026-07-14 21:17:39 -07:00
commit a00bd620a1
2160 changed files with 300574 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
#!/bin/bash
# panel-native Rust entrypoint.
#
# Contract:
# /game — SteamCMD-installed Rust Dedicated Server (app 258550).
# Populated by the panel's SteamCMD updater sidecar.
# /game-saves — world/save data + logs volume.
#
# Stage 1 runs as root: chown volumes, symlink server/<identity> into the
# save volume, then drop to the `panel` user via setpriv.
set -euo pipefail
log() { printf '[panel-rust] %s\n' "$*"; }
# ----- stage 1: root-only bootstrap -----
if [[ "$(id -u)" = "0" ]]; then
chown -R panel:panel /game /game-saves /home/panel 2>/dev/null || true
log "dropping privileges to panel (UID 1000)"
exec setpriv --reuid=panel --regid=panel --clear-groups \
--inh-caps=-all --bounding-set=-all \
env \
HOME=/home/panel \
SERVER_NAME="${SERVER_NAME:-panel Rust}" \
SERVER_DESCRIPTION="${SERVER_DESCRIPTION:-Powered by panel}" \
SERVER_IDENTITY="${SERVER_IDENTITY:-panel-rust}" \
SERVER_SEED="${SERVER_SEED:-12345}" \
SERVER_WORLD_SIZE="${SERVER_WORLD_SIZE:-3000}" \
MAX_PLAYERS="${MAX_PLAYERS:-50}" \
RCON_PASSWORD="${RCON_PASSWORD:-panel_rcon}" \
SERVER_URL="${SERVER_URL:-}" \
SERVER_HEADER_IMAGE="${SERVER_HEADER_IMAGE:-}" \
SERVER_LEVEL="${SERVER_LEVEL:-Procedural Map}" \
GAME_PORT="${GAME_PORT:-28015}" \
RCON_PORT="${RCON_PORT:-28016}" \
RUSTPLUS_PORT="${RUSTPLUS_PORT:-28082}" \
LD_LIBRARY_PATH="/game:/game/RustDedicated_Data/Plugins:/game/RustDedicated_Data/Plugins/x86_64:${LD_LIBRARY_PATH:-}" \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
/entrypoint.sh "$@"
fi
GAME_DIR=/game
SAVE_DIR=/game-saves
DEDI_EXE="${GAME_DIR}/RustDedicated"
if [[ ! -x "${DEDI_EXE}" ]]; then
log "ERROR: ${DEDI_EXE} not found or not executable."
log " Run 'Update' on this server in the panel — that launches a"
log " SteamCMD sidecar which installs Rust (app 258550) here."
exit 78 # EX_CONFIG
fi
# --- persist server/<identity>/ into the save volume ---
# RustDedicated writes its world + save + cfg tree to <game>/server/<identity>.
# First boot moves it to /game-saves + symlinks back, so SteamCMD validate
# can't clobber world data. Idempotent on re-boot.
mkdir -p "${SAVE_DIR}/server"
if [[ -d "${GAME_DIR}/server" && ! -L "${GAME_DIR}/server" ]]; then
# On fresh install this dir doesn't exist; server creates it on first boot.
# If it does exist (e.g. from an earlier panel image without the symlink),
# migrate and replace.
if [[ ! -d "${SAVE_DIR}/server/${SERVER_IDENTITY}" ]]; then
mv "${GAME_DIR}/server"/* "${SAVE_DIR}/server/" 2>/dev/null || true
fi
rm -rf "${GAME_DIR}/server"
fi
ln -sfn "${SAVE_DIR}/server" "${GAME_DIR}/server"
# Mirror logs to the bind-mounted logs dir for external access.
mkdir -p "${SAVE_DIR}/logs"
cd "${GAME_DIR}"
log "starting Rust Dedicated Server"
log " name: ${SERVER_NAME}"
log " identity: ${SERVER_IDENTITY}"
log " level: ${SERVER_LEVEL}"
log " seed: ${SERVER_SEED}"
log " size: ${SERVER_WORLD_SIZE}"
log " max: ${MAX_PLAYERS}"
log " game port: ${GAME_PORT}/udp"
log " rcon port: ${RCON_PORT}/tcp (internal, websocket)"
log " rust+: ${RUSTPLUS_PORT}/tcp (push, optional)"
trap 'log "SIGTERM received; stopping server"; kill -TERM "${RUST_PID:-0}" 2>/dev/null || true; wait "${RUST_PID:-0}" 2>/dev/null || true; exit 0' TERM INT
# Rust's own flags — all `+` prefixed. `-batchmode` / `-nographics` come
# before the `+` convars. We intentionally omit `-logFile`: Unity defaults
# to stdout when absent, and passing `/dev/stdout` or `-` makes RustDedicated
# fail with "Unable to open log file, exiting." on Linux-container Unity.
exec stdbuf -oL -eL "${DEDI_EXE}" \
-batchmode \
-nographics \
+server.ip 0.0.0.0 \
+server.port "${GAME_PORT}" \
+server.tickrate 10 \
+server.hostname "${SERVER_NAME}" \
+server.description "${SERVER_DESCRIPTION}" \
+server.identity "${SERVER_IDENTITY}" \
+server.level "${SERVER_LEVEL}" \
+server.seed "${SERVER_SEED}" \
+server.worldsize "${SERVER_WORLD_SIZE}" \
+server.maxplayers "${MAX_PLAYERS}" \
+server.saveinterval 600 \
+server.url "${SERVER_URL}" \
+server.headerimage "${SERVER_HEADER_IMAGE}" \
+rcon.web 1 \
+rcon.ip 0.0.0.0 \
+rcon.port "${RCON_PORT}" \
+rcon.password "${RCON_PASSWORD}" \
+app.port "${RUSTPLUS_PORT}" &
RUST_PID=$!
wait "${RUST_PID}"