Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Public view (not signed in) — invite code, server status, connected players, r

![Public view: invite + status + players + resources](docs/screenshots/01-public-view.jpg)

Admin view (signed in) — adds the Worlds card with per-row editor, Server Config with stage/apply/discard, Backups, Manual WindowsServer update, and the live log tail:
Admin view (signed in) — adds the Worlds card with per-row editor, Server Config with stage/apply/discard, Mods, Backups, Manual WindowsServer update, and the live log tail:

![Admin view: worlds + server config + backups + log](docs/screenshots/02-admin-view.jpg)

Expand Down Expand Up @@ -94,6 +94,12 @@ kubectl -n games cp "<path>/WindowsServer" windrose-0:/home/steam/windrose/Windo

Uploads preserve `R5/Saved/`, `ServerDescription.json`, and `WorldDescription.json`; a timestamped snapshot of the previous tree lands in `/home/steam/backups/<utc>/`.

### Optional: Upload Server-Side Mods

For normal `.pak` style server-side mods, use the admin console's **Mods** card instead of replacing the whole `WindowsServer/` tree. Upload a `.pak`, `.zip`, `.tar`, or `.tar.gz`; the UI stages the files under `R5/.mods-staging/` and writes desired state to `R5/.mods.staged.json`. Click **Apply + restart** to promote the staged set into `R5/Content/Paks/~mods/` and restart the game. Disable/delete operations are staged the same way.

The entrypoint preserves `~mods/`, `~mods.disabled/`, `.mods.json`, `.mods.staged.json`, and `.mods-staging/` across SteamCMD updates. UI backups created after this feature include those files and restore them with the save/identity state.

## Install On Kubernetes With Helm

```bash
Expand Down Expand Up @@ -408,6 +414,12 @@ All routes are served by the `windrose-ui` container at `:28080`. Static assets
| POST | `/api/backups/{id}/restore` | *destructive* | Swap a named backup's saves + identity back into the live tree. **This is the only supported recovery path — never raw-`cp` parts of a backup in place, the game's internal state under `Saved/` expects the whole tree to be consistent.** Fires `backup.restored`. |
| GET | `/api/backups/{id}/download` | authed | Stream a full backup (Saved/ + ServerDescription + WorldDescription + .backup-config + .idle-patch-override) as `.tar.gz`. Same shape `POST /api/backups/upload` accepts — the two form a round-trip for server migration (download on host A → upload on host B → restore). |
| POST | `/api/backups/upload` | *destructive* | Accept a `.tar.gz` (shape from Download above), extract into a new `manual-imported-<ts>/` pinned backup dir, and return its id. Caller typically follows with `POST /api/backups/{id}/restore` to complete a migration. Fires `backup.created` with `source: "imported"`. |
| GET | `/api/mods` | authed | List live/staged server-side mods and their files, upload time, enabled state, and pending action. |
| POST | `/api/mods/upload` | *destructive* | Stage a `.pak`, `.zip`, `.tar`, or `.tar.gz` mod upload. Does not touch live `~mods/`; apply via `POST /api/config/apply` + restart. |
| POST | `/api/mods/{id}/enable` | *destructive* | Stage a mod enable for next restart. |
| POST | `/api/mods/{id}/disable` | *destructive* | Stage a mod disable for next restart. |
| DELETE | `/api/mods/{id}` | *destructive* | Stage removal of a mod from the desired next-boot set. |
| DELETE | `/api/mods/staged` | *destructive* | Discard all staged mod changes and staged upload files. |
| GET | `/api/backup-config` | authed | Current effective auto-backup config (idleMinutes, floorHours, retainCount, retainDays) + the env-seeded defaults + last-run status. |
| PUT | `/api/backup-config` | *destructive* | Persist auto-backup config atomically to `$R5_DIR/.backup-config.json`. Validates range + type; file is authoritative thereafter. |
| GET | `/api/game-backups` | authed | Windrose's own auto-backups under `SaveProfiles/Default_Backups/` (world-only snapshots on the game's own schedule). |
Expand Down
55 changes: 42 additions & 13 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,45 @@ server_files_present() {
|| [ -f "${WINDROSE_SERVER_DIR}/R5/Binaries/Win64/WindroseServer-Win64-Shipping.exe" ]
}

steamcmd_preserved_files=(ServerDescription.json WorldDescription.json .mods.json .mods.staged.json)
steamcmd_preserved_dirs=("Content/Paks/~mods" "Content/Paks/~mods.disabled" ".mods-staging")

preserve_r5_state() {
local src_r5="$1"
local dst="$2"

for f in "${steamcmd_preserved_files[@]}"; do
if [ -f "${src_r5}/${f}" ]; then
cp -a "${src_r5}/${f}" "${dst}/${f}"
fi
done
for rel in "${steamcmd_preserved_dirs[@]}"; do
if [ -d "${src_r5}/${rel}" ]; then
mkdir -p "${dst}/$(dirname "${rel}")"
cp -a "${src_r5}/${rel}" "${dst}/${rel}"
fi
done
}

restore_r5_state() {
local src="$1"
local dst_r5="$2"

mkdir -p "${dst_r5}"
for f in "${steamcmd_preserved_files[@]}"; do
if [ -f "${src}/${f}" ]; then
cp -a "${src}/${f}" "${dst_r5}/${f}"
fi
done
for rel in "${steamcmd_preserved_dirs[@]}"; do
if [ -d "${src}/${rel}" ]; then
rm -rf "${dst_r5:?}/${rel}"
mkdir -p "$(dirname "${dst_r5:?}/${rel}")"
cp -a "${src}/${rel}" "${dst_r5:?}/${rel}"
fi
done
}

# Pull (and keep up-to-date) the Windrose Dedicated Server via SteamCMD
# app id 4129620. This is anonymous-downloadable — no Steam account /
# license required. Preserves R5/Saved and R5/ServerDescription.json
Expand Down Expand Up @@ -173,9 +212,7 @@ install_via_steamcmd() {
echo "$(timestamp) WARNING: Found abandoned preserve_dir ${stray} from a prior interrupted boot; restoring before proceeding"
mkdir -p "${WINDROSE_SERVER_DIR}/R5"
mv "${stray}/Saved" "${WINDROSE_SERVER_DIR}/R5/Saved"
for f in ServerDescription.json WorldDescription.json; do
[ -f "${stray}/${f}" ] && cp -a "${stray}/${f}" "${WINDROSE_SERVER_DIR}/R5/${f}"
done
restore_r5_state "${stray}" "${WINDROSE_SERVER_DIR}/R5"
fi
rm -rf "${stray}"
done
Expand All @@ -185,11 +222,7 @@ install_via_steamcmd() {
if [ -d "${WINDROSE_SERVER_DIR}/R5/Saved" ]; then
mv "${WINDROSE_SERVER_DIR}/R5/Saved" "${preserve_dir}/Saved"
fi
for f in ServerDescription.json WorldDescription.json; do
if [ -f "${WINDROSE_SERVER_DIR}/R5/${f}" ]; then
cp -a "${WINDROSE_SERVER_DIR}/R5/${f}" "${preserve_dir}/${f}"
fi
done
preserve_r5_state "${WINDROSE_SERVER_DIR}/R5" "${preserve_dir}"

mkdir -p "${WINDROSE_SERVER_DIR}"
# SteamCMD's anonymous app_update is flaky on the first attempt
Expand Down Expand Up @@ -239,11 +272,7 @@ EOF
rm -rf "${WINDROSE_SERVER_DIR}/R5/Saved"
mv "${preserve_dir}/Saved" "${WINDROSE_SERVER_DIR}/R5/Saved"
fi
for f in ServerDescription.json WorldDescription.json; do
if [ -f "${preserve_dir}/${f}" ]; then
cp -a "${preserve_dir}/${f}" "${WINDROSE_SERVER_DIR}/R5/${f}"
fi
done
restore_r5_state "${preserve_dir}" "${WINDROSE_SERVER_DIR}/R5"
rm -rf "${preserve_dir}"

if [ "${rc}" -ne 0 ]; then
Expand Down
Loading
Loading