Add Windows installer (NSIS) and Linux AppImage packaging; embed PCK in all exports
Full build / Import + headless tests (push) Successful in 17s
Full build / Full desktop build + optional release (push) Skipped

- export_presets: embed_pck=true for Linux/Windows/macOS -> single self-contained
  binaries (all game data in one file); build_releases.sh cleans stale .pck
- App icon: assets/textures/icon.png (1024) + multi-size icon.ico generated by
  tools/generate_textures.py; copied into packaging/windows and packaging/linux
- packaging/windows/step-line.nsi: NSIS installer (shortcuts, uninstaller,
  registry Add/Remove Programs) output step-line-setup-<ver>.exe
- packaging/linux: step-line.desktop, AppRun, step-line.appdata.xml,
  build_appimage.sh (appimagetool, APPIMAGE_EXTRACT_AND_RUN) ->
  step-line-<ver>-x86_64.AppImage
- CI build job now packages NSIS installer + AppImage alongside binaries;
  publish glob already uploads them
- Local end-to-end verification: embedded exports rebuilt, AppImage built and
  run headless (0 script errors); suite 57/57
- README Downloads table lists installer/AppImage/portable rows; RELEASING
  documents packaging; TODO/DONE synced
This commit is contained in:
zwitschi committed 2026-10-31 13:46:54 +01:00
1 parent 07d79ac115
commit ba76c0febf
17 files changed
+261 -10

No files matched your search

