summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 22:23:00 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 22:23:00 +0100
commit4887abc5773b33ab1e213ed5f528dfebd242e2cd (patch)
tree8eb2316783a21817cce422c3fb58ec953206dd9a /scripts
parentv4.3.1: render Windows through wgpu and keep the installer window open (diff)
downloadzagan-bet-4887abc5773b33ab1e213ed5f528dfebd242e2cd.tar.gz
zagan-bet-4887abc5773b33ab1e213ed5f528dfebd242e2cd.zip
v4.4.0: stable installers that follow the newest published buildHEADmaster
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.
Diffstat (limited to '')
-rwxr-xr-xscripts/deploy.sh24
-rwxr-xr-xscripts/publish.sh37
-rw-r--r--scripts/render-installer.py49
-rw-r--r--web/install.ps1 (renamed from scripts/installers/windows.ps1.in)45
-rw-r--r--web/install.sh (renamed from scripts/installers/linux.sh.in)50
5 files changed, 100 insertions, 105 deletions
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#')
95grep -oE '[0-9a-f]{64} [A-Za-z0-9._-]+' "$tmp/page.html" > "$tmp/sums" || true 95grep -oE '[0-9a-f]{64} [A-Za-z0-9._-]+' "$tmp/page.html" > "$tmp/sums" || true
96[ -s "$tmp/sums" ] || die "no SHA-256 lines on the page (is index.php being served?)" 96[ -s "$tmp/sums" ] || die "no SHA-256 lines on the page (is index.php being served?)"
97 97
98for script in install.sh install.ps1; do
99 grep -qE "href=\"[^\"]*/$script\"" "$tmp/page.html" \
100 || die "$script is not linked from the page"
101 curl -fsSL --max-time 30 "$origin/$script" -o "$tmp/$script" \
102 || die "could not fetch $script"
103 cmp -s "web/$script" "$tmp/$script" \
104 || die "$script served from the site differs from the local file"
105done
106
107curl -fsSL --max-time 30 "$origin/latest.php" -o "$tmp/latest.txt" \
108 || die "could not fetch latest.php"
109newest=$(awk '$1 == "version" { print $2; exit }' "$tmp/latest.txt")
110[ -n "$newest" ] || die "latest.php has no version line"
111
98failed=0 112failed=0
99verified=0 113verified=0
100for local in "${artifacts[@]}"; do 114for local in "${artifacts[@]}"; do
@@ -132,6 +146,16 @@ for local in "${artifacts[@]}"; do
132 continue 146 continue
133 fi 147 fi
134 148
149 version=$(printf '%s' "$file" | sed -E 's/^zagan-bet-[a-z]+-([0-9]+\.[0-9]+\.[0-9]+)-.*/\1/')
150 if [ "$version" = "$newest" ]; then
151 latest_hash=$(awk -v kind="$kind" -v name="$file" '$1 == kind && $5 == name { print $6 }' "$tmp/latest.txt")
152 if [ "$latest_hash" != "$local_hash" ]; then
153 echo " $file: latest.php shows ${latest_hash:-nothing}, local is $local_hash"
154 failed=1
155 continue
156 fi
157 fi
158
135 echo " $file: ok" 159 echo " $file: ok"
136 verified=$((verified + 1)) 160 verified=$((verified + 1))
137done 161done
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 @@
5# zagan-bet-client-<version>-<platform>-<arch>[.exe] 5# zagan-bet-client-<version>-<platform>-<arch>[.exe]
6# zagan-bet-server-<version>-<platform>-<arch>[.exe] 6# zagan-bet-server-<version>-<platform>-<arch>[.exe]
7# zagan-bet-gui-<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
10# 8#
11# Cargo always writes target/<triple>/<profile>/<bin>, so the rename happens 9# Cargo always writes target/<triple>/<profile>/<bin>, so the rename happens
12# here after each build. The version comes from Cargo.toml. 10# here after each build. The version comes from Cargo.toml.
13# 11#
14# The GUI uses the workspace's `gui-release` profile (small, stripped, LTO). 12# 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 13# The installers are stable site files (web/install.sh and web/install.ps1):
16# from a clean tree (or set ALLOW_DIRTY=1 for a preview). 14# they ask web/latest.php for the newest build, so they are not regenerated
15# here. The public git repository is the GPL source for these binaries, so
16# publish from a clean tree (or set ALLOW_DIRTY=1 for a preview).
17# 17#
18# Usage: 18# Usage:
19# ./scripts/publish.sh 19# ./scripts/publish.sh
20# DEST=/srv/www/bin ./scripts/publish.sh 20# DEST=/srv/www/bin ./scripts/publish.sh
21# URL_BASE=https://staging.example/ ./scripts/publish.sh
22set -euo pipefail 21set -euo pipefail
23 22
24ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) 23ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
@@ -31,22 +30,12 @@ if [ -z "${VERSION:-}" ]; then
31fi 30fi
32 31
33DEST=${DEST:-web/bin} 32DEST=${DEST:-web/bin}
34URL_BASE=${URL_BASE:-https://zagan.2137697.xyz}
35 33
36if [ "${ALLOW_DIRTY:-0}" != "1" ] && [ -n "$(git status --porcelain)" ]; then 34if [ "${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 35 echo "the working tree is dirty; commit the release first (or ALLOW_DIRTY=1)" >&2
38 exit 1 36 exit 1
39fi 37fi
40 38
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}
49
50publish() { 39publish() {
51 local triple=$1 kind=$2 platform=$3 arch=$4 40 local triple=$1 kind=$2 platform=$3 arch=$4
52 local ext="" 41 local ext=""
@@ -85,23 +74,13 @@ publish_gui() {
85 74
86 mkdir -p "$DEST/gui" 75 mkdir -p "$DEST/gui"
87 install -m 0755 "$source" "$DEST/gui/$artifact" 76 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))" 77 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} 78}
104 79
80rm web/bin/client/*
81rm web/bin/gui/*
82rm web/bin/server/*
83
105publish_gui x86_64-unknown-linux-gnu linux x86_64 84publish_gui x86_64-unknown-linux-gnu linux x86_64
106publish_gui x86_64-pc-windows-gnu windows x86_64 85publish_gui x86_64-pc-windows-gnu windows x86_64
107 86
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 @@
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/scripts/installers/windows.ps1.in b/web/install.ps1
index 4b8e61d..92eef83 100644
--- a/scripts/installers/windows.ps1.in
+++ b/web/install.ps1
@@ -1,21 +1,18 @@
1# Zagan-Bet desktop installer for Windows. 1# Zagan-Bet installer for Windows.
2# 2#
3# Verifies the app against its published SHA-256, installs it for the current 3# Asks the site which build is newest, verifies its SHA-256, installs it for
4# user and adds a Start-menu shortcut. Put the downloaded app next to this 4# the current user and adds a Start-menu shortcut. Point it at a staging site
5# script, or run the script on its own and it will fetch the matching binary. 5# with -Site http://127.0.0.1:8899; add -NoPause for automation.
6# 6#
7# Run it with: 7# Run it with:
8# powershell -ExecutionPolicy Bypass -File .\zagan-bet-gui-*-install.ps1 8# powershell -ExecutionPolicy Bypass -File .\install.ps1
9# Add -NoPause to run it unattended. 9param(
10param([switch]$NoPause) 10 [string]$Site = "https://zagan.2137697.xyz",
11 [switch]$NoPause
12)
11 13
12$ErrorActionPreference = "Stop" 14$ErrorActionPreference = "Stop"
13 15
14$Version = "__VERSION__"
15$Binary = "__BINARY__"
16$Sha256 = "__SHA256__"
17$Url = "__URL__"
18
19function Finish([int]$Code) { 16function Finish([int]$Code) {
20 if (-not $NoPause) { 17 if (-not $NoPause) {
21 Read-Host "Press Enter to close" | Out-Null 18 Read-Host "Press Enter to close" | Out-Null
@@ -28,6 +25,30 @@ function Stop-Install([string]$Message) {
28 Finish 1 25 Finish 1
29} 26}
30 27
28try {
29 $latest = (Invoke-WebRequest -Uri "$Site/latest.php" -UseBasicParsing).Content
30} catch {
31 Stop-Install "could not read $Site/latest.php: $_"
32}
33
34$line = ($latest -split "`r?`n") |
35 Where-Object { $_ -match '^gui windows x86_64 ' } |
36 Select-Object -First 1
37if (-not $line) {
38 Stop-Install "no Windows build is published yet"
39}
40
41$parts = $line.Trim() -split '\s+'
42if ($parts.Count -lt 6) {
43 Stop-Install "the latest-build file is malformed"
44}
45$Version = $parts[3]
46$Binary = $parts[4]
47$Sha256 = $parts[5]
48$Url = "$Site/bin/gui/$Binary"
49
50Write-Host "Newest Zagan-Bet is $Version."
51
31$here = Split-Path -Parent $MyInvocation.MyCommand.Path 52$here = Split-Path -Parent $MyInvocation.MyCommand.Path
32$candidates = @( 53$candidates = @(
33 (Join-Path $here $Binary), 54 (Join-Path $here $Binary),
diff --git a/scripts/installers/linux.sh.in b/web/install.sh
index 5b53c02..998b262 100644
--- a/scripts/installers/linux.sh.in
+++ b/web/install.sh
@@ -1,24 +1,38 @@
1#!/usr/bin/env bash 1#!/usr/bin/env bash
2# Zagan-Bet desktop installer for Linux. 2# Zagan-Bet installer for Linux.
3# 3#
4# Verifies the app against its published SHA-256, installs it for the current 4# Asks the site which build is newest, verifies its SHA-256, installs it for
5# user and adds a launcher. Put the downloaded app next to this script, or run 5# the current user and adds a launcher. Point it at a staging site with
6# the script on its own and it will fetch the matching binary. 6# ZAGAN_SITE=http://127.0.0.1:8899.
7set -euo pipefail 7set -euo pipefail
8 8
9VERSION="__VERSION__" 9SITE=${ZAGAN_SITE:-https://zagan.2137697.xyz}
10BINARY="__BINARY__" 10PLATFORM=linux
11SHA256="__SHA256__" 11ARCH=x86_64
12URL="__URL__"
13 12
14say() { printf '%s\n' "$*"; } 13say() { printf '%s\n' "$*"; }
15die() { printf 'install: %s\n' "$*" >&2; exit 1; } 14die() { printf 'install: %s\n' "$*" >&2; exit 1; }
16 15
16command -v curl >/dev/null 2>&1 || die "curl is required"
17command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required" 17command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required"
18 18
19latest=$(curl -fsSL --max-time 30 "$SITE/latest.php") \
20 || die "could not read $SITE/latest.php"
21line=$(printf '%s\n' "$latest" \
22 | awk -v p="$PLATFORM" -v a="$ARCH" '$1 == "gui" && $2 == p && $3 == a { print; exit }')
23[ -n "$line" ] || die "no Linux build is published yet"
24
25version=$(printf '%s\n' "$line" | awk '{print $4}')
26binary=$(printf '%s\n' "$line" | awk '{print $5}')
27sha=$(printf '%s\n' "$line" | awk '{print $6}')
28[ -n "$version" ] && [ -n "$binary" ] && [ -n "$sha" ] \
29 || die "the latest-build file is malformed"
30
31say "Newest Zagan-Bet is $version."
32
19here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) 33here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
20source_file="" 34source_file=""
21for candidate in "$here/$BINARY" "./$BINARY" "$HOME/Downloads/$BINARY"; do 35for candidate in "$here/$binary" "./$binary" "$HOME/Downloads/$binary"; do
22 if [ -f "$candidate" ]; then 36 if [ -f "$candidate" ]; then
23 source_file=$candidate 37 source_file=$candidate
24 break 38 break
@@ -27,16 +41,17 @@ done
27 41
28cleanup="" 42cleanup=""
29if [ -z "$source_file" ]; then 43if [ -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" 44 cleanup=$(mktemp) || die "cannot create a temporary file"
32 say "Downloading $BINARY..." 45 say "Downloading $binary..."
33 curl -fsSL --max-time 300 "$URL" -o "$cleanup" || die "download failed" 46 curl -fsSL --max-time 300 "$SITE/bin/gui/$binary" -o "$cleanup" \
47 || die "download failed"
34 source_file=$cleanup 48 source_file=$cleanup
35fi 49fi
36trap 'if [ -n "$cleanup" ]; then rm -f "$cleanup"; fi' EXIT 50trap 'if [ -n "$cleanup" ]; then rm -f "$cleanup"; fi' EXIT
37 51
38actual=$(sha256sum "$source_file" | awk '{print $1}') 52actual=$(sha256sum "$source_file" | awk '{print $1}')
39[ "$actual" = "$SHA256" ] || die "checksum mismatch: expected $SHA256, got $actual" 53[ "$actual" = "$sha" ] \
54 || die "checksum mismatch: expected $sha, got $actual"
40 55
41bin_dir="${XDG_BIN_HOME:-$HOME/.local/bin}" 56bin_dir="${XDG_BIN_HOME:-$HOME/.local/bin}"
42data_dir="${XDG_DATA_HOME:-$HOME/.local/share}" 57data_dir="${XDG_DATA_HOME:-$HOME/.local/share}"
@@ -48,7 +63,12 @@ mkdir -p "$bin_dir" "$desktop_dir" "$icon_dir"
48install -m 0755 "$source_file" "$app" 63install -m 0755 "$source_file" "$app"
49 64
50cat > "$icon_dir/zagan-bet.svg" <<'ICON' 65cat > "$icon_dir/zagan-bet.svg" <<'ICON'
51__ICON__ 66<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
67 <rect width="64" height="64" rx="14" fill="#0a0e12"/>
68 <rect x="9" y="9" width="46" height="46" rx="10" fill="#0f1a14" stroke="#1f7d52" stroke-width="2"/>
69 <path d="M22 20h20L22 44h20" fill="none" stroke="#4ec27b" stroke-width="6"
70 stroke-linecap="round" stroke-linejoin="round"/>
71</svg>
52ICON 72ICON
53 73
54cat > "$desktop_dir/zagan-bet.desktop" <<DESKTOP 74cat > "$desktop_dir/zagan-bet.desktop" <<DESKTOP
@@ -70,7 +90,7 @@ if 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 90 gtk-update-icon-cache -q "$data_dir/icons/hicolor" >/dev/null 2>&1 || true
71fi 91fi
72 92
73say "Installed Zagan-Bet $VERSION to $app" 93say "Installed Zagan-Bet $version to $app"
74say "A launcher was added to your applications menu." 94say "A launcher was added to your applications menu."
75case ":$PATH:" in 95case ":$PATH:" in
76 *":$bin_dir:"*) ;; 96 *":$bin_dir:"*) ;;