max / balanced_breakfast
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
5 files changed,
+133 insertions,
-4 deletions
| @@ -25,6 +25,13 @@ | |||
| 25 | 25 | # recipes are non-secret and tracked. | |
| 26 | 26 | dist/* | |
| 27 | 27 | !dist/recipes/ | |
| 28 | + | # Artifact signing. sign-artifacts.sh holds no secret (the key and its password | |
| 29 | + | # live outside the repo, under ~/.minisign), and the public key is public by | |
| 30 | + | # definition: README.md tells people to verify downloads against it, so both have | |
| 31 | + | # to arrive with a checkout. Named individually rather than as `!dist/*.sh` to | |
| 32 | + | # keep the rule above in force for anything else that lands in dist/. | |
| 33 | + | !dist/sign-artifacts.sh | |
| 34 | + | !dist/makecreative.pub | |
| 28 | 35 | ||
| 29 | 36 | # Tauri generated | |
| 30 | 37 | src-tauri/gen/ |
| @@ -103,6 +103,36 @@ | |||
| 103 | 103 | | Plugin authoring guide | `docs/plugin_authoring.md` | | |
| 104 | 104 | | Architecture | `docs/architecture.md` | | |
| 105 | 105 | ||
| 106 | + | ## Verifying a release | |
| 107 | + | ||
| 108 | + | Every Linux release artifact (`.AppImage`, `.deb`) is published with a detached | |
| 109 | + | [minisign](https://jedisct1.github.io/minisign/) signature beside it, named | |
| 110 | + | `<artifact>.minisig`. Updates that arrive through the in-app updater are checked | |
| 111 | + | automatically against a separate key; this covers the manual download path, | |
| 112 | + | which nothing checks for you. | |
| 113 | + | ||
| 114 | + | Download both files, then: | |
| 115 | + | ||
| 116 | + | ```bash | |
| 117 | + | minisign -Vm balanced-breakfast_<version>_<arch>.AppImage -p makecreative.pub | |
| 118 | + | ``` | |
| 119 | + | ||
| 120 | + | The public key is `dist/makecreative.pub` in this repository: | |
| 121 | + | ||
| 122 | + | ``` | |
| 123 | + | untrusted comment: Make Creative, LLC release key (minisign) | |
| 124 | + | RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9 | |
| 125 | + | ``` | |
| 126 | + | ||
| 127 | + | You can also pass it inline instead of saving the file: | |
| 128 | + | ||
| 129 | + | ```bash | |
| 130 | + | minisign -Vm balanced-breakfast_<version>_<arch>.AppImage -P 'RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9' | |
| 131 | + | ``` | |
| 132 | + | ||
| 133 | + | Expect `Signature and comment signature verified`. Any other result means the | |
| 134 | + | file is not what we published. Do not run it. | |
| 135 | + | ||
| 106 | 136 | ## License | |
| 107 | 137 | ||
| 108 | 138 | PolyForm Noncommercial 1.0.0 |
| @@ -31,9 +31,26 @@ | |||
| 31 | 31 | "TAURI_SIGNING_PRIVATE_KEY_PASSWORD=$BB_TAURI_PASSWORD " + | |
| 32 | 32 | "cargo tauri build " + feature_flags()); | |
| 33 | 33 | ||
| 34 | - | step("collect"); | |
| 34 | + | // The Tauri updater key above signs the OTA bundle, so an update that arrives | |
| 35 | + | // through the updater is already verified. A user who downloads the AppImage or | |
| 36 | + | // the .deb by hand gets none of that, which is what this covers: a detached | |
| 37 | + | // minisign signature against the key in dist/makecreative.pub, published in | |
| 38 | + | // README.md. It is deliberately a different key from the updater one, because | |
| 39 | + | // this signature is checked by a person rather than by the app. | |
| 40 | + | // sign-artifacts.sh verifies each signature before it returns, so a build host | |
| 41 | + | // holding the wrong key fails here. | |
| 42 | + | step("sign"); | |
| 35 | 43 | let appimage = resolve_artifact(h, r + "/target/release/bundle/appimage/*.AppImage"); | |
| 36 | - | collect(h, appimage, "balanced_breakfast", v); | |
| 37 | 44 | let deb = resolve_artifact_opt(h, r + "/target/release/bundle/deb/*.deb"); | |
| 38 | - | if deb != "" { collect(h, deb, "balanced_breakfast", v); } | |
| 39 | - | log("Balanced Breakfast " + target() + " v" + v + " collected."); | |
| 45 | + | let artifacts = "'" + appimage + "'"; | |
| 46 | + | if deb != "" { artifacts = artifacts + " '" + deb + "'"; } | |
| 47 | + | sh_ok(h, "cd " + r + " && ./dist/sign-artifacts.sh " + artifacts); | |
| 48 | + | ||
| 49 | + | step("collect"); | |
| 50 | + | collect(h, appimage, "balanced_breakfast", v); | |
| 51 | + | collect(h, resolve_artifact(h, appimage + ".minisig"), "balanced_breakfast", v); | |
| 52 | + | if deb != "" { | |
| 53 | + | collect(h, deb, "balanced_breakfast", v); | |
| 54 | + | collect(h, resolve_artifact(h, deb + ".minisig"), "balanced_breakfast", v); | |
| 55 | + | } | |
| 56 | + | log("Balanced Breakfast " + 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 |