Skip to main content

max / audiofiles

Sign published Linux artifacts with minisign audiofiles has no updater, so a manual download was the only way to get a build and nothing verified it. Every published .AppImage and .deb now carries a detached .minisig against the Make Creative release key. sign-artifacts.sh verifies each signature against dist/makecreative.pub before returning, so a build host holding the wrong key fails the release instead of shipping artifacts nobody can check. build-appimage.sh signs when a key is present and skips when it is not, keeping local dev builds unblocked; the Bento recipe signs unconditionally, which is what makes signing mandatory for anything published.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-28 15:39 UTC
Signed with PGP, not checked
Commit: d716f9495366bfe6df726ab6e77796e839a2bb44
Parent: 8664649
6 files changed, +141 insertions, -4 deletions
M .gitignore +2
@@ -4,6 +4,8 @@
4 4 dist/*.dmg
5 5 dist/*.AppImage
6 6 dist/*.deb
7 + # Detached signatures follow the artifacts they sign, and are never committed.
8 + dist/*.minisig
7 9 # Release/build scripts are source — keep them version-controlled even if a
8 10 # broader dist/ ignore is ever added.
9 11 !dist/*.sh
M README.md +29
@@ -96,6 +96,35 @@
96 96 | Device export profiles | `crates/audiofiles-rhai/plugins/bundled/` |
97 97 | Architecture | `docs/architecture.md` |
98 98
99 + ## Verifying a release
100 +
101 + Every Linux release artifact (`.AppImage`, `.deb`) is published with a detached
102 + [minisign](https://jedisct1.github.io/minisign/) signature beside it, named
103 + `<artifact>.minisig`. audiofiles does not update itself, so running this check is
104 + the only thing that tells you a download is the build we published.
105 +
106 + Download both files, then:
107 +
108 + ```bash
109 + minisign -Vm Audiofiles-<version>-<arch>.AppImage -p makecreative.pub
110 + ```
111 +
112 + The public key is `dist/makecreative.pub` in this repository:
113 +
114 + ```
115 + untrusted comment: Make Creative, LLC release key (minisign)
116 + RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9
117 + ```
118 +
119 + You can also pass it inline instead of saving the file:
120 +
121 + ```bash
122 + minisign -Vm Audiofiles-<version>-<arch>.AppImage -P 'RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9'
123 + ```
124 +
125 + Expect `Signature and comment signature verified`. Any other result means the
126 + file is not what we published. Do not run it.
127 +
99 128 ## License
100 129
101 130 [PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/)
@@ -56,5 +56,19 @@
56 56 # Cleanup
57 57 rm -rf "$APPDIR"
58 58
59 + # Step 5: Sign the AppImage, when a release key is available.
60 + #
61 + # A release build must be signed, but a developer building locally has no key
62 + # and should not be blocked, so this is skipped rather than failed when the key
63 + # is absent. The Bento recipe (dist/recipes/linux.rhai) signs unconditionally,
64 + # which is what makes signing mandatory for anything we publish.
65 + MINISIGN_KEY="${MINISIGN_KEY:-$HOME/.minisign/makecreative.key}"
66 + if [ -f "$MINISIGN_KEY" ]; then
67 + echo "==> Signing AppImage..."
68 + MINISIGN_KEY="$MINISIGN_KEY" "$DIST_DIR/sign-artifacts.sh" "$APPIMAGE_PATH"
69 + else
70 + echo "==> No signing key at $MINISIGN_KEY, skipping signature (unreleasable build)"
71 + fi
72 +
59 73 echo ""
60 74 echo "Done: $APPIMAGE_PATH"
@@ -24,9 +24,26 @@
24 24 sh_ok(h, "cd " + r + " && ./dist/build-appimage.sh");
25 25 sh_ok(h, "cd " + r + " && ./dist/build-deb.sh");
26 26
27 - step("collect");
27 + // audiofiles has no updater, so a download from the dist host is the only way a
28 + // user gets a build and nothing verifies it for them. Every artifact we publish
29 + // carries a detached minisign signature against the key in dist/makecreative.pub.
30 + // build-appimage.sh already signs the AppImage when a key is on the host; this
31 + // re-signs it so the guarantee comes from the recipe rather than from the key
32 + // happening to be there, and it is the only thing that signs the .deb.
33 + // sign-artifacts.sh verifies each signature against the public key before it
34 + // returns, so a build host holding the wrong key fails here instead of shipping.
35 + step("sign");
28 36 let appimage = resolve_artifact(h, r + "/dist/*.AppImage");
29 - collect(h, appimage, "audiofiles", v);
30 37 let deb = resolve_artifact_opt(h, r + "/dist/*.deb");
31 - if deb != "" { collect(h, deb, "audiofiles", v); }
32 - log("audiofiles " + target() + " v" + v + " collected.");
38 + let artifacts = "'" + appimage + "'";
39 + if deb != "" { artifacts = artifacts + " '" + deb + "'"; }
40 + sh_ok(h, "cd " + r + " && ./dist/sign-artifacts.sh " + artifacts);
41 +
42 + step("collect");
43 + collect(h, appimage, "audiofiles", v);
44 + collect(h, resolve_artifact(h, appimage + ".minisig"), "audiofiles", v);
45 + if deb != "" {
46 + collect(h, deb, "audiofiles", v);
47 + collect(h, resolve_artifact(h, deb + ".minisig"), "audiofiles", v);
48 + }
49 + log("audiofiles " + target() + " v" + v + " collected (signed).");
@@ -1,0 +1,2 @@
1 + untrusted comment: Make Creative, LLC release key (minisign)
2 + RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9
@@ -1,0 +1,73 @@
1 + #!/usr/bin/env bash
2 + # Sign release artifacts with the Make Creative release key (minisign).
3 + #
4 + # Every Linux artifact we publish gets a detached `.minisig` beside it so a
5 + # download can be checked against the public key in dist/makecreative.pub (also
6 + # printed in README.md). Nothing in the app auto-applies an update, so a user
7 + # running the verify command is the only thing between a compromised dist host
8 + # and a bad binary on their machine.
9 + #
10 + # dist/sign-artifacts.sh <artifact> [<artifact>...]
11 + #
12 + # Key material, present on every Linux build host (fw13, astra):
13 + #
14 + # ~/.minisign/makecreative.key encrypted secret key, mode 0600
15 + # ~/.minisign/password.env exports MINISIGN_PASSWORD
16 + #
17 + # Neither is in git. Generation, storage, and rotation are in
18 + # _private/docs/meta/ota-release-runbook.md.
19 + #
20 + # Signatures are verified against dist/makecreative.pub before this script
21 + # exits, so a build host holding the wrong key fails the release rather than
22 + # shipping artifacts nobody can verify.
23 +
24 + set -euo pipefail
25 +
26 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
27 +
28 + KEY="${MINISIGN_KEY:-$HOME/.minisign/makecreative.key}"
29 + PASSWORD_ENV="${MINISIGN_PASSWORD_ENV:-$HOME/.minisign/password.env}"
30 + PUBKEY="${MINISIGN_PUBKEY:-$SCRIPT_DIR/makecreative.pub}"
31 +
32 + die() { echo "sign-artifacts: $*" >&2; exit 1; }
33 +
34 + [ "$#" -gt 0 ] || die "usage: $0 <artifact> [<artifact>...]"
35 +
36 + command -v minisign >/dev/null 2>&1 \
37 + || die "minisign is not on PATH (Debian/Ubuntu: apt install minisign)"
38 +
39 + [ -f "$KEY" ] || die "no signing key at $KEY (see the OTA release runbook)"
40 + [ -f "$PUBKEY" ] || die "no public key at $PUBKEY"
41 + if grep -q '^PLACEHOLDER' "$PUBKEY"; then
42 + die "$PUBKEY is still a placeholder: install the real public key (see the OTA release runbook)"
43 + fi
44 +
45 + if [ -z "${MINISIGN_PASSWORD:-}" ]; then
46 + [ -f "$PASSWORD_ENV" ] \
47 + || die "MINISIGN_PASSWORD is unset and $PASSWORD_ENV does not exist"
48 + # shellcheck disable=SC1090
49 + . "$PASSWORD_ENV"
50 + fi
51 + [ -n "${MINISIGN_PASSWORD:-}" ] || die "MINISIGN_PASSWORD is empty"
52 +
53 + for artifact in "$@"; do
54 + [ -f "$artifact" ] || die "no such artifact: $artifact"
55 + done
56 +
57 + # One invocation for the whole set: the password is read once, and minisign
58 + # still writes a per-file trusted comment (timestamp, filename, prehashed), so
59 + # a signature cannot be transplanted onto a different artifact.
60 + printf '%s\n' "$MINISIGN_PASSWORD" | minisign -S \
61 + -s "$KEY" \
62 + -c "Make Creative, LLC release artifact" \
63 + -m "$@" >/dev/null
64 +
65 + for artifact in "$@"; do
66 + if ! minisign -V -q -p "$PUBKEY" -m "$artifact" >/dev/null; then
67 + # Leave nothing behind that a later collect could mistake for a good
68 + # signature.
69 + rm -f "${artifact}.minisig"
70 + die "signature for $artifact does not verify against $PUBKEY"
71 + fi
72 + echo "signed: ${artifact}.minisig"
73 + done