summaryrefslogtreecommitdiffstats
path: root/scripts
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 /scripts
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.
Diffstat (limited to 'scripts')
-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
5 files changed, 271 insertions, 6 deletions
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))