Files
deploy_home/ansible/roles/podman/templates/zomboid/entrypoint.sh.j2
T
Bastian de BylandClaude Opus 5 ea98d1b556 fix: strip the server-only log stack script that breaks client world load
The dedicated server (app 380870) and the client are packaged separately and
have drifted. Base.Log_Stack_01 was removed from the client in 42.16, but the
server package still ships entity_logstack.txt.

The server registers every script it can see into a new world's
WorldDictionary, so the entry lands in the save, and every client then fails at
world load with:

  WorldDictionaryException: [SpriteConfigs] Missing dictionary script on
  client: Base.Log_Stack_01

Nothing in multiplayer can legitimately reference a script that no client has,
so dropping it server-side costs nothing.

The strip runs after the SteamCMD update rather than once at install time on
purpose: validate restores the file on every boot, so it has to be removed on
every boot too.

Note this does not repair a world whose dictionary already records the entry --
that save still needs regenerating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 14:36:36 -04:00

130 lines
4.9 KiB
Django/Jinja

#!/bin/bash
# Project Zomboid Build 42 Server Entrypoint
# Based on IndifferentBroccoli/projectzomboid-server-docker
set -e
# Configuration
INSTALL_DIR="/project-zomboid"
CONFIG_DIR="/project-zomboid-config"
STEAMCMD="/home/steam/steamcmd/steamcmd.sh"
SERVER_NAME="${SERVER_NAME:-zomboid}"
PUID="${PUID:-1000}"
PGID="${PGID:-1000}"
MIN_RAM="${MIN_RAM:-{{ zomboid_min_ram }}}"
MAX_RAM="${MAX_RAM:-{{ zomboid_max_ram }}}"
echo "=== Project Zomboid Build 42 Server ({{ zomboid_preset_version }}) ==="
echo "Server Name: ${SERVER_NAME}"
echo "RAM: ${MIN_RAM} - ${MAX_RAM}"
# Set user permissions (IndifferentBroccoli approach)
echo "=== Setting file permissions ==="
usermod -o -u "${PUID}" steam
groupmod -o -g "${PGID}" steam
for dir in "${INSTALL_DIR}" "${CONFIG_DIR}"; do
# Recursing is a full inode walk over the install tree -- with 256 workshop
# mods that is real SSD churn on every restart, and a no-op after the first.
if [ "$(stat -c %u "${dir}")" = "${PUID}" ]; then
echo "=== ${dir} already owned by ${PUID}, skipping recursive chown ==="
else
chown -R steam:steam "${dir}"
fi
done
# Only chown writable parts of /home/steam (not read-only mounts)
chown steam:steam /home/steam
chown -R steam:steam /home/steam/steamcmd 2>/dev/null || true
chown -R steam:steam /home/steam/Steam 2>/dev/null || true
# Create required directories
mkdir -p "${CONFIG_DIR}/Server"
mkdir -p "${CONFIG_DIR}/Saves"
mkdir -p "${CONFIG_DIR}/db"
# Update/Install PZ dedicated server from the default (stable) branch
if [ "${AUTO_UPDATE:-true}" = "true" ]; then
echo "=== Updating Project Zomboid Server (Build 42 stable) ==="
su -c "${STEAMCMD} +runscript /home/steam/install.scmd" steam
echo "=== Update complete ==="
fi
# Strip scripts the dedicated-server package ships but the client no longer has.
#
# The two Steam apps are packaged separately and have drifted. Base.Log_Stack_01
# was removed from the client in 42.16, but app 380870 still ships
# entity_logstack.txt. The server registers every script it can see into a new
# world's WorldDictionary, so the entry lands in the save, and then every client
# fails at world load with:
#
# WorldDictionaryException: [SpriteConfigs] Missing dictionary script on
# client: Base.Log_Stack_01
#
# Nothing in multiplayer can legitimately use a script no client has, so
# removing it server-side costs nothing. This runs after the SteamCMD update on
# purpose: validate restores the file on every boot, so it has to be stripped
# each time, not once.
#
# Removing the file does not fix a world whose dictionary already records the
# entry -- that save needs regenerating.
strip_client_missing_scripts() {
local stale="${INSTALL_DIR}/media/scripts/generated/entities/misc/entity_logstack.txt"
if [ -f "${stale}" ]; then
echo "=== Removing ${stale##*/} (shipped by server, absent from client) ==="
rm -f "${stale}"
fi
}
strip_client_missing_scripts
# Configure JVM memory settings in ProjectZomboid64.json (Build 42 uses JSON config)
configure_memory() {
local json_file="${INSTALL_DIR}/ProjectZomboid64.json"
if [ ! -f "$json_file" ]; then
echo "=== ProjectZomboid64.json not found, skipping memory config ==="
return 0
fi
echo "=== Configuring JVM memory: Xms=${MIN_RAM}, Xmx=${MAX_RAM} ==="
# Update Xmx
sed -i "s/-Xmx[0-9]*[gGmM]*/-Xmx${MAX_RAM}/g" "$json_file"
# Update or add Xms
if grep -q "\-Xms" "$json_file"; then
sed -i "s/-Xms[0-9]*[gGmM]*/-Xms${MIN_RAM}/g" "$json_file"
else
# Insert -Xms before -Xmx
sed -i "s/\"-Xmx/\"-Xms${MIN_RAM}\",\n\t\t\"-Xmx/g" "$json_file"
fi
echo "=== Memory configuration complete ==="
}
configure_memory
# Build server arguments
# Note: -modfolders is NOT used - mods are configured via INI only
# Reference: IndifferentBroccoli/projectzomboid-server-docker
SERVER_ARGS="-cachedir=${CONFIG_DIR} -servername ${SERVER_NAME}"
# Always pass the admin password when we have one.
#
# This used to be gated on the admin DB not existing yet, on the assumption that
# the file's presence meant the admin user was already set up. It does not: PZ
# creates the DB file in ServerWorldDatabase.create() *before* it creates the
# admin user, so any boot interrupted between those two steps leaves a database
# that exists but has no admin. From then on the guard withheld the flag, the
# server fell back to an interactive "Enter new administrator password:" prompt,
# read EOF because a detached container has no stdin, and died with
# NoSuchElementException -- on every restart, forever.
if [ -n "${ADMIN_PASSWORD}" ]; then
SERVER_ARGS="${SERVER_ARGS} -adminpassword ${ADMIN_PASSWORD}"
fi
# Note: Server password is set via INI file, not command line args
# Start server
cd "${INSTALL_DIR}"
echo "=== Starting Project Zomboid Server ==="
echo "Connect to: home.debyl.io:16261"
exec su -c "export LD_LIBRARY_PATH=${INSTALL_DIR}/jre64/lib:\${LD_LIBRARY_PATH} && ./start-server.sh ${SERVER_ARGS}" steam