panel v0.9.2 — public release

Self-hostable game server control panel: Go controller + agent, 26 game
modules, embedded web UI. One-line install via install.sh / install.ps1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:43:35 -07:00
commit 4ccccc6fe2
2164 changed files with 301480 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
# panel-native Palworld runtime image.
FROM debian:12-slim
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
RUN dpkg --add-architecture i386 \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
locales \
tini \
lib32gcc-s1 \
lib32stdc++6 \
libc6:i386 \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
&& locale-gen \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1000 panel \
&& useradd -u 1000 -g 1000 -m -s /bin/bash panel \
&& mkdir -p /game /game-saves \
&& chown -R panel:panel /game /game-saves
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8211/udp 27015/udp 25575/tcp
ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"]
+91
View File
@@ -0,0 +1,91 @@
#!/bin/bash
# panel-native Palworld entrypoint.
set -euo pipefail
log() { printf '[panel-palworld] %s\n' "$*"; }
if [[ "$(id -u)" = "0" ]]; then
chown -R panel:panel /game /game-saves /home/panel 2>/dev/null || true
exec setpriv --reuid=panel --regid=panel --clear-groups \
--inh-caps=-all --bounding-set=-all \
env HOME=/game-saves PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
SERVER_NAME="${SERVER_NAME:-panel Palworld}" \
SERVER_DESCRIPTION="${SERVER_DESCRIPTION:-A Palworld server}" \
MAX_PLAYERS="${MAX_PLAYERS:-32}" \
RCON_PASSWORD="${RCON_PASSWORD:-panel_rcon}" \
GAME_PORT="${GAME_PORT:-8211}" \
QUERY_PORT="${QUERY_PORT:-27015}" \
RCON_PORT="${RCON_PORT:-25575}" \
/entrypoint.sh "$@"
fi
GAME_DIR=/game
SAVE_DIR=/game-saves
PAL_SH="${GAME_DIR}/PalServer.sh"
SETTINGS_DIR="${GAME_DIR}/Pal/Saved/Config/LinuxServer"
SETTINGS="${SETTINGS_DIR}/PalWorldSettings.ini"
DEFAULT_SETTINGS="${GAME_DIR}/DefaultPalWorldSettings.ini"
if [[ ! -x "${PAL_SH}" ]]; then
log "ERROR: ${PAL_SH} not found. Run 'Update' in the panel to install Palworld via SteamCMD."
exit 78
fi
# Seed PalWorldSettings.ini on first boot from the shipped default, then
# patch RCON + server name/description/max-players into it. Palworld
# ignores these keys on re-save unless they live in PalWorldSettings.ini
# under [/Script/Pal.PalGameWorldSettings].
mkdir -p "${SETTINGS_DIR}"
if [[ ! -s "${SETTINGS}" && -s "${DEFAULT_SETTINGS}" ]]; then
log "seeding ${SETTINGS} from shipped default"
cp -f "${DEFAULT_SETTINGS}" "${SETTINGS}"
fi
# Patch the single OptionSettings=(…) tuple each boot so panel-managed
# values (RCON auth, server identity) track env vars. Uses | as sed
# delimiter since the tuple contains slashes.
if [[ -s "${SETTINGS}" ]]; then
log "patching ${SETTINGS} (RCON + identity + ports)"
sed -i -E \
-e "s|RCONEnabled=(True\|False)|RCONEnabled=True|" \
-e "s|RCONPort=[0-9]+|RCONPort=${RCON_PORT}|" \
-e "s|PublicPort=[0-9]+|PublicPort=${GAME_PORT}|" \
-e "s|AdminPassword=\"[^\"]*\"|AdminPassword=\"${RCON_PASSWORD}\"|" \
-e "s|ServerPlayerMaxNum=[0-9]+|ServerPlayerMaxNum=${MAX_PLAYERS}|" \
-e "s|ServerName=\"[^\"]*\"|ServerName=\"${SERVER_NAME}\"|" \
-e "s|ServerDescription=\"[^\"]*\"|ServerDescription=\"${SERVER_DESCRIPTION}\"|" \
"${SETTINGS}"
fi
# Redirect Pal saves into the save volume so SteamCMD validate can't clobber.
mkdir -p "${SAVE_DIR}/Pal"
if [[ -d "${GAME_DIR}/Pal/Saved" && ! -L "${GAME_DIR}/Pal/Saved" ]]; then
# Migrate any existing shipped saves once, then symlink.
if [[ ! -d "${SAVE_DIR}/Pal/Saved" ]]; then
mv "${GAME_DIR}/Pal/Saved" "${SAVE_DIR}/Pal/Saved"
else
rm -rf "${GAME_DIR}/Pal/Saved"
fi
fi
mkdir -p "${SAVE_DIR}/Pal/Saved"
ln -sfn "${SAVE_DIR}/Pal/Saved" "${GAME_DIR}/Pal/Saved"
cd "${GAME_DIR}"
log "starting Palworld dedicated server"
log " name: ${SERVER_NAME}"
log " max_players: ${MAX_PLAYERS}"
log " game port: ${GAME_PORT}/udp"
log " query port: ${QUERY_PORT}/udp"
log " rcon port: ${RCON_PORT}/tcp"
exec stdbuf -oL -eL "${PAL_SH}" \
-port="${GAME_PORT}" \
-queryport="${QUERY_PORT}" \
-players="${MAX_PLAYERS}" \
-useperfthreads \
-NoAsyncLoadingThread \
-UseMultithreadForDS \
"-servername=${SERVER_NAME}" \
"-adminpassword=${RCON_PASSWORD}" \
-RconEnabled=True \
-RconPort="${RCON_PORT}" \
"-ServerDescription=${SERVER_DESCRIPTION}"
+127
View File
@@ -0,0 +1,127 @@
# Palworld — panel-native dedicated server module.
#
# Native Linux binary from SteamCMD app 2394010. Native Source RCON on
# port 25575 when EnableRCON=True in PalWorldSettings.ini.
#
# Reference: https://github.com/CubeCoders/AMPTemplates (PalworldLinux.kvp)
id: palworld
name: "Palworld"
version: 0.1.0
authors:
- panel contributors
supported_modes:
- docker
runtime:
docker:
image: panel-palworld:latest
# Host networking -- container shares the host net namespace, so
# any port the game/mod binds is directly on the LAN. AMP-like
# model. Multi-instance safety comes from env-driven per-instance
# ports (panel allocator + env: mapping on each ports entry).
network_mode: host
browseable_root: /game-saves
browseable_roots:
- name: "Saves & Configs"
path: /game-saves
hint: "World save + PalWorldSettings.ini + admin files"
- name: "Game Files"
path: /game
hint: "PalServer install, binaries"
env:
SERVER_NAME: "panel Palworld"
SERVER_DESCRIPTION: "A Palworld server"
MAX_PLAYERS: "32"
RCON_PASSWORD: "panel_rcon"
# Pre-declared so the agent's resolve.go filter lets panel-allocated
# ports flow through to the container env. Defaults match port defaults.
GAME_PORT: "8211"
QUERY_PORT: "27015"
RCON_PORT: "25575"
volumes:
- { type: volume, name: "panel-$INSTANCE_ID-saves", container: "/game-saves" }
- { type: volume, name: "panel-$INSTANCE_ID-game", container: "/game" }
- { target: "$DATA_PATH/logs", container: "/game-saves/logs" }
ports:
- { name: game, proto: udp, default: 8211, required: true, env: GAME_PORT }
- { name: query, proto: udp, default: 27015, required: true, env: QUERY_PORT }
- { name: rcon, proto: tcp, default: 25575, internal: true, env: RCON_PORT }
resources:
min_ram_mb: 6144
recommended_ram_mb: 16384 # Palworld is memory-hungry
# Per-instance generated RCON secret. The agent generates a random
# 24-byte token at create time (pkg/module GenerateSecrets), which
# overrides the env default for RCON_PASSWORD below — so every instance
# gets a unique password. The weak env default only applies to
# containers launched outside the panel.
secrets:
- name: RCON_PASSWORD
description: "Palworld RCON password — generated per instance"
generated: true
rcon:
adapter: source_rcon
host_port: rcon
auth: source_rcon_login
password_secret: RCON_PASSWORD
# Legacy fallback only (pre-generated-secret instances). New instances
# always get the generated RCON_PASSWORD secret above.
password_literal: "panel_rcon"
commands:
list: "ShowPlayers"
save: "Save"
broadcast: "Broadcast {msg}"
kick: "KickPlayer {player}"
ban: "BanPlayer {player}"
shutdown: "Shutdown 30 \"Server going down\""
state_sources:
- type: rcon
command: "ShowPlayers"
every: 60s
parse:
kind: regex
pattern: '^(?P<count>\d+)\s*$' # Fallback; real parsing in playerList
fields:
players_online: count
# Log-derived player events. Regexes converted from CubeCoders'
# AMPTemplates (palworld.kvp) — .NET (?<n>) syntax -> Go (?P<n>).
# Sourced-from-template; not yet exercised against a live client.
events:
join:
pattern: '(?P<name>.+?) \S+ connected the server\. \(User id: (?P<id>[^)]+)\)$'
kind: join
leave:
pattern: '(?P<name>.+?) left the server\. \(User id: (?P<id>[^)]+)\)$'
kind: leave
chat:
pattern: '^(?:\[[\d-]+ [\d:]+\] )?\[CHAT\] <(?P<name>.+?)> (?P<msg>.+)$'
kind: chat
update_providers:
- id: stable
kind: steamcmd
app_id: "2394010"
install_path: /game
# Force Linux depot — SteamCMD's auto-detect mis-fires in Docker
# Desktop on Windows (WSL2 backend), returning "Missing configuration"
# on anonymous app_update. Pinning platform fixes it.
platform: linux
# --- Manifest-driven UI metadata (WI-07) --------------------------
# Consumed by the CONTROLLER/dashboard only (the controller loads its
# own ./modules copy at startup); the agent ignores these at runtime.
# ready_pattern is a JavaScript regex (slash-delimited when it needs
# flags) moved verbatim from the dashboard's READY_PATTERNS map.
ready_pattern: '/Running Palworld dedicated server|Setting breakpad minidump AppID/i'
appearance:
emoji: "🐾"
grad: "linear-gradient(135deg,#006d77,#83c5be)"
art: "/game-art/palworld.jpg"
steam: "1623730"