summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 21:35:30 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 21:38:36 +0100
commit7830b3c5e89f0339949940b8986bf4849ad7f223 (patch)
treed4ebb4f10583cef1da3b181e093dd5d65ac8a0bb
parentv4.2.1: plain-language game copy and a balance fetch on login (diff)
downloadzagan-bet-7830b3c5e89f0339949940b8986bf4849ad7f223.tar.gz
zagan-bet-7830b3c5e89f0339949940b8986bf4849ad7f223.zip
v4.3.0: package the desktop app with verifiable installers
publish.sh builds the GUI with the gui-release profile, names it zagan-bet-gui-<version>-<platform>-<arch>, and renders a bash and a PowerShell installer next to each app. Both embed the app's SHA-256, verify the file (or fetch it), install per-user and add a launcher: a desktop entry plus an SVG icon on Linux, a Start-menu shortcut on Windows. The download page gains a Desktop app section with the apps and installers, keeps the terminal client and server, links the public git repository for the GPL source, and lists every file in the sha256sum block. deploy.sh verifies all four artifact kinds. Publishing refuses a dirty tree so the linked history matches the binaries.
-rw-r--r--AGENTS.md6
-rw-r--r--Cargo.toml2
-rw-r--r--docs/GUI-PLAN.md31
-rw-r--r--docs/TESTING.md17
-rw-r--r--gui/Cargo.toml2
-rw-r--r--gui/assets/zagan-bet.svg6
-rwxr-xr-xscripts/deploy.sh9
-rw-r--r--scripts/installers/linux.sh.in78
-rw-r--r--scripts/installers/windows.ps1.in75
-rwxr-xr-xscripts/publish.sh66
-rw-r--r--scripts/render-installer.py49
-rw-r--r--web/README.md43
-rw-r--r--web/index.php78
13 files changed, 416 insertions, 46 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 368a348..7258710 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -85,6 +85,12 @@
85- The embedded fonts have no suit or arrow glyphs. Draw card suits as shapes 85- The embedded fonts have no suit or arrow glyphs. Draw card suits as shapes
86 and use plain words for navigation; a glyph that is not in Open Sans or Hack 86 and use plain words for navigation; a glyph that is not in Open Sans or Hack
87 renders as tofu. 87 renders as tofu.
88- `scripts/publish.sh` builds the GUI with `gui-release` and renders the
89 installers with the published SHA-256. Installers verify the hash and install
90 per-user; never install system-wide or skip the check.
91- The download page links the public git repository
92 (https://git.2137697.xyz/zagan-bet.git) for the GPL corresponding source.
93 Run publish from a clean tree so the linked history matches the binaries.
88 94
89## Client TUI 95## Client TUI
90- `render_lines()` and `render()` must agree. If `render()` uses `\r\n`, 96- `render_lines()` and `render()` must agree. If `render()` uses `\r\n`,
diff --git a/Cargo.toml b/Cargo.toml
index 179cb39..22a6ec7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ members = ["gui"]
3 3
4[package] 4[package]
5name = "zagan-bet" 5name = "zagan-bet"
6version = "4.2.1" 6version = "4.3.0"
7edition = "2024" 7edition = "2024"
8license = "GPL-3.0-or-later" 8license = "GPL-3.0-or-later"
9 9
diff --git a/docs/GUI-PLAN.md b/docs/GUI-PLAN.md
index 89cf495..f058e41 100644
--- a/docs/GUI-PLAN.md
+++ b/docs/GUI-PLAN.md
@@ -162,18 +162,29 @@ Measured on Linux at 1080p with llvmpipe software GL:
162**Gate:** the screenshot set is ready for review. Remaining for the gate: 162**Gate:** the screenshot set is ready for review. Remaining for the gate:
163frame rate on integrated graphics and the Windows interactive pass. 163frame rate on integrated graphics and the Windows interactive pass.
164 164
165## Phase 5 — packaging 165## Phase 5 — packaging (code complete, v4.3.0)
166## Phase 5 — packaging 166
167 167- `publish.sh` builds the GUI with the `gui-release` profile for Linux and
168- `publish.sh` emits `zagan-bet-gui-<version>-<platform>-<arch>[.exe]`. 168 Windows and names the app `zagan-bet-gui-<version>-<platform>-<arch>[.exe]`.
169- Bash and PowerShell installers that verify the SHA-256, install to the user 169- It renders a bash installer and a PowerShell installer next to each app.
170 directory, and create `.desktop`/Start-menu entries. 170 Both embed the app's SHA-256, verify the file (or download it), install
171- Download page lists raw binaries and installers; add the source-code link. 171 per-user and add a launcher: `.desktop` plus an SVG icon on Linux, a
172 172 Start-menu shortcut on Windows. Templates live in `scripts/installers/`;
173**Gate:** `deploy.sh` verifies both artifact kinds; a clean machine installs 173 `render-installer.py` fills them in.
174from the page and launches. 174- The download page scans `bin/gui`, `bin/client` and `bin/server`, lists
175 apps, installers, clients and servers, and includes every file in the
176 `sha256sum` block. A Source section links the public git repository
177 (<https://git.2137697.xyz/zagan-bet.git>) for the GPL corresponding source.
178 `deploy.sh` verifies every artifact kind over the public URL.
179- `publish.sh` refuses to run from a dirty tree so the linked history matches
180 the binaries.
181
182**Gate:** deploy verification passes for apps, installers, clients and
183servers; the Linux installer is exercised in a clean HOME. The Windows
184installer needs a Windows machine before the gate closes.
175 185
176## Testing split 186## Testing split
187## Testing split
177 188
178- `View`/`Command` tests in the core: flows, gating, state. Always. 189- `View`/`Command` tests in the core: flows, gating, state. Always.
179- The TUI adapter remains the regression net through Phases 1–2. 190- The TUI adapter remains the regression net through Phases 1–2.
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 613cd08..c32ac22 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -266,3 +266,20 @@ terminal client; a saved login from one signs in the other.
266 the row is upgraded to bcrypt. 266 the row is upgraded to bcrypt.
267- Spin several times and confirm `plays` grows by one row per spin while 267- Spin several times and confirm `plays` grows by one row per spin while
268 `users.balance` changes by `winnings - bet`. 268 `users.balance` changes by `winnings - bet`.
269
270## Packaging checklist
271
2721. Set the version in `Cargo.toml` and `gui/Cargo.toml` (same MAJOR.MINOR),
273 run `cargo test --workspace`, and commit. `publish.sh` refuses a dirty tree
274 because the site links the committed git history as the GPL source.
2752. `./scripts/publish.sh` (add `DEST=` and `URL_BASE=` to stage elsewhere). It
276 builds the GUI with `gui-release`, the terminal clients and servers, and
277 renders the bash/PowerShell installers with each app's SHA-256.
2783. `php -S 127.0.0.1:8899 -t <staging>` and
279 `./scripts/deploy.sh <staging> http://127.0.0.1:8899/` to upload and verify
280 every app, installer, client and server against the page's checksums.
2814. On a clean user account: download an app with its installer, run the
282 installer, and launch the new menu entry. The installer must refuse a
283 corrupted download.
2845. Repeat on Windows for the `.ps1` installer and the Start-menu shortcut.
285
diff --git a/gui/Cargo.toml b/gui/Cargo.toml
index 5418794..8bcbda0 100644
--- a/gui/Cargo.toml
+++ b/gui/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "zagan-bet-gui" 2name = "zagan-bet-gui"
3version = "4.2.1" 3version = "4.3.0"
4edition = "2024" 4edition = "2024"
5license = "GPL-3.0-or-later" 5license = "GPL-3.0-or-later"
6description = "Desktop frontend for zagan-bet" 6description = "Desktop frontend for zagan-bet"
diff --git a/gui/assets/zagan-bet.svg b/gui/assets/zagan-bet.svg
new file mode 100644
index 0000000..74e5a3a
--- /dev/null
+++ b/gui/assets/zagan-bet.svg
@@ -0,0 +1,6 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2 <rect width="64" height="64" rx="14" fill="#0a0e12"/>
3 <rect x="9" y="9" width="46" height="46" rx="10" fill="#0f1a14" stroke="#1f7d52" stroke-width="2"/>
4 <path d="M22 20h20L22 44h20" fill="none" stroke="#4ec27b" stroke-width="6"
5 stroke-linecap="round" stroke-linejoin="round"/>
6</svg>
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 99129ad..b18d462 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -1,5 +1,6 @@
1#!/usr/bin/env bash 1#!/usr/bin/env bash
2# Upload web/ and verify every published artifact over the public URL. 2# Upload web/ and verify every published artifact (GUI app and installers,
3# terminal client and server) over the public URL.
3# 4#
4# Usage: 5# Usage:
5# ./scripts/deploy.sh user@host:/srv/zagan.bet https://zagan.bet/ 6# ./scripts/deploy.sh user@host:/srv/zagan.bet https://zagan.bet/
@@ -50,7 +51,11 @@ done
50[ -f web/index.php ] || die "web/index.php is missing" 51[ -f web/index.php ] || die "web/index.php is missing"
51 52
52shopt -s nullglob 53shopt -s nullglob
53artifacts=(web/bin/client/zagan-bet-* web/bin/server/zagan-bet-*) 54artifacts=(
55 web/bin/gui/zagan-bet-*
56 web/bin/client/zagan-bet-*
57 web/bin/server/zagan-bet-*
58)
54shopt -u nullglob 59shopt -u nullglob
55[ ${#artifacts[@]} -gt 0 ] || die "no artifacts in web/bin; run scripts/publish.sh first" 60[ ${#artifacts[@]} -gt 0 ] || die "no artifacts in web/bin; run scripts/publish.sh first"
56 61
diff --git a/scripts/installers/linux.sh.in b/scripts/installers/linux.sh.in
new file mode 100644
index 0000000..5b53c02
--- /dev/null
+++ b/scripts/installers/linux.sh.in
@@ -0,0 +1,78 @@
1#!/usr/bin/env bash
2# Zagan-Bet desktop installer for Linux.
3#
4# Verifies the app against its published SHA-256, installs it for the current
5# user and adds a launcher. Put the downloaded app next to this script, or run
6# the script on its own and it will fetch the matching binary.
7set -euo pipefail
8
9VERSION="__VERSION__"
10BINARY="__BINARY__"
11SHA256="__SHA256__"
12URL="__URL__"
13
14say() { printf '%s\n' "$*"; }
15die() { printf 'install: %s\n' "$*" >&2; exit 1; }
16
17command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required"
18
19here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
20source_file=""
21for candidate in "$here/$BINARY" "./$BINARY" "$HOME/Downloads/$BINARY"; do
22 if [ -f "$candidate" ]; then
23 source_file=$candidate
24 break
25 fi
26done
27
28cleanup=""
29if [ -z "$source_file" ]; then
30 command -v curl >/dev/null 2>&1 || die "put $BINARY next to this installer, or install curl"
31 cleanup=$(mktemp) || die "cannot create a temporary file"
32 say "Downloading $BINARY..."
33 curl -fsSL --max-time 300 "$URL" -o "$cleanup" || die "download failed"
34 source_file=$cleanup
35fi
36trap 'if [ -n "$cleanup" ]; then rm -f "$cleanup"; fi' EXIT
37
38actual=$(sha256sum "$source_file" | awk '{print $1}')
39[ "$actual" = "$SHA256" ] || die "checksum mismatch: expected $SHA256, got $actual"
40
41bin_dir="${XDG_BIN_HOME:-$HOME/.local/bin}"
42data_dir="${XDG_DATA_HOME:-$HOME/.local/share}"
43app="$bin_dir/zagan-bet-gui"
44desktop_dir="$data_dir/applications"
45icon_dir="$data_dir/icons/hicolor/scalable/apps"
46
47mkdir -p "$bin_dir" "$desktop_dir" "$icon_dir"
48install -m 0755 "$source_file" "$app"
49
50cat > "$icon_dir/zagan-bet.svg" <<'ICON'
51__ICON__
52ICON
53
54cat > "$desktop_dir/zagan-bet.desktop" <<DESKTOP
55[Desktop Entry]
56Type=Application
57Name=Zagan-Bet
58Comment=Slots, roulette, blackjack and crash
59Exec=$app
60Icon=zagan-bet
61Terminal=false
62Categories=Game;
63DESKTOP
64chmod 0644 "$desktop_dir/zagan-bet.desktop" "$icon_dir/zagan-bet.svg"
65
66if command -v update-desktop-database >/dev/null 2>&1; then
67 update-desktop-database "$desktop_dir" >/dev/null 2>&1 || true
68fi
69if command -v gtk-update-icon-cache >/dev/null 2>&1; then
70 gtk-update-icon-cache -q "$data_dir/icons/hicolor" >/dev/null 2>&1 || true
71fi
72
73say "Installed Zagan-Bet $VERSION to $app"
74say "A launcher was added to your applications menu."
75case ":$PATH:" in
76 *":$bin_dir:"*) ;;
77 *) say "Add $bin_dir to PATH to start it as 'zagan-bet-gui'." ;;
78esac
diff --git a/scripts/installers/windows.ps1.in b/scripts/installers/windows.ps1.in
new file mode 100644
index 0000000..fd2b8b3
--- /dev/null
+++ b/scripts/installers/windows.ps1.in
@@ -0,0 +1,75 @@
1# Zagan-Bet desktop installer for Windows.
2#
3# Verifies the app against its published SHA-256, installs it for the current
4# user and adds a Start-menu shortcut. Put the downloaded app next to this
5# script, or run the script on its own and it will fetch the matching binary.
6#
7# Run it with:
8# powershell -ExecutionPolicy Bypass -File .\zagan-bet-gui-*-install.ps1
9$ErrorActionPreference = "Stop"
10
11$Version = "__VERSION__"
12$Binary = "__BINARY__"
13$Sha256 = "__SHA256__"
14$Url = "__URL__"
15
16function Stop-Install([string]$Message) {
17 Write-Host "install: $Message" -ForegroundColor Red
18 exit 1
19}
20
21$here = Split-Path -Parent $MyInvocation.MyCommand.Path
22$candidates = @(
23 (Join-Path $here $Binary),
24 (Join-Path (Get-Location) $Binary),
25 (Join-Path $env:USERPROFILE "Downloads\$Binary")
26)
27$sourceFile = $null
28foreach ($candidate in $candidates) {
29 if (Test-Path -LiteralPath $candidate) {
30 $sourceFile = $candidate
31 break
32 }
33}
34
35$temporary = $null
36if (-not $sourceFile) {
37 $temporary = Join-Path ([System.IO.Path]::GetTempPath()) $Binary
38 Write-Host "Downloading $Binary..."
39 try {
40 Invoke-WebRequest -Uri $Url -OutFile $temporary -UseBasicParsing
41 } catch {
42 Stop-Install "download failed: $_"
43 }
44 $sourceFile = $temporary
45}
46
47try {
48 $actual = (Get-FileHash -LiteralPath $sourceFile -Algorithm SHA256).Hash.ToLowerInvariant()
49 if ($actual -ne $Sha256.ToLowerInvariant()) {
50 Stop-Install "checksum mismatch: expected $Sha256, got $actual"
51 }
52
53 $installDir = Join-Path $env:LOCALAPPDATA "Programs\Zagan-Bet"
54 New-Item -ItemType Directory -Force -Path $installDir | Out-Null
55 $target = Join-Path $installDir "zagan-bet-gui.exe"
56 Copy-Item -LiteralPath $sourceFile -Destination $target -Force
57
58 $startMenu = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
59 New-Item -ItemType Directory -Force -Path $startMenu | Out-Null
60 $shortcutPath = Join-Path $startMenu "Zagan-Bet.lnk"
61 $shell = New-Object -ComObject WScript.Shell
62 $shortcut = $shell.CreateShortcut($shortcutPath)
63 $shortcut.TargetPath = $target
64 $shortcut.WorkingDirectory = $installDir
65 $shortcut.IconLocation = "$target,0"
66 $shortcut.Description = "Slots, roulette, blackjack and crash"
67 $shortcut.Save()
68
69 Write-Host "Installed Zagan-Bet $Version to $target"
70 Write-Host "A Start-menu shortcut was created."
71} finally {
72 if ($temporary -and (Test-Path -LiteralPath $temporary)) {
73 Remove-Item -LiteralPath $temporary -Force
74 }
75}
diff --git a/scripts/publish.sh b/scripts/publish.sh
index 8240859..3a04631 100755
--- a/scripts/publish.sh
+++ b/scripts/publish.sh
@@ -3,14 +3,22 @@
3# download page expects: 3# download page expects:
4# 4#
5# zagan-bet-client-<version>-<platform>-<arch>[.exe] 5# zagan-bet-client-<version>-<platform>-<arch>[.exe]
6# zagan-bet-server-<version>-<platform>-<arch> 6# zagan-bet-server-<version>-<platform>-<arch>[.exe]
7# zagan-bet-gui-<version>-<platform>-<arch>[.exe]
8# zagan-bet-gui-<version>-<platform>-<arch>-install.sh
9# zagan-bet-gui-<version>-<platform>-<arch>-install.ps1
7# 10#
8# Cargo always writes target/<triple>/release/<bin>, so the rename happens here 11# Cargo always writes target/<triple>/<profile>/<bin>, so the rename happens
9# after each build. The version comes from Cargo.toml. 12# here after each build. The version comes from Cargo.toml.
13#
14# The GUI uses the workspace's `gui-release` profile (small, stripped, LTO).
15# The public git repository is the GPL source for these binaries, so publish
16# from a clean tree (or set ALLOW_DIRTY=1 for a preview).
10# 17#
11# Usage: 18# Usage:
12# ./scripts/publish.sh 19# ./scripts/publish.sh
13# DEST=/srv/www/bin ./scripts/publish.sh 20# DEST=/srv/www/bin ./scripts/publish.sh
21# URL_BASE=https://staging.example/ ./scripts/publish.sh
14set -euo pipefail 22set -euo pipefail
15 23
16ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) 24ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
@@ -23,6 +31,21 @@ if [ -z "${VERSION:-}" ]; then
23fi 31fi
24 32
25DEST=${DEST:-web/bin} 33DEST=${DEST:-web/bin}
34URL_BASE=${URL_BASE:-https://zagan.2137697.xyz}
35
36if [ "${ALLOW_DIRTY:-0}" != "1" ] && [ -n "$(git status --porcelain)" ]; then
37 echo "the working tree is dirty; commit the release first (or ALLOW_DIRTY=1)" >&2
38 exit 1
39fi
40
41command -v python3 >/dev/null || {
42 echo "python3 is required to render the installers" >&2
43 exit 1
44}
45
46hash_of() {
47 sha256sum "$1" | awk '{print $1}'
48}
26 49
27publish() { 50publish() {
28 local triple=$1 kind=$2 platform=$3 arch=$4 51 local triple=$1 kind=$2 platform=$3 arch=$4
@@ -47,11 +70,46 @@ publish() {
47 echo " $DEST/$kind/$artifact ($(du -h "$DEST/$kind/$artifact" | cut -f1))" 70 echo " $DEST/$kind/$artifact ($(du -h "$DEST/$kind/$artifact" | cut -f1))"
48} 71}
49 72
73publish_gui() {
74 local triple=$1 platform=$2 arch=$3
75 local ext=""
76 if [ "$platform" = windows ]; then
77 ext=".exe"
78 fi
79
80 local artifact="zagan-bet-gui-$VERSION-$platform-$arch$ext"
81 local source="target/$triple/gui-release/zagan-bet-gui$ext"
82
83 echo "==> $artifact"
84 cargo build -p zagan-bet-gui --profile gui-release --quiet --target "$triple"
85
86 mkdir -p "$DEST/gui"
87 install -m 0755 "$source" "$DEST/gui/$artifact"
88 local sha
89 sha=$(hash_of "$DEST/gui/$artifact")
90 echo " $DEST/gui/$artifact ($(du -h "$DEST/gui/$artifact" | cut -f1))"
91
92 if [ "$platform" = linux ]; then
93 python3 scripts/render-installer.py \
94 scripts/installers/linux.sh.in \
95 "$DEST/gui/zagan-bet-gui-$VERSION-linux-$arch-install.sh" \
96 "$VERSION" "$artifact" "$sha" "$URL_BASE/bin/gui/$artifact"
97 elif [ "$platform" = windows ]; then
98 python3 scripts/render-installer.py \
99 scripts/installers/windows.ps1.in \
100 "$DEST/gui/zagan-bet-gui-$VERSION-windows-$arch-install.ps1" \
101 "$VERSION" "$artifact" "$sha" "$URL_BASE/bin/gui/$artifact"
102 fi
103}
104
105publish_gui x86_64-unknown-linux-gnu linux x86_64
106publish_gui x86_64-pc-windows-gnu windows x86_64
107
50publish x86_64-unknown-linux-gnu client linux x86_64 108publish x86_64-unknown-linux-gnu client linux x86_64
51publish i686-unknown-linux-gnu client linux i686 109publish i686-unknown-linux-gnu client linux i686
52publish x86_64-pc-windows-gnu client windows x86_64 110publish x86_64-pc-windows-gnu client windows x86_64
53publish x86_64-unknown-linux-gnu server linux x86_64 111publish x86_64-unknown-linux-gnu server linux x86_64
54publish x86_64-pc-windows-gnu server windows x86_64 112publish x86_64-pc-windows-gnu server windows x86_64
55 113
56echo 114echo
57echo "published $VERSION to $DEST" 115echo "published $VERSION to $DEST"
diff --git a/scripts/render-installer.py b/scripts/render-installer.py
new file mode 100644
index 0000000..1a00551
--- /dev/null
+++ b/scripts/render-installer.py
@@ -0,0 +1,49 @@
1#!/usr/bin/env python3
2"""Fill an installer template with one published artifact's details.
3
4Usage: render-installer.py <template> <output> <version> <binary> <sha256> <url>
5
6The Linux template embeds the application icon so the installer works offline.
7"""
8
9from __future__ import annotations
10
11import pathlib
12import sys
13
14
15def main(argv: list[str]) -> int:
16 if len(argv) != 7:
17 print(
18 "usage: render-installer.py <template> <output> <version> "
19 "<binary> <sha256> <url>",
20 file=sys.stderr,
21 )
22 return 2
23
24 template, output, version, binary, sha256, url = argv[1:]
25 root = pathlib.Path(__file__).resolve().parent.parent
26 icon = (root / "gui" / "assets" / "zagan-bet.svg").read_text()
27
28 text = pathlib.Path(template).read_text()
29 replacements = {
30 "__VERSION__": version,
31 "__BINARY__": binary,
32 "__SHA256__": sha256,
33 "__URL__": url,
34 "__ICON__": icon.rstrip("\n"),
35 }
36 for placeholder, value in replacements.items():
37 text = text.replace(placeholder, value)
38
39 destination = pathlib.Path(output)
40 destination.write_text(text)
41 if destination.suffix == ".sh":
42 destination.chmod(0o755)
43
44 print(f" {destination} ({destination.stat().st_size} bytes)")
45 return 0
46
47
48if __name__ == "__main__":
49 raise SystemExit(main(sys.argv))
diff --git a/web/README.md b/web/README.md
index e79abd4..bd87e11 100644
--- a/web/README.md
+++ b/web/README.md
@@ -1,15 +1,15 @@
1# Download page 1# Download page
2 2
3`index.php` scans `bin/client` and `bin/server` on every request and renders 3`index.php` scans `bin/gui`, `bin/client` and `bin/server` on every request and
4whatever builds it finds, with version, size and SHA-256. No database, no 4renders whatever builds it finds, with version, size and SHA-256. No database, no JavaScript, no CSS, no dependencies. The page is plain
5JavaScript, no CSS, no dependencies. The page is plain browser defaults: 5browser defaults: black text, blue links, monospace checksums. PHP 8.0+.
6black text, blue links, monospace checksums. PHP 8.0+.
7 6
8## Layout 7## Layout
9 8
10``` 9```
11web/ 10web/
12 index.php 11 index.php
12 bin/gui/ served at /bin/gui/... (app and installers)
13 bin/client/ served at /bin/client/... 13 bin/client/ served at /bin/client/...
14 bin/server/ served at /bin/server/... 14 bin/server/ served at /bin/server/...
15``` 15```
@@ -22,13 +22,22 @@ The page only lists files that match the release pattern; anything else in
22those directories is ignored. 22those directories is ignored.
23 23
24``` 24```
25zagan-bet-client-<version>-<platform>-<arch>[.exe] 25bin/gui/zagan-bet-gui-<version>-<platform>-<arch>[.exe]
26zagan-bet-server-<version>-<platform>-<arch> 26bin/gui/zagan-bet-gui-<version>-<platform>-<arch>-install.sh
27bin/gui/zagan-bet-gui-<version>-<platform>-<arch>-install.ps1
28bin/client/zagan-bet-client-<version>-<platform>-<arch>[.exe]
29bin/server/zagan-bet-server-<version>-<platform>-<arch>[.exe]
27``` 30```
28 31
29`<version>` is `MAJOR.MINOR.PATCH`, `<platform>` is `linux`, `windows` or 32`<version>` is `MAJOR.MINOR.PATCH`, `<platform>` is `linux`, `windows` or
30`macos`, `<arch>` is `x86_64`, `i686` or `aarch64`. 33`macos`, `<arch>` is `x86_64`, `i686` or `aarch64`.
31 34
35The installers are generated next to their app. They embed the app's SHA-256,
36check the file (or download it) and install it for the current user: Linux
37writes `~/.local/bin/zagan-bet-gui` plus a `.desktop` launcher, Windows writes
38`%LOCALAPPDATA%\Programs\Zagan-Bet` plus a Start-menu shortcut. The source
39archives satisfy the GPL's corresponding-source requirement.
40
32## Publishing a build 41## Publishing a build
33 42
34From the repository root: 43From the repository root:
@@ -37,15 +46,21 @@ From the repository root:
37./scripts/publish.sh 46./scripts/publish.sh
38``` 47```
39 48
40It reads the version from `Cargo.toml`, builds all four artifacts and installs 49It reads the version from `Cargo.toml`, builds every artifact and installs
41them under `web/bin` with the names above. Override the destination with 50them under `web/bin` with the names above. The GUI is built with the
42`DEST=/srv/www/bin ./scripts/publish.sh`. 51workspace's `gui-release` profile (LTO, stripped) and the installers are
52rendered with Python 3 and the published SHA-256. Run it from a clean tree so
53the release commit is the source the page links to. Override the destination
54with `DEST=/srv/www/bin ./scripts/publish.sh`, and the download origin baked
55into the installers with `URL_BASE=https://staging.example/ ./scripts/publish.sh`.
56`ALLOW_DIRTY=1` skips the clean-tree check for a preview.
43 57
44Builds are per target triple, so these must be installed: 58Builds are per target triple, so these must be installed:
45`x86_64-unknown-linux-gnu`, `i686-unknown-linux-gnu` and 59`x86_64-unknown-linux-gnu`, `i686-unknown-linux-gnu` and
46`x86_64-pc-windows-gnu`, plus the mingw linker for the Windows build. Client 60`x86_64-pc-windows-gnu`, plus the mingw linker for the Windows build. Client
47builds always pass `--no-default-features`, which drops the server's OpenSSL 61builds always pass `--no-default-features`, which drops the server's OpenSSL
48dependency; the client is rustls-only. 62dependency; the client is rustls-only. The GUI needs Python 3 at publish time
63for the installer templates in `scripts/installers/`.
49 64
50Cargo itself cannot produce these names: it always writes 65Cargo itself cannot produce these names: it always writes
51`target/<triple>/release/<bin>`. The script renames after each build. 66`target/<triple>/release/<bin>`. The script renames after each build.
@@ -100,8 +115,9 @@ Then open <http://127.0.0.1:8899/>.
100``` 115```
101 116
102`deploy.sh` rsyncs `web/` with `--delay-updates`, so a visitor never sees a 117`deploy.sh` rsyncs `web/` with `--delay-updates`, so a visitor never sees a
103half-written binary, then fetches the live page and every artifact it lists and 118half-written binary, then fetches the live page and every artifact it lists —
104compares the downloaded bytes with the local SHA-256. `--verify-only` skips the 119GUI apps, installers, terminal clients and servers — and compares the
120downloaded bytes with the local SHA-256. `--verify-only` skips the
105upload and only checks the live site. Old versions are left on the server; 121upload and only checks the live site. Old versions are left on the server;
106prune them by hand. 122prune them by hand.
107 123
@@ -124,3 +140,6 @@ php -S 127.0.0.1:8899 -t /srv/staging
124 stay downloadable until you remove them from `bin/`. 140 stay downloadable until you remove them from `bin/`.
125- The checksum block is in `sha256sum` format, so visitors can save it as 141- The checksum block is in `sha256sum` format, so visitors can save it as
126 `SHA256SUMS` and run `sha256sum -c SHA256SUMS`. 142 `SHA256SUMS` and run `sha256sum -c SHA256SUMS`.
143- The page links <https://git.2137697.xyz/zagan-bet.git> for the GPL
144 corresponding source; publish only from a committed release so that link
145 contains the exact source.
diff --git a/web/index.php b/web/index.php
index 7ed79da..77edf95 100644
--- a/web/index.php
+++ b/web/index.php
@@ -4,20 +4,23 @@ declare(strict_types=1);
4/** 4/**
5 * zagan.bet download page. 5 * zagan.bet download page.
6 * 6 *
7 * Scans ./bin/client and ./bin/server and renders every build it finds, with 7 * Scans ./bin/{gui,client,server} and renders every build it finds, with its
8 * its version, size and SHA-256. No database, no JavaScript, no dependencies. 8 * version, size and SHA-256. The GPL source link points at the public git
9 * repository. No database, no JavaScript, no dependencies.
9 * 10 *
10 * Expected file names (anything else is ignored): 11 * Expected file names (anything else is ignored):
11 * 12 *
12 * bin/client/zagan-bet-client-<version>-<platform>-<arch> 13 * bin/gui/zagan-bet-gui-<version>-<platform>-<arch>[.exe]
13 * bin/client/zagan-bet-client-<version>-<platform>-<arch>.exe 14 * bin/gui/zagan-bet-gui-<version>-<platform>-<arch>-install.sh
14 * bin/server/zagan-bet-server-<version>-<platform>-<arch> 15 * bin/gui/zagan-bet-gui-<version>-<platform>-<arch>-install.ps1
16 * bin/client/zagan-bet-client-<version>-<platform>-<arch>[.exe]
17 * bin/server/zagan-bet-server-<version>-<platform>-<arch>[.exe]
15 * 18 *
16 * <version> is MAJOR.MINOR.PATCH, <platform> is linux|windows|macos and 19 * <version> is MAJOR.MINOR.PATCH, <platform> is linux|windows|macos and
17 * <arch> is x86_64|i686|aarch64. Example: 20 * <arch> is x86_64|i686|aarch64. Example:
18 * 21 *
19 * bin/client/zagan-bet-client-3.1.1-linux-x86_64 22 * bin/gui/zagan-bet-gui-4.3.0-linux-x86_64
20 * bin/server/zagan-bet-server-3.1.1-linux-x86_64 23 * bin/client/zagan-bet-client-4.3.0-linux-x86_64
21 * 24 *
22 * SHA-256 values are cached in a JSON file keyed on size and mtime, so a file 25 * SHA-256 values are cached in a JSON file keyed on size and mtime, so a file
23 * is only re-read when it changes. The cache lives in the system temp 26 * is only re-read when it changes. The cache lives in the system temp
@@ -26,6 +29,10 @@ declare(strict_types=1);
26 */ 29 */
27 30
28const KINDS = [ 31const KINDS = [
32 'gui' => [
33 'title' => 'Desktop app',
34 'hint' => 'Windows 10 or newer, or a Linux desktop. No terminal needed.',
35 ],
29 'client' => [ 36 'client' => [
30 'title' => 'Client', 37 'title' => 'Client',
31 'hint' => 'Needs a terminal of at least 80×24 cells.', 38 'hint' => 'Needs a terminal of at least 80×24 cells.',
@@ -163,7 +170,7 @@ function read_release(
163 bool &$dirty 170 bool &$dirty
164): ?array { 171): ?array {
165 $pattern = '/^zagan-bet-' . preg_quote($kind, '/') 172 $pattern = '/^zagan-bet-' . preg_quote($kind, '/')
166 . '-v?(\d+\.\d+\.\d+)-([a-z]+)-([a-z0-9_]+)(\.exe)?$/i'; 173 . '-v?(\d+\.\d+\.\d+)-([a-z]+)-([a-z0-9_]+)(-install\.sh|-install\.ps1|\.exe)?$/i';
167 if (preg_match($pattern, $entry, $matches) !== 1) { 174 if (preg_match($pattern, $entry, $matches) !== 1) {
168 return null; 175 return null;
169 } 176 }
@@ -192,10 +199,14 @@ function read_release(
192 return null; 199 return null;
193 } 200 }
194 201
202 $suffix = strtolower($matches[4] ?? '');
203 $variant = str_starts_with($suffix, '-install.') ? 'installer' : 'binary';
204
195 return [ 205 return [
196 'version' => $matches[1], 206 'version' => $matches[1],
197 'platform' => strtolower($matches[2]), 207 'platform' => strtolower($matches[2]),
198 'arch' => strtolower($matches[3]), 208 'arch' => strtolower($matches[3]),
209 'variant' => $variant,
199 'file' => $entry, 210 'file' => $entry,
200 'size' => $size, 211 'size' => $size,
201 'mtime' => $mtime, 212 'mtime' => $mtime,
@@ -257,6 +268,10 @@ foreach (KINDS as $kind => $meta) {
257 if ($byArch !== 0) { 268 if ($byArch !== 0) {
258 return $byArch; 269 return $byArch;
259 } 270 }
271 $byVariant = ($a['variant'] === 'installer' ? 1 : 0) <=> ($b['variant'] === 'installer' ? 1 : 0);
272 if ($byVariant !== 0) {
273 return $byVariant;
274 }
260 return strcmp($a['file'], $b['file']); 275 return strcmp($a['file'], $b['file']);
261 }); 276 });
262 277
@@ -280,18 +295,41 @@ if ($cacheDirty) {
280<meta charset="utf-8"> 295<meta charset="utf-8">
281<meta name="viewport" content="width=device-width, initial-scale=1"> 296<meta name="viewport" content="width=device-width, initial-scale=1">
282<title>żagań.bet</title> 297<title>żagań.bet</title>
283<meta name="description" content="Official zagan.bet client and server downloads."> 298<meta name="description" content="Official zagan.bet desktop app, terminal client and server downloads.">
284<meta http-equiv="Content-Security-Policy" content="default-src 'none'"> 299<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
285</head> 300</head>
286<body> 301<body>
287 302
288<h1>żagań.bet</h1> 303<h1>żagań.bet</h1>
289<p>Official client and server builds.</p> 304<p>Official desktop app, terminal client and server builds.</p>
290<?php if ($newest > 0): ?> 305<?php if ($newest > 0): ?>
291<p>Builds published <?= e(gmdate('j M Y H:i', $newest)) ?> UTC.</p> 306<p>Builds published <?= e(gmdate('j M Y H:i', $newest)) ?> UTC.</p>
292<?php endif; ?> 307<?php endif; ?>
293 308
294<h2>Client</h2> 309<h2>Desktop app</h2>
310<p>Windows 10 or newer, or a Linux desktop. No terminal needed.</p>
311<?php $guiBinaries = array_values(array_filter($releases['gui'], static fn (array $r): bool => $r['variant'] === 'binary')); ?>
312<?php $guiInstallers = array_values(array_filter($releases['gui'], static fn (array $r): bool => $r['variant'] === 'installer')); ?>
313<?php if ($guiBinaries === []): ?>
314<p>Nothing published yet.</p>
315<?php else: ?>
316<ul>
317<?php foreach ($guiBinaries as $release): ?>
318<li><a href="<?= e($release['href']) ?>" download><?= e(PLATFORMS[$release['platform']] ?? ucfirst($release['platform'])) ?> <?= e($release['arch']) ?><?php if (isset(ARCHES[$release['arch']])): ?> (<?= e(ARCHES[$release['arch']]) ?>)<?php endif; ?> v<?= e($release['version']) ?> — <?= e(human_size($release['size'])) ?></a></li>
319<?php endforeach; ?>
320</ul>
321<?php endif; ?>
322<?php if ($guiInstallers !== []): ?>
323<h3>Installers</h3>
324<p>The installer checks the download, installs Zagan-Bet for your user and adds it to your programs. Run it next to a downloaded app, or on its own to fetch one.</p>
325<ul>
326<?php foreach ($guiInstallers as $release): ?>
327<li><a href="<?= e($release['href']) ?>" download><?= e(PLATFORMS[$release['platform']] ?? ucfirst($release['platform'])) ?> <?= e($release['arch']) ?> installer (<?= str_ends_with($release['file'], '.ps1') ? 'PowerShell' : 'bash' ?>) v<?= e($release['version']) ?> — <?= e(human_size($release['size'])) ?></a></li>
328<?php endforeach; ?>
329</ul>
330<?php endif; ?>
331
332<h2>Terminal client</h2>
295<p>Needs a terminal of at least 80×24 cells.</p> 333<p>Needs a terminal of at least 80×24 cells.</p>
296<?php if ($releases['client'] === []): ?> 334<?php if ($releases['client'] === []): ?>
297<p>Nothing published yet.</p> 335<p>Nothing published yet.</p>
@@ -315,7 +353,7 @@ if ($cacheDirty) {
315</ul> 353</ul>
316<?php endif; ?> 354<?php endif; ?>
317 355
318<?php $all = array_merge($releases['client'], $releases['server']); ?> 356<?php $all = array_merge($releases['gui'], $releases['client'], $releases['server']); ?>
319<?php if ($all !== []): ?> 357<?php if ($all !== []): ?>
320<h2>SHA-256</h2> 358<h2>SHA-256</h2>
321<p>On Linux use <code>sha256sum &lt;file&gt;</code>. On Windows use <code>Get-FileHash &lt;file&gt;</code>.</p> 359<p>On Linux use <code>sha256sum &lt;file&gt;</code>. On Windows use <code>Get-FileHash &lt;file&gt;</code>.</p>
@@ -329,8 +367,17 @@ echo e($sums);
329?></pre> 367?></pre>
330<?php endif; ?> 368<?php endif; ?>
331 369
370<h2>Source</h2>
371<p>Zagan-Bet is free software under the GNU GPL v3 or later. The source for these builds is at <a href="https://git.2137697.xyz/zagan-bet.git">https://git.2137697.xyz/zagan-bet.git</a>.</p>
372
332<h2>Running it</h2> 373<h2>Running it</h2>
333<p>Client:</p> 374<p>Desktop app on Linux:</p>
375<pre><code>chmod +x zagan-bet-gui-*-linux-x86_64-install.sh
376./zagan-bet-gui-*-linux-x86_64-install.sh</code></pre>
377<p>Desktop app on Windows (PowerShell):</p>
378<pre><code>powershell -ExecutionPolicy Bypass -File .\zagan-bet-gui-*-windows-x86_64-install.ps1</code></pre>
379<p>The installers verify the SHA-256 before installing. You can also run the downloaded app directly.</p>
380<p>Terminal client:</p>
334<pre><code>chmod +x &lt;downloaded-client&gt; 381<pre><code>chmod +x &lt;downloaded-client&gt;
335./&lt;downloaded-client&gt;</code></pre> 382./&lt;downloaded-client&gt;</code></pre>
336<p>Server, first run creates <code>identity.p12</code>, <code>key.pem</code> and <code>cert.pem</code>:</p> 383<p>Server, first run creates <code>identity.p12</code>, <code>key.pem</code> and <code>cert.pem</code>:</p>
@@ -338,8 +385,7 @@ echo e($sums);
338./&lt;downloaded-server&gt; --init 385./&lt;downloaded-server&gt; --init
339./&lt;downloaded-server&gt; -i 0.0.0.0 -p 11525</code></pre> 386./&lt;downloaded-server&gt; -i 0.0.0.0 -p 11525</code></pre>
340 387
341<p>Windows: run the <code>.exe</code> from Windows Terminal or another real terminal.</p> 388<p>Terminal client on Windows: run the <code>.exe</code> from Windows Terminal or another real terminal.</p>
342 389<p>Run <code>help</code> inside the terminal client; the server binary supports <code>--help</code>.</p>
343<p>When lost, execute <code>help</code> command inside zagan.bet client. Server binary supports <code>--help</code> flag</p>
344</body> 390</body>
345</html> 391</html>