#!/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/ 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// into the save volume --- # RustDedicated writes its world + save + cfg tree to /server/. # 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}"