Debian packaging
This commit is contained in:
49
README.md
49
README.md
@@ -139,6 +139,55 @@ From then on, `sudo dnf upgrade` picks up new releases.
|
||||
- The RPM is built against this machine's libraries, so it installs on this
|
||||
Fedora release and newer.
|
||||
|
||||
### Distributing to Debian/Ubuntu (apt repo)
|
||||
|
||||
Debian-based machines get the app the same way, from an apt repo you host.
|
||||
`scripts/deb-publish.sh` builds the `.deb` here on Fedora and adds it to the
|
||||
repo in a directory you choose. It needs:
|
||||
|
||||
```bash
|
||||
sudo dnf install reprepro dpkg
|
||||
```
|
||||
|
||||
It signs with the same key as the dnf repo. If you haven't made that key yet,
|
||||
pass `--init-key` the first time. Either way, give the first run the address
|
||||
the directory will be served at:
|
||||
|
||||
```bash
|
||||
DEB_REPO_URL=https://deb.example.com/mtgc ./scripts/deb-publish.sh ~/deb-mtgc
|
||||
```
|
||||
|
||||
After that, publishing is just:
|
||||
|
||||
```bash
|
||||
./scripts/deb-publish.sh ~/deb-mtgc # or: npm run deb:publish -- ~/deb-mtgc
|
||||
```
|
||||
|
||||
Then, on each machine, add the repo once and install:
|
||||
|
||||
```bash
|
||||
sudo curl -fsSLo /etc/apt/sources.list.d/mtgc.sources https://deb.example.com/mtgc/mtgc.sources
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt install mtgc
|
||||
```
|
||||
|
||||
From then on, `sudo apt upgrade` picks up new releases.
|
||||
|
||||
- **Serve the whole directory.** It holds `pool/`, the signed `dists/`, the
|
||||
public key (`mtgc.asc`) and `mtgc.sources`, which carries the key inline so
|
||||
machines don't need a separate keyring step. `conf/` and `db/` are
|
||||
reprepro's own state; they're harmless to serve but aren't needed. The
|
||||
newest three versions are kept, so a bad release can be undone with
|
||||
`sudo apt install mtgc=<older version>`.
|
||||
- Versions are the same as the RPM's. A build published with `--allow-dirty`
|
||||
gets a `~dirty` suffix, which apt sorts below the clean build of the same
|
||||
version.
|
||||
- The `.deb` is built against this machine's libraries, so it needs a distro
|
||||
with at least this Fedora's glibc (2.43 on Fedora 44). Older Debian and
|
||||
Ubuntu releases can't install it.
|
||||
|
||||
### Distributing to Windows
|
||||
|
||||
The Windows installer is cross-built here on Linux and published to a plain
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"sync-server:push": "scripts/sync-server-image.sh push",
|
||||
"android:publish": "scripts/fdroid-publish.sh",
|
||||
"rpm:publish": "scripts/rpm-publish.sh",
|
||||
"deb:publish": "scripts/deb-publish.sh",
|
||||
"windows:publish": "scripts/windows-publish.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
219
scripts/deb-publish.sh
Executable file
219
scripts/deb-publish.sh
Executable file
@@ -0,0 +1,219 @@
|
||||
#!/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"
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user