From 4887abc5773b33ab1e213ed5f528dfebd242e2cd Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 22:23:00 +0100 Subject: v4.4.0: stable installers that follow the newest published build Adds web/latest.php: a plain-text feed of the newest version with each artifact's filename and SHA-256. The per-release generated installers are replaced by stable web/install.sh and web/install.ps1; both read the feed, pick the newest build, verify it, install per-user and add a launcher. Publishing a new app version no longer regenerates or versions the installers. publish.sh drops the installer renderer and Python dependency. deploy.sh now also verifies latest.php against the local artifacts and that the served installer scripts match the repository. --- AGENTS.md | 10 +- Cargo.toml | 2 +- docs/GUI-PLAN.md | 12 +- docs/TESTING.md | 16 +- gui/Cargo.toml | 2 +- scripts/deploy.sh | 24 +++ scripts/installers/linux.sh.in | 78 ---------- scripts/installers/windows.ps1.in | 87 ----------- scripts/publish.sh | 37 +---- scripts/render-installer.py | 49 ------ web/README.md | 38 ++--- web/index.php | 308 ++++---------------------------------- web/install.ps1 | 108 +++++++++++++ web/install.sh | 98 ++++++++++++ web/latest.php | 50 +++++++ web/releases.php | 262 ++++++++++++++++++++++++++++++++ 16 files changed, 623 insertions(+), 558 deletions(-) delete mode 100644 scripts/installers/linux.sh.in delete mode 100644 scripts/installers/windows.ps1.in delete mode 100644 scripts/render-installer.py create mode 100644 web/install.ps1 create mode 100644 web/install.sh create mode 100644 web/latest.php create mode 100644 web/releases.php diff --git a/AGENTS.md b/AGENTS.md index cd5c71b..ab794f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,10 +85,12 @@ - The embedded fonts have no suit or arrow glyphs. Draw card suits as shapes and use plain words for navigation; a glyph that is not in Open Sans or Hack renders as tofu. -- `scripts/publish.sh` builds the GUI with `gui-release` and renders the - installers with the published SHA-256. Installers verify the hash and install - per-user; never install system-wide or skip the check. The Windows installer - waits for Enter before closing; `-NoPause` is only for automation. +- `scripts/publish.sh` builds the GUI with `gui-release`; the installers are + stable site files (`web/install.sh`, `web/install.ps1`) that read + `web/latest.php` for the newest build and its SHA-256. Never regenerate or + version the installers per release, install system-wide, or skip the hash + check. The Windows installer waits for Enter before closing; `-NoPause` is + only for automation. - Windows renders through wgpu on Direct3D 12, including the WARP software adapter, so a machine with only OpenGL 1.1 (VM, Remote Desktop) still runs. Linux keeps the smaller glow renderer. Do not assume OpenGL 2.0 on Windows. diff --git a/Cargo.toml b/Cargo.toml index 8897f9d..16ed8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["gui"] [package] name = "zagan-bet" -version = "4.3.1" +version = "4.4.0" edition = "2024" license = "GPL-3.0-or-later" diff --git a/docs/GUI-PLAN.md b/docs/GUI-PLAN.md index a322057..c0370a0 100644 --- a/docs/GUI-PLAN.md +++ b/docs/GUI-PLAN.md @@ -169,13 +169,13 @@ frame rate on integrated graphics and the Windows interactive pass. - `publish.sh` builds the GUI with the `gui-release` profile for Linux and Windows and names the app `zagan-bet-gui---[.exe]`. -- It renders a bash installer and a PowerShell installer next to each app. - Both embed the app's SHA-256, verify the file (or download it), install - per-user and add a launcher: `.desktop` plus an SVG icon on Linux, a - Start-menu shortcut on Windows. Templates live in `scripts/installers/`; - `render-installer.py` fills them in. +- The installers are stable, unversioned site files: `install.sh` and + `install.ps1` read `latest.php`, pick the newest build for the machine, + verify its SHA-256, install per-user and add a launcher (`.desktop` plus an + SVG icon on Linux, a Start-menu shortcut on Windows). Publishing a new + version does not regenerate them. - The download page scans `bin/gui`, `bin/client` and `bin/server`, lists - apps, installers, clients and servers, and includes every file in the + apps, stable installers, clients and servers, and includes every file in the `sha256sum` block. A Source section links the public git repository () for the GPL corresponding source. `deploy.sh` verifies every artifact kind over the public URL. diff --git a/docs/TESTING.md b/docs/TESTING.md index c32ac22..09ceb82 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -272,14 +272,16 @@ terminal client; a saved login from one signs in the other. 1. Set the version in `Cargo.toml` and `gui/Cargo.toml` (same MAJOR.MINOR), run `cargo test --workspace`, and commit. `publish.sh` refuses a dirty tree because the site links the committed git history as the GPL source. -2. `./scripts/publish.sh` (add `DEST=` and `URL_BASE=` to stage elsewhere). It - builds the GUI with `gui-release`, the terminal clients and servers, and - renders the bash/PowerShell installers with each app's SHA-256. +2. `./scripts/publish.sh` (add `DEST=` to stage elsewhere). It builds the GUI + with `gui-release` and the terminal clients and servers. The installers are + stable site files and are not regenerated. 3. `php -S 127.0.0.1:8899 -t ` and `./scripts/deploy.sh http://127.0.0.1:8899/` to upload and verify - every app, installer, client and server against the page's checksums. -4. On a clean user account: download an app with its installer, run the - installer, and launch the new menu entry. The installer must refuse a - corrupted download. + every app, client and server against the page's checksums, the stable + installers, and the newest-version feed at `latest.php`. +4. On a clean user account: run the stable installer + (`ZAGAN_SITE=http://127.0.0.1:8899 ./web/install.sh`) and launch the new + menu entry. It must install the newest version even when an older app sits + next to it, and refuse a corrupted download. 5. Repeat on Windows for the `.ps1` installer and the Start-menu shortcut. diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 24bf560..e216309 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zagan-bet-gui" -version = "4.3.1" +version = "4.4.0" edition = "2024" license = "GPL-3.0-or-later" description = "Desktop frontend for zagan-bet" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index b18d462..63bd31c 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -95,6 +95,20 @@ origin=$(printf '%s' "$URL" | sed -E 's#^(https?://[^/]+).*#\1#') grep -oE '[0-9a-f]{64} [A-Za-z0-9._-]+' "$tmp/page.html" > "$tmp/sums" || true [ -s "$tmp/sums" ] || die "no SHA-256 lines on the page (is index.php being served?)" +for script in install.sh install.ps1; do + grep -qE "href=\"[^\"]*/$script\"" "$tmp/page.html" \ + || die "$script is not linked from the page" + curl -fsSL --max-time 30 "$origin/$script" -o "$tmp/$script" \ + || die "could not fetch $script" + cmp -s "web/$script" "$tmp/$script" \ + || die "$script served from the site differs from the local file" +done + +curl -fsSL --max-time 30 "$origin/latest.php" -o "$tmp/latest.txt" \ + || die "could not fetch latest.php" +newest=$(awk '$1 == "version" { print $2; exit }' "$tmp/latest.txt") +[ -n "$newest" ] || die "latest.php has no version line" + failed=0 verified=0 for local in "${artifacts[@]}"; do @@ -132,6 +146,16 @@ for local in "${artifacts[@]}"; do continue fi + version=$(printf '%s' "$file" | sed -E 's/^zagan-bet-[a-z]+-([0-9]+\.[0-9]+\.[0-9]+)-.*/\1/') + if [ "$version" = "$newest" ]; then + latest_hash=$(awk -v kind="$kind" -v name="$file" '$1 == kind && $5 == name { print $6 }' "$tmp/latest.txt") + if [ "$latest_hash" != "$local_hash" ]; then + echo " $file: latest.php shows ${latest_hash:-nothing}, local is $local_hash" + failed=1 + continue + fi + fi + echo " $file: ok" verified=$((verified + 1)) done diff --git a/scripts/installers/linux.sh.in b/scripts/installers/linux.sh.in deleted file mode 100644 index 5b53c02..0000000 --- a/scripts/installers/linux.sh.in +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -# Zagan-Bet desktop installer for Linux. -# -# Verifies the app against its published SHA-256, installs it for the current -# user and adds a launcher. Put the downloaded app next to this script, or run -# the script on its own and it will fetch the matching binary. -set -euo pipefail - -VERSION="__VERSION__" -BINARY="__BINARY__" -SHA256="__SHA256__" -URL="__URL__" - -say() { printf '%s\n' "$*"; } -die() { printf 'install: %s\n' "$*" >&2; exit 1; } - -command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required" - -here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -source_file="" -for candidate in "$here/$BINARY" "./$BINARY" "$HOME/Downloads/$BINARY"; do - if [ -f "$candidate" ]; then - source_file=$candidate - break - fi -done - -cleanup="" -if [ -z "$source_file" ]; then - command -v curl >/dev/null 2>&1 || die "put $BINARY next to this installer, or install curl" - cleanup=$(mktemp) || die "cannot create a temporary file" - say "Downloading $BINARY..." - curl -fsSL --max-time 300 "$URL" -o "$cleanup" || die "download failed" - source_file=$cleanup -fi -trap 'if [ -n "$cleanup" ]; then rm -f "$cleanup"; fi' EXIT - -actual=$(sha256sum "$source_file" | awk '{print $1}') -[ "$actual" = "$SHA256" ] || die "checksum mismatch: expected $SHA256, got $actual" - -bin_dir="${XDG_BIN_HOME:-$HOME/.local/bin}" -data_dir="${XDG_DATA_HOME:-$HOME/.local/share}" -app="$bin_dir/zagan-bet-gui" -desktop_dir="$data_dir/applications" -icon_dir="$data_dir/icons/hicolor/scalable/apps" - -mkdir -p "$bin_dir" "$desktop_dir" "$icon_dir" -install -m 0755 "$source_file" "$app" - -cat > "$icon_dir/zagan-bet.svg" <<'ICON' -__ICON__ -ICON - -cat > "$desktop_dir/zagan-bet.desktop" </dev/null 2>&1; then - update-desktop-database "$desktop_dir" >/dev/null 2>&1 || true -fi -if command -v gtk-update-icon-cache >/dev/null 2>&1; then - gtk-update-icon-cache -q "$data_dir/icons/hicolor" >/dev/null 2>&1 || true -fi - -say "Installed Zagan-Bet $VERSION to $app" -say "A launcher was added to your applications menu." -case ":$PATH:" in - *":$bin_dir:"*) ;; - *) say "Add $bin_dir to PATH to start it as 'zagan-bet-gui'." ;; -esac diff --git a/scripts/installers/windows.ps1.in b/scripts/installers/windows.ps1.in deleted file mode 100644 index 4b8e61d..0000000 --- a/scripts/installers/windows.ps1.in +++ /dev/null @@ -1,87 +0,0 @@ -# Zagan-Bet desktop installer for Windows. -# -# Verifies the app against its published SHA-256, installs it for the current -# user and adds a Start-menu shortcut. Put the downloaded app next to this -# script, or run the script on its own and it will fetch the matching binary. -# -# Run it with: -# powershell -ExecutionPolicy Bypass -File .\zagan-bet-gui-*-install.ps1 -# Add -NoPause to run it unattended. -param([switch]$NoPause) - -$ErrorActionPreference = "Stop" - -$Version = "__VERSION__" -$Binary = "__BINARY__" -$Sha256 = "__SHA256__" -$Url = "__URL__" - -function Finish([int]$Code) { - if (-not $NoPause) { - Read-Host "Press Enter to close" | Out-Null - } - exit $Code -} - -function Stop-Install([string]$Message) { - Write-Host "install: $Message" -ForegroundColor Red - Finish 1 -} - -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -$candidates = @( - (Join-Path $here $Binary), - (Join-Path (Get-Location) $Binary), - (Join-Path $env:USERPROFILE "Downloads\$Binary") -) -$sourceFile = $null -foreach ($candidate in $candidates) { - if (Test-Path -LiteralPath $candidate) { - $sourceFile = $candidate - break - } -} - -$temporary = $null -if (-not $sourceFile) { - $temporary = Join-Path ([System.IO.Path]::GetTempPath()) $Binary - Write-Host "Downloading $Binary..." - try { - Invoke-WebRequest -Uri $Url -OutFile $temporary -UseBasicParsing - } catch { - Stop-Install "download failed: $_" - } - $sourceFile = $temporary -} - -try { - $actual = (Get-FileHash -LiteralPath $sourceFile -Algorithm SHA256).Hash.ToLowerInvariant() - if ($actual -ne $Sha256.ToLowerInvariant()) { - Stop-Install "checksum mismatch: expected $Sha256, got $actual" - } - - $installDir = Join-Path $env:LOCALAPPDATA "Programs\Zagan-Bet" - New-Item -ItemType Directory -Force -Path $installDir | Out-Null - $target = Join-Path $installDir "zagan-bet-gui.exe" - Copy-Item -LiteralPath $sourceFile -Destination $target -Force - - $startMenu = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs" - New-Item -ItemType Directory -Force -Path $startMenu | Out-Null - $shortcutPath = Join-Path $startMenu "Zagan-Bet.lnk" - $shell = New-Object -ComObject WScript.Shell - $shortcut = $shell.CreateShortcut($shortcutPath) - $shortcut.TargetPath = $target - $shortcut.WorkingDirectory = $installDir - $shortcut.IconLocation = "$target,0" - $shortcut.Description = "Slots, roulette, blackjack and crash" - $shortcut.Save() - - Write-Host "Installed Zagan-Bet $Version to $target" - Write-Host "A Start-menu shortcut was created." -} finally { - if ($temporary -and (Test-Path -LiteralPath $temporary)) { - Remove-Item -LiteralPath $temporary -Force - } -} - -Finish 0 diff --git a/scripts/publish.sh b/scripts/publish.sh index 3a04631..fbcd588 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -5,20 +5,19 @@ # zagan-bet-client---[.exe] # zagan-bet-server---[.exe] # zagan-bet-gui---[.exe] -# zagan-bet-gui----install.sh -# zagan-bet-gui----install.ps1 # # Cargo always writes target///, so the rename happens # here after each build. The version comes from Cargo.toml. # # The GUI uses the workspace's `gui-release` profile (small, stripped, LTO). -# The public git repository is the GPL source for these binaries, so publish -# from a clean tree (or set ALLOW_DIRTY=1 for a preview). +# The installers are stable site files (web/install.sh and web/install.ps1): +# they ask web/latest.php for the newest build, so they are not regenerated +# here. The public git repository is the GPL source for these binaries, so +# publish from a clean tree (or set ALLOW_DIRTY=1 for a preview). # # Usage: # ./scripts/publish.sh # DEST=/srv/www/bin ./scripts/publish.sh -# URL_BASE=https://staging.example/ ./scripts/publish.sh set -euo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) @@ -31,22 +30,12 @@ if [ -z "${VERSION:-}" ]; then fi DEST=${DEST:-web/bin} -URL_BASE=${URL_BASE:-https://zagan.2137697.xyz} if [ "${ALLOW_DIRTY:-0}" != "1" ] && [ -n "$(git status --porcelain)" ]; then echo "the working tree is dirty; commit the release first (or ALLOW_DIRTY=1)" >&2 exit 1 fi -command -v python3 >/dev/null || { - echo "python3 is required to render the installers" >&2 - exit 1 -} - -hash_of() { - sha256sum "$1" | awk '{print $1}' -} - publish() { local triple=$1 kind=$2 platform=$3 arch=$4 local ext="" @@ -85,23 +74,13 @@ publish_gui() { mkdir -p "$DEST/gui" install -m 0755 "$source" "$DEST/gui/$artifact" - local sha - sha=$(hash_of "$DEST/gui/$artifact") echo " $DEST/gui/$artifact ($(du -h "$DEST/gui/$artifact" | cut -f1))" - - if [ "$platform" = linux ]; then - python3 scripts/render-installer.py \ - scripts/installers/linux.sh.in \ - "$DEST/gui/zagan-bet-gui-$VERSION-linux-$arch-install.sh" \ - "$VERSION" "$artifact" "$sha" "$URL_BASE/bin/gui/$artifact" - elif [ "$platform" = windows ]; then - python3 scripts/render-installer.py \ - scripts/installers/windows.ps1.in \ - "$DEST/gui/zagan-bet-gui-$VERSION-windows-$arch-install.ps1" \ - "$VERSION" "$artifact" "$sha" "$URL_BASE/bin/gui/$artifact" - fi } +rm web/bin/client/* +rm web/bin/gui/* +rm web/bin/server/* + publish_gui x86_64-unknown-linux-gnu linux x86_64 publish_gui x86_64-pc-windows-gnu windows x86_64 diff --git a/scripts/render-installer.py b/scripts/render-installer.py deleted file mode 100644 index 1a00551..0000000 --- a/scripts/render-installer.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -"""Fill an installer template with one published artifact's details. - -Usage: render-installer.py