Files
mtgc/scripts/deb-publish.sh
2026-10-22 21:33:31 -05:00

264 lines
8.8 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# deb-publish.sh — build the desktop app's .deb and add it to a signed apt repo.
#
# Usage:
# DEB_REPO_URL=https://deb.example.com/mtgc \
# ./scripts/deb-publish.sh --init-key ~/deb-mtgc # first time
# ./scripts/deb-publish.sh ~/deb-mtgc # every release after
#
# Or through npm: `npm run deb:publish -- ~/deb-mtgc`.
#
# Options:
# --init-key generate the package signing key (once, ever; skip it if
# rpm-publish.sh already made one, the two share it)
# --allow-dirty publish uncommitted changes; the version gets a ~dirty suffix
#
# Environment:
# MTGC_DEB_DIR repo directory, if not given as an argument
# DEB_REPO_URL public address of the repo directory; needed the first time,
# and changes the address in mtgc.sources if set later
# MTGC_DEB_GNUPGHOME where the key lives (default ~/.local/share/mtgc/gnupg)
#
# The whole repo directory is public — the signing key lives elsewhere — so
# serve it as is. Needs reprepro and dpkg (`sudo dnf install reprepro dpkg`)
# and the desktop setup from ./scripts/devel-setup.sh.
#
set -euo pipefail
usage() {
sed -n '3,26p' "$0" | sed 's/^# \{0,1\}//'
exit "${1:-0}"
}
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[err]\033[0m %s\n' "$*" >&2; }
PACKAGE="mtgc"
SUITE="stable"
GNUPGHOME="${MTGC_DEB_GNUPGHOME:-$HOME/.local/share/mtgc/gnupg}"
KEEP=3 # versions kept in the repo, so a bad release can be downgraded from
init_key=false
allow_dirty=false
dir="${MTGC_DEB_DIR:-}"
while [ $# -gt 0 ]; do
case "$1" in
--init-key) init_key=true ;;
--allow-dirty) allow_dirty=true ;;
-h | --help) usage ;;
-*) usage 1 ;;
*) dir="$1" ;;
esac
shift
done
[ -n "$dir" ] || usage 1
mkdir -p "$dir"
dir="$(cd "$dir" && pwd)"
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root"
# --- 1. Tools ---------------------------------------------------------------
for tool in reprepro dpkg-deb gpg; do
if ! command -v "$tool" >/dev/null 2>&1; then
err "$tool not found. Install them: sudo dnf install reprepro dpkg"
exit 1
fi
done
# --- 2. Package signing key -------------------------------------------------
#
# The same key and keyring as rpm-publish.sh, with no passphrase so publishing
# runs unattended. Every machine trusts it once, when it adds the repo; lose it
# and each one has to take a new key before it takes another update.
gpg_() { gpg --homedir "$GNUPGHOME" --batch "$@"; }
key_fpr() { gpg_ --list-secret-keys --with-colons 2>/dev/null | awk -F: '/^fpr/ { print $10; exit }'; }
if $init_key; then
if [ -n "$(key_fpr)" ]; then
err "a signing key already exists in $GNUPGHOME; not making another"
exit 1
fi
info "generating the package signing key in $GNUPGHOME"
mkdir -p "$GNUPGHOME"
chmod 700 "$GNUPGHOME"
gpg_ --pinentry-mode loopback --passphrase '' \
--quick-gen-key "mtgc packages" rsa4096 sign never 2>/dev/null
warn "Back up $GNUPGHOME."
warn "Without it, every machine has to trust a new key before it takes another update."
fi
fpr="$(key_fpr)"
if [ -z "$fpr" ]; then
err "no package signing key in $GNUPGHOME. Run once with --init-key."
exit 1
fi
# --- 3. Version -------------------------------------------------------------
source scripts/lib/release-version.sh
deb_version="$release_version"
if $release_dirty; then
if ! $allow_dirty; then
err "the working tree has uncommitted changes; commit them or pass --allow-dirty"
exit 1
fi
# ~ sorts before everything, so the clean build of this version still wins.
deb_version="$release_version~dirty"
fi
# --- 4. The repo's address --------------------------------------------------
#
# mtgc.sources carries the public key inline, so a machine adds the repo with
# one download and no separate keyring step.
repo_url="${DEB_REPO_URL:-}"
repo_url="${repo_url%/}"
case "$repo_url" in
"" | http://* | https://*) ;;
*) err "DEB_REPO_URL must be an http(s):// address"; exit 1 ;;
esac
if [ -z "$repo_url" ] && [ ! -f "$dir/$PACKAGE.sources" ]; then
err "no repo in $dir yet; set DEB_REPO_URL to the address $dir will be served at"
exit 1
fi
gpg_ --armor --export "$fpr" >"$dir/$PACKAGE.asc"
if [ -z "$repo_url" ]; then
repo_url="$(sed -n 's/^URIs: //p' "$dir/$PACKAGE.sources")"
fi
{
cat <<EOF
Types: deb
URIs: $repo_url
Suites: $SUITE
Components: main
Architectures: amd64
Signed-By:
EOF
sed 's/^$/./; s/^/ /' "$dir/$PACKAGE.asc"
} >"$dir/$PACKAGE.sources"
# A landing page for whoever opens the repo's address in a browser.
cat >"$dir/README.html" <<EOF
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>mtgc apt repo</title>
<style>
:root { color-scheme: light dark; --bg: #fff; --fg: #1d1d1f; --muted: #6e6e73; --code: #f2f2f4; }
@media (prefers-color-scheme: dark) { :root { --bg: #161618; --fg: #ececee; --muted: #9a9aa0; --code: #242428; } }
body { margin: 0 auto; max-width: 42rem; padding: 2rem 1rem; background: var(--bg); color: var(--fg);
font: 16px/1.55 system-ui, sans-serif; }
h1 { font-size: 1.5rem; margin: 0 0 .25rem; }
h2 { font-size: 1.05rem; margin: 1.75rem 0 .5rem; }
p { margin: .5rem 0; }
.muted { color: var(--muted); }
pre { background: var(--code); padding: .75rem 1rem; border-radius: 6px; overflow-x: auto; }
code { font: .9em ui-monospace, monospace; }
</style>
</head>
<body>
<h1>mtgc for Debian &amp; Ubuntu</h1>
<p class="muted">An apt repository for the mtgc desktop app (amd64).</p>
<h2>Install</h2>
<p>Add the repo once. The file includes the signing key, so you don't need a separate keyring step.</p>
<pre><code>sudo curl -fsSLo /etc/apt/sources.list.d/$PACKAGE.sources $repo_url/$PACKAGE.sources
sudo apt update &amp;&amp; sudo apt install $PACKAGE</code></pre>
<p>From then on, <code>sudo apt upgrade</code> picks up new releases.</p>
<h2>Requirements</h2>
<p>A 64-bit x86 system with glibc 2.43 or newer (check with <code>ldd --version</code>).
Older releases, such as Ubuntu 24.04 and Debian 13, can't install it.</p>
<h2>Uninstall</h2>
<pre><code>sudo apt remove $PACKAGE
sudo rm /etc/apt/sources.list.d/$PACKAGE.sources</code></pre>
<p class="muted">Signing key: <a href="$PACKAGE.asc">$PACKAGE.asc</a>, fingerprint <code>$fpr</code></p>
</body>
</html>
EOF
mkdir -p "$dir/conf"
cat >"$dir/conf/distributions" <<EOF
Codename: $SUITE
Suite: $SUITE
Label: mtgc
Origin: mtgc
Components: main
Architectures: amd64
SignWith: $fpr
Limit: $KEEP
EOF
repo_() { GNUPGHOME="$GNUPGHOME" reprepro --silent -b "$dir" "$@"; }
# --- 5. Only ever move forward ----------------------------------------------
newest=0
while read -r version; do
patch="${version%%~*}"
patch="${patch##*.}"
[ "$patch" -gt "$newest" ] && newest="$patch"
done < <(repo_ list "$SUITE" "$PACKAGE" 2>/dev/null | awk '{ print $NF }')
if [ "$release_count" -le "$newest" ]; then
err "version $release_version is not newer than the repo's x.y.$newest; commit something first"
exit 1
fi
# --- 6. Build ---------------------------------------------------------------
info "building $deb_version"
outputs="target/release/bundle/deb"
rm -rf "$outputs"
# reprepro files packages by section, which Tauri leaves out unless told.
cargo tauri build --bundles deb --ci \
--config "{\"version\":\"$release_version\",\"bundle\":{\"linux\":{\"deb\":{\"section\":\"games\",\"priority\":\"optional\"}}}}"
built="$(find "$outputs" -maxdepth 1 -name '*.deb' | head -1)"
if [ -z "$built" ]; then
err "the build produced no .deb under $outputs"
exit 1
fi
# Tauri's deb config has no release field, so mark a dirty build by repacking.
if [ "$deb_version" != "$release_version" ]; then
unpacked="$outputs/unpacked"
dpkg-deb -R "$built" "$unpacked"
sed -i "s/^Version: .*/Version: $deb_version/" "$unpacked/DEBIAN/control"
repacked="$outputs/${PACKAGE}_${deb_version}_amd64.deb"
dpkg-deb --root-owner-group -b "$unpacked" "$repacked" >/dev/null
rm -rf "$unpacked" "$built"
built="$repacked"
fi
# --- 7. Publish -------------------------------------------------------------
#
# reprepro writes the pool, the indexes and the signed Release in one go, and
# drops all but the newest $KEEP versions; if it fails, nothing changed.
info "adding it to the repo"
if ! repo_ includedeb "$SUITE" "$built"; then
err "reprepro failed; nothing was published"
exit 1
fi
# --- 8. How to use it -------------------------------------------------------
info "published $deb_version"
cat <<EOF
Serve $dir at $repo_url. On each machine, once:
sudo curl -fsSLo /etc/apt/sources.list.d/$PACKAGE.sources $repo_url/$PACKAGE.sources
sudo apt update && sudo apt install $PACKAGE
After that, \`sudo apt upgrade\` picks up new releases.
EOF