From 4c1cc1175fd3bb030fe90ec734116e63f24f793e Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 30 Oct 2026 00:04:50 +0100 Subject: make the download page dynamic: scan builds, show versions and SHA-256 --- web/README.md | 64 ++++++++ web/bin/.gitignore | 2 + web/index.html | 213 -------------------------- web/index.php | 437 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 503 insertions(+), 213 deletions(-) create mode 100644 web/README.md create mode 100644 web/bin/.gitignore delete mode 100644 web/index.html create mode 100644 web/index.php diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..3c6ee08 --- /dev/null +++ b/web/README.md @@ -0,0 +1,64 @@ +# Download page + +`index.php` scans `bin/client` and `bin/server` on every request and renders +whatever builds it finds, with version, size and SHA-256. No database, no +JavaScript, no dependencies. PHP 8.0+. + +## Layout + +``` +web/ + index.php + bin/client/ served at /bin/client/... + bin/server/ served at /bin/server/... +``` + +Serve `web/` as the document root. Binaries in `bin/` are gitignored. + +## Naming + +The page only lists files that match the release pattern; anything else in +those directories is ignored. + +``` +zagan-bet-client---[.exe] +zagan-bet-server--- +``` + +`` is `MAJOR.MINOR.PATCH`, `` is `linux`, `windows` or +`macos`, `` is `x86_64`, `i686` or `aarch64`. + +## Publishing a build + +From the repository root, with the version taken from `Cargo.toml`: + +```sh +V=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) + +cargo build --release --target x86_64-unknown-linux-gnu +cargo build --release --bin client --no-default-features --target i686-unknown-linux-gnu +cargo build --release --bin client --target x86_64-pc-windows-gnu + +cp "target/x86_64-unknown-linux-gnu/release/server" "web/bin/server/zagan-bet-server-$V-linux-x86_64" +cp "target/x86_64-unknown-linux-gnu/release/client" "web/bin/client/zagan-bet-client-$V-linux-x86_64" +cp "target/i686-unknown-linux-gnu/release/client" "web/bin/client/zagan-bet-client-$V-linux-i686" +cp "target/x86_64-pc-windows-gnu/release/client.exe" "web/bin/client/zagan-bet-client-$V-windows-x86_64.exe" +``` + +`--no-default-features` drops the server's OpenSSL dependency, which is what +the 32-bit client build would otherwise fail on. + +## Preview locally + +```sh +php -S 127.0.0.1:8899 -t web +``` + +Then open . + +## Notes + +- Hashes are computed per request with `hash_file()`. That is fine for a + handful of builds; add an mtime-keyed cache if the directory grows. +- The page lists every build it finds, newest version first, so old releases + stay downloadable until you remove them from `bin/`. diff --git a/web/bin/.gitignore b/web/bin/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/web/bin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/web/index.html b/web/index.html deleted file mode 100644 index c9ad8d5..0000000 --- a/web/index.html +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - -Download · zagan.bet - - - - - -
- -
-

zagan.bet

-

Terminal betting. Slots, roulette, blackjack and crash.

-
- -
-
-

Client

-

Needs a terminal of at least 80×24 cells.

-
    -
  • -

    Linux

    -

    x86_64 · 64-bit

    - Download -
  • -
  • -

    Linux

    -

    i686 · 32-bit

    - Download -
  • -
  • -

    Windows

    -

    x86_64 · 64-bit

    - Download -
  • -
-
- -
-

Server

-

One build, Linux x86_64. Generates its own TLS identity on first run.

-
    -
  • -

    Linux

    -

    x86_64 · 64-bit

    - Download -
  • -
-
-
- -
-

Running it

-
# client
-chmod +x zagan-bet-client-linux-x86_64
-./zagan-bet-client-linux-x86_64
- -
# server: first run creates identity.p12, key.pem and cert.pem
-chmod +x zagan-bet-server-linux-x86_64
-./zagan-bet-server-linux-x86_64 --init
-./zagan-bet-server-linux-x86_64 -i 0.0.0.0 -p 11525
- -

Windows: run the .exe from Windows Terminal or another real terminal.

-

Verify a download before running it: sha256sum <file> (Linux) or Get-FileHash <file> (Windows).

-
- -
- - diff --git a/web/index.php b/web/index.php new file mode 100644 index 0000000..cd77dad --- /dev/null +++ b/web/index.php @@ -0,0 +1,437 @@ +-- + * bin/client/zagan-bet-client---.exe + * bin/server/zagan-bet-server--- + * + * is MAJOR.MINOR.PATCH, is linux|windows|macos and + * is x86_64|i686|aarch64. Example: + * + * bin/client/zagan-bet-client-3.1.1-linux-x86_64 + * bin/server/zagan-bet-server-3.1.1-linux-x86_64 + * + * The hashes are computed per request with hash_file(). That is a few tens of + * milliseconds for a handful of builds; add a cache keyed on mtime if the + * directory ever grows. + */ + +const KINDS = [ + 'client' => [ + 'title' => 'Client', + 'hint' => 'Needs a terminal of at least 80×24 cells.', + ], + 'server' => [ + 'title' => 'Server', + 'hint' => 'Generates its own TLS identity on first run.', + ], +]; + +const PLATFORMS = [ + 'linux' => 'Linux', + 'windows' => 'Windows', + 'macos' => 'macOS', +]; + +const ARCHES = [ + 'x86_64' => '64-bit', + 'i686' => '32-bit', + 'aarch64' => 'ARM64', +]; + +// Newest version first, then 64-bit builds before 32-bit. +const PLATFORM_ORDER = ['linux' => 0, 'windows' => 1, 'macos' => 2]; +const ARCH_ORDER = ['x86_64' => 0, 'aarch64' => 1, 'i686' => 2]; + +function e(?string $value): string +{ + return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); +} + +function human_size(int $bytes): string +{ + if ($bytes >= 1048576) { + return number_format($bytes / 1048576, 1) . ' MB'; + } + if ($bytes >= 1024) { + return number_format($bytes / 1024) . ' KB'; + } + return $bytes . ' B'; +} + +/** + * @return array{version:string,platform:string,arch:string,file:string,size:int,mtime:int,sha256:string}|null + */ +function read_release(string $kind, string $directory, string $entry): ?array +{ + $pattern = '/^zagan-bet-' . preg_quote($kind, '/') + . '-v?(\d+\.\d+\.\d+)-([a-z]+)-([a-z0-9_]+)(\.exe)?$/i'; + if (preg_match($pattern, $entry, $matches) !== 1) { + return null; + } + + $realDirectory = realpath($directory); + $real = realpath($directory . '/' . $entry); + if ($realDirectory === false || $real === false) { + return null; + } + // Never serve anything that resolves outside the published directory. + if (!str_starts_with($real, $realDirectory . DIRECTORY_SEPARATOR)) { + return null; + } + if (!is_file($real)) { + return null; + } + + $size = filesize($real); + $hash = hash_file('sha256', $real); + if ($size === false || $hash === false) { + return null; + } + + return [ + 'version' => $matches[1], + 'platform' => strtolower($matches[2]), + 'arch' => strtolower($matches[3]), + 'file' => $entry, + 'size' => $size, + 'mtime' => filemtime($real) ?: 0, + 'sha256' => $hash, + ]; +} + +$base = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/'); +if ($base === '.' || $base === '/') { + $base = ''; +} + +$releases = []; +$newest = 0; + +foreach (KINDS as $kind => $meta) { + $found = []; + $directory = __DIR__ . '/bin/' . $kind; + + if (is_dir($directory)) { + $entries = scandir($directory); + if ($entries !== false) { + foreach ($entries as $entry) { + if ($entry === '.' || $entry === '..') { + continue; + } + $release = read_release($kind, $directory, $entry); + if ($release === null) { + continue; + } + $release['href'] = $base . '/bin/' . $kind . '/' . rawurlencode($release['file']); + $found[] = $release; + $newest = max($newest, $release['mtime']); + } + } + } + + usort($found, static function (array $a, array $b): int { + $byVersion = version_compare($b['version'], $a['version']); + if ($byVersion !== 0) { + return $byVersion; + } + $byPlatform = (PLATFORM_ORDER[$a['platform']] ?? 9) <=> (PLATFORM_ORDER[$b['platform']] ?? 9); + if ($byPlatform !== 0) { + return $byPlatform; + } + $byArch = (ARCH_ORDER[$a['arch']] ?? 9) <=> (ARCH_ORDER[$b['arch']] ?? 9); + if ($byArch !== 0) { + return $byArch; + } + return strcmp($a['file'], $b['file']); + }); + + $releases[$kind] = $found; +} +?> + + + + + +Download · zagan.bet + + + + + + +
+ +
+

zagan.bet

+

Terminal betting. Slots, roulette, blackjack and crash.

+ 0): ?> +

Builds published UTC

+ +
+ +
+ $meta): ?> +
+

+

+ + +

Nothing published yet.

+ +
    + +
  • +
    +

    + v +
    +

    + + · + +

    +

    + Download +
  • + +
+ +
+ + + + +
+

Checksums

+

SHA-256 of every published file.

+
+ + + + + + + + + + + + + + + + + + + +
FileVersionSizeSHA-256
+
+
+ +
+ + + +
+ + -- cgit v1.2.3