summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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>