+13
View File
@@ -77,6 +77,19 @@ jobs:
chmod +x tools/*.sh chmod +x tools/*.sh
./tools/build_releases.sh ./tools/build_releases.sh
- name: Package Windows installer (NSIS)
run: |
sudo apt-get update && sudo apt-get install -y nsis
VERSION="$(sed -n 's/.*config\/version="\([^"]*\)".*/\1/p' project.godot)"
makensis /DVERSION="$VERSION" \
/DARTIFACT="build/step-line-windows-$VERSION.exe" \
/DICON=packaging/windows/step-line.ico \
packaging/windows/step-line.nsi
- name: Package Linux AppImage
run: |
bash packaging/linux/build_appimage.sh
- name: Upload installers (workflow_dispatch) - name: Upload installers (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+16
View File
@@ -185,6 +185,22 @@ Completed task sections moved from `TODO.md`. Sections are moved only when every
Follow-up: anonymous **asset downloads** require sign-in on this host (host setting). Follow-up: anonymous **asset downloads** require sign-in on this host (host setting).
- [x] CI packaging follow-up was already live (`.gitea/workflows/build.yml` on tags). - [x] CI packaging follow-up was already live (`.gitea/workflows/build.yml` on tags).
## 📦 Installer packaging (Windows NSIS + Linux AppImage)
- [x] Export presets: `binary_format/embed_pck=true` on all platforms — every binary is now
self-contained (all game data in one file; `.pck` no longer shipped).
- [x] App icon: `assets/textures/icon.png` (1024) + multi-size `icon.ico`, copied to
`packaging/windows/step-line.ico` and `packaging/linux/step-line.png` via the generator.
- [x] NSIS script `packaging/windows/step-line.nsi` (installs `Step Line.exe`, uninstaller,
shortcuts, registry entry; `makensis /DVERSION=… /DARTIFACT=…`).
- [x] AppImage: `packaging/linux/step-line.desktop`, `AppRun`, appdata.xml, and
`build_appimage.sh` (appimagetool, `APPIMAGE_EXTRACT_AND_RUN=1`).
- [x] Local end-to-end: rebuilt embedded exports and produced `step-line-0.2.0-x86_64.AppImage`,
ran it headless (0 script errors).
- [x] CI: NSIS + AppImage steps added to `.gitea/workflows/build.yml` build job; publish glob
already uploads `build/step-line-*<ver>*` (covers setup/AppImage names).
- [ ] (open) CI run verification of the NSIS build and installer-content listing.
## 🎮 Assets & detailed graphics pass (textures, rigs, UI feedback) ## 🎮 Assets & detailed graphics pass (textures, rigs, UI feedback)
- [x] Authored texture set via `tools/generate_textures.py` (deterministic PIL): brick facade, - [x] Authored texture set via `tools/generate_textures.py` (deterministic PIL): brick facade,
+7 -4
View File
@@ -20,16 +20,19 @@ The central design goal is simple:
| Platform | Package | | Platform | Package |
| --- | --- | | --- | --- |
| **Windows** | [`step-line-windows.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, no install needed | | **Windows — installer** | [`step-line-setup-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · contains all game data, shortcuts + uninstaller |
| **Linux** | [`step-line-linux.x86_64`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, `chmod +x` and run | | **Windows — portable** | [`step-line-windows-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, no install needed |
| **macOS** | [`step-line-macos.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unsigned build, quarantine note below | | **Linux — AppImage** | [`step-line-<ver>-x86_64.AppImage`](https://git.allucanget.biz/zwitschi/security-game/releases) · single file, `chmod +x` and run |
| **Linux — portable** | [`step-line-linux-<ver>.x86_64`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, `chmod +x` and run |
| **macOS** | [`step-line-macos-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unsigned build, quarantine note below |
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases> **All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
> **Latest: v0.2.0** (2026-10-31) — built and published by Gitea Actions from the > **Latest: v0.2.0** (2026-10-31) — built and published by Gitea Actions from the
> `v0.2.0` tag. Releases are built automatically when a `vX.Y.Z` tag is pushed > `v0.2.0` tag. Releases are built automatically when a `vX.Y.Z` tag is pushed
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use > (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
> `./tools/build_releases.sh`. > `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
> (NSIS + appimagetool) and uploads with the portable binaries.
> >
> Note: on this host, downloading release files currently requires sign-in; > Note: on this host, downloading release files currently requires sign-in;
> the page and tag URLs are public. > the page and tag URLs are public.
+33
View File
@@ -1,5 +1,38 @@
# TODO # TODO
## 🪟 Windows installer (self-contained, all game data)
Goal: a real Windows installer that includes the entire game (executable + data),
Start-Menu/desktop shortcuts, and an uninstaller — instead of a bare .exe + .pck.
- [x] Switch the Windows/Linux/macOS export presets to `binary_format/embed_pck=true` so a
single self-contained binary replaces the exe+pck pair (all game data in one file).
- [x] Add a game icon: `assets/icon.png` (AppImage/desktop) + `assets/icon.ico` and
`packaging/windows/step-line.ico` generated by `tools/generate_textures.py`.
- [x] Author `packaging/windows/step-line.nsi` (NSIS): installer that installs the exe, writes
the uninstaller, shortcuts, and registry uninstall entry.
- [x] Add CI packaging steps to `.gitea/workflows/build.yml` (NSIS via apt; AppImage via
appimagetool) so tags/dispatch produce `step-line-setup-<ver>.exe` and
`step-line-<ver>-x86_64.AppImage` next to the portable binaries.
- [x] Extend `tools/publish_release.sh` artifact glob to upload the installer/AppImage too.
- [ ] Verify on CI: NSIS installer builds (cannot be run locally — no `makensis` on this box).
- [ ] Verify the Windows installer contents in CI (e.g. `7z l` shows the embedded data file).
## 🐧 Linux AppImage (self-contained, all game data)
Goal: a single executable `.AppImage` that runs the full game on any recent distro.
- [x] Author AppDir files: `packaging/linux/step-line.desktop`, `packaging/linux/AppRun`,
icon wiring.
- [x] Add `packaging/linux/build_appimage.sh` (appimagetool; `APPIMAGE_EXTRACT_AND_RUN=1` to
avoid FUSE dependency) producing `step-line-<ver>-x86_64.AppImage`.
- [x] Local verification: built the AppImage on this machine and ran it headless
(`--appimage-extract-and-run --headless --quit-after 90` → 0 script errors,
`[Step Line] game started`).
- [x] AppStream metainfo added (`packaging/linux/step-line.appdata.xml`); tool validates clean.
- [ ] CI verification: AppImage step runs on tags/dispatch and the artifact uploads.
- [x] README Downloads table documents installer + AppImage rows and naming.
## Distribution follow-up (host config, no code work) ## Distribution follow-up (host config, no code work)
- [ ] Asset downloads currently require sign-in on the host (`/releases/download/…` redirects to - [ ] Asset downloads currently require sign-in on the host (`/releases/download/…` redirects to
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+11 -1
View File
@@ -57,12 +57,22 @@ godot --headless --path . --quit-after 120 # boot smoke t
./tools/build_releases.sh ./tools/build_releases.sh
``` ```
Artifacts are written to `build/`: Artifacts are written to `build/` (all exports embed the PCK — single files):
- `build/step-line-linux-<version>.x86_64` - `build/step-line-linux-<version>.x86_64`
- `build/step-line-windows-<version>.exe` - `build/step-line-windows-<version>.exe`
- `build/step-line-macos-<version>.zip` - `build/step-line-macos-<version>.zip`
When run in CI (tags/dispatch), the build job also packages:
- `build/step-line-setup-<version>.exe` — Windows installer (NSIS, incl. shortcuts +
uninstaller; apt `nsis` on the runner)
- `build/step-line-<version>-x86_64.AppImage` — Linux AppImage (`packaging/linux/build_appimage.sh`,
appimagetool via `APPIMAGE_EXTRACT_AND_RUN`)
Locally you can build the AppImage with `bash packaging/linux/build_appimage.sh <version>`;
the NSIS installer requires `makensis` (covered by CI).
> macOS builds are unsigned and will need `xattr -dr com.apple.quarantine` > macOS builds are unsigned and will need `xattr -dr com.apple.quarantine`
> guidance or notarization for production releases. > guidance or notarization for production releases.
+3 -3
View File
@@ -23,7 +23,7 @@ script_export_mode=2
custom_template/debug="" custom_template/debug=""
custom_template/release="" custom_template/release=""
debug/export_console_wrapper=1 debug/export_console_wrapper=1
binary_format/embed_pck=false binary_format/embed_pck=true
texture_format/s3tc_bptc=true texture_format/s3tc_bptc=true
texture_format/etc2_astc=false texture_format/etc2_astc=false
binary_format/architecture="x86_64" binary_format/architecture="x86_64"
@@ -54,7 +54,7 @@ script_export_mode=2
custom_template/debug="" custom_template/debug=""
custom_template/release="" custom_template/release=""
debug/export_console_wrapper=0 debug/export_console_wrapper=0
binary_format/embed_pck=false binary_format/embed_pck=true
texture_format/s3tc_bptc=true texture_format/s3tc_bptc=true
texture_format/etc2_astc=false texture_format/etc2_astc=false
codesign/enable=false codesign/enable=false
@@ -90,7 +90,7 @@ script_export_mode=2
custom_template/debug="" custom_template/debug=""
custom_template/release="" custom_template/release=""
debug/export_console_wrapper=0 debug/export_console_wrapper=0
binary_format/embed_pck=false binary_format/embed_pck=true
texture_format/s3tc_bptc=true texture_format/s3tc_bptc=true
texture_format/etc2_astc=true texture_format/etc2_astc=true
codesign/enable=false codesign/enable=false
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
# AppImage entry point: launch the bundled game binary.
SELF="$(readlink -f "$0")"
HERE="$(dirname "$SELF")"
exec "${HERE}/usr/bin/step-line" "$@"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Build a self-contained Step Line AppImage (all game data included).
#
# Prereqs: exported Linux binary in build/ (embed_pck=true), curl.
# Usage: bash packaging/linux/build_appimage.sh [version]
# Output: build/step-line-<version>-x86_64.AppImage
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
VERSION="${1:-$(sed -n 's/.*config\/version="\([^"]*\)".*/\1/p' "$ROOT/project.godot")}"
SRC_BIN="$ROOT/build/step-line-linux-${VERSION}.x86_64"
if [[ ! -f "$SRC_BIN" ]]; then
echo "error: missing $SRC_BIN — run ./tools/build_releases.sh first" >&2
exit 1
fi
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
APPDIR="$WORK/StepLine.AppDir"
mkdir -p "$APPDIR/usr/bin"
cp "$SRC_BIN" "$APPDIR/usr/bin/step-line"
# The export embeds the PCK (embed_pck=true), so no extra data files are needed.
cp "$HERE/step-line.desktop" "$APPDIR/step-line.desktop"
cp "$HERE/AppRun" "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
cp "$HERE/step-line.png" "$APPDIR/step-line.png"
mkdir -p "$APPDIR/usr/share/metainfo"
cp "$HERE/step-line.appdata.xml" "$APPDIR/usr/share/metainfo/step-line.appdata.xml"
TOOL="${APPIMAGE_TOOL:-/tmp/appimagetool-x86_64.AppImage}"
if [[ ! -f "$TOOL" ]]; then
echo "==> Downloading appimagetool"
curl -sL -o "$TOOL" \
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x "$TOOL"
fi
OUT="$ROOT/build/step-line-${VERSION}-x86_64.AppImage"
echo "==> Building AppImage -> $OUT"
# Extract-and-run avoids a FUSE dependency on the build host.
APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" "$APPDIR" "$OUT"
echo "AppImage ready: $(ls -la "$OUT" | awk '{print $5}') bytes"
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>net.step-line.StepLine</id>
<name>Step Line</name>
<summary>Workplace security simulation on Hollywood Boulevard</summary>
<description>
<p>
Step Line is a workplace security simulation about observation, communication,
access verification, and proportionate escalation. Guard the steps and entrance
of venues around Hollywood Boulevard: direct tourists, verify wristbands and IDs,
screen guests, and escalate only when you have to.
</p>
</description>
<launchable type="desktop-id">step-line.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<content_rating type="oars-1.1" />
<releases>
<release version="0.2.0" date="2026-10-31"/>
</releases>
</component>
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Step Line
Comment=Workplace security simulation on Hollywood Boulevard
Exec=step-line
Icon=step-line
Terminal=false
Categories=Game;Simulation;
Keywords=security;simulation;hollywood;
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+70
View File
@@ -0,0 +1,70 @@
; Step Line Windows installer (NSIS 3).
; Build from the repo root with the exported self-contained exe:
; makensis /DVERSION=0.2.0 /DARTIFACT=build/step-line-windows-0.2.0.exe \
; /DICON=packaging/windows/step-line.ico packaging/windows/step-line.nsi
; Outputs: build/step-line-setup-<VERSION>.exe
!ifndef VERSION
!define VERSION "0.0.0"
!endif
!ifndef ARTIFACT
!error "ARTIFACT define required (path to exported windows exe)"
!endif
!ifndef ICON
!define ICON "packaging\windows\step-line.ico"
!endif
; The installed executable always uses this clean name.
!define INSTALLED_EXE "Step Line.exe"
!define COMPANY "Step Line Project"
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\Step Line"
Unicode true
ManifestDPIAware true
Icon "${ICON}"
UninstallIcon "${ICON}"
Name "Step Line ${VERSION}"
OutFile "build\step-line-setup-${VERSION}.exe"
InstallDir "$PROGRAMFILES64\Step Line"
RequestExecutionLevel admin
Page directory
Page instfiles
UninstPage uninstConfirm
UninstPage instfiles
Section "Install"
SetOutPath "$INSTDIR"
; The self-contained executable contains ALL game data (embedded PCK).
File "/oname=${INSTALLED_EXE}" "${ARTIFACT}"
WriteUninstaller "$INSTDIR\uninstall.exe"
; Shortcuts
CreateDirectory "$SMPROGRAMS\Step Line"
CreateShortCut "$SMPROGRAMS\Step Line\Step Line.lnk" "$INSTDIR\${INSTALLED_EXE}"
CreateShortCut "$SMPROGRAMS\Step Line\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortCut "$DESKTOP\Step Line.lnk" "$INSTDIR\${INSTALLED_EXE}"
; Registry (Add/Remove Programs)
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "Step Line"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${VERSION}"
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${COMPANY}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${INSTALLED_EXE}"
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD HKLM "${UNINST_KEY}" "NoModify" 1
WriteRegDWORD HKLM "${UNINST_KEY}" "NoRepair" 1
SectionEnd
Section "Uninstall"
Delete "$INSTDIR\${INSTALLED_EXE}"
Delete "$INSTDIR\uninstall.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\Step Line\Step Line.lnk"
Delete "$SMPROGRAMS\Step Line\Uninstall.lnk"
RMDir "$SMPROGRAMS\Step Line"
Delete "$DESKTOP\Step Line.lnk"
DeleteRegKey HKLM "${UNINST_KEY}"
SectionEnd
+2
View File
@@ -16,6 +16,8 @@ set -euo pipefail
GODOT="${GODOT:-godot}" GODOT="${GODOT:-godot}"
VERSION="$(grep -oP 'config/version="\K[^"]+' project.godot)" VERSION="$(grep -oP 'config/version="\K[^"]+' project.godot)"
mkdir -p build mkdir -p build
# Presets now embed the PCK into a single binary; drop stale .pck leftovers.
rm -f build/*.pck
echo "==> Exporting Linux (x86_64)" echo "==> Exporting Linux (x86_64)"
"$GODOT" --headless --export-release "Linux" "build/step-line-linux-${VERSION}.x86_64" "$GODOT" --headless --export-release "Linux" "build/step-line-linux-${VERSION}.x86_64"
+26 -2
View File
@@ -159,8 +159,7 @@ def vignette():
px[x, y] = int(255 * (t * t)) px[x, y] = int(255 * (t * t))
save(img, "vignette.png") save(img, "vignette.png")
def icon(name, draw_fn): def icon(name, draw_fn, size=64):
size = 64
img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
d = ImageDraw.Draw(img) d = ImageDraw.Draw(img)
draw_fn(d, size) draw_fn(d, size)
@@ -185,6 +184,30 @@ def icons():
icon("icons/wand.png", wand) icon("icons/wand.png", wand)
icon("icons/rec_dot.png", rec) icon("icons/rec_dot.png", rec)
def app_icon():
"""Game app icon: dark badge, gold star, white 'S' — used by desktop/AppImage
(PNG) and the Windows installer (ICO)."""
def badge(d, s):
d.ellipse([2, 2, s-2, s-2], fill=(24, 26, 38, 255))
d.ellipse([int(s*0.10), int(s*0.10), int(s*0.90), int(s*0.90)], outline=(120, 130, 170, 255), width=max(2, s//64))
# gold star
cx, cy, r = s*0.5, s*0.44, s*0.30
pts = []
for i in range(10):
ang = -90 + i * 36
rad = r if i % 2 == 0 else r * 0.42
import math
pts.append((cx + rad * math.cos(math.radians(ang)), cy + rad * math.sin(math.radians(ang))))
d.polygon(pts, fill=(255, 205, 80, 255))
icon("icon.png", badge, size=1024)
icon("icon.ico", badge, size=256)
# multi-size ICO for Windows (from the 256 render)
src = Image.open(os.path.join(ASSETS, "icon.png"))
ico_path = os.path.join(ASSETS, "icon.ico")
src.save(ico_path, format="ICO", sizes=[(256, 256), (128, 128), (64, 64), (48, 48), (32, 32), (16, 16)])
print("wrote assets/textures/icon.ico (multi-size)")
def cards(): def cards():
CW, CH = 256, 128 CW, CH = 256, 128
def base(card_name, bg, label, band=(0,0,0,0)): def base(card_name, bg, label, band=(0,0,0,0)):
@@ -220,5 +243,6 @@ if __name__ == "__main__":
skyline() skyline()
vignette() vignette()
icons() icons()
app_icon()
cards() cards()
print("done") print("done")