max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
5 files changed,
+130 insertions,
-4 deletions
| @@ -22,6 +22,9 @@ | |||
| 22 | 22 | dist/* | |
| 23 | 23 | !dist/recipes/ | |
| 24 | 24 | !dist/*.sh | |
| 25 | + | # The release-signing public key is public by definition: README.md tells people | |
| 26 | + | # to verify downloads against it, so it has to arrive with a checkout. | |
| 27 | + | !dist/makecreative.pub | |
| 25 | 28 | ||
| 26 | 29 | # Archive | |
| 27 | 30 | _archive/ |
| @@ -68,6 +68,36 @@ | |||
| 68 | 68 | - **Keyboard shortcuts**: vim-style navigation throughout | |
| 69 | 69 | - **Platforms**: macOS (primary), Windows, Linux; iOS in development | |
| 70 | 70 | ||
| 71 | + | ## Verifying a release | |
| 72 | + | ||
| 73 | + | Every Linux release artifact (`.AppImage`, `.deb`) is published with a detached | |
| 74 | + | [minisign](https://jedisct1.github.io/minisign/) signature beside it, named | |
| 75 | + | `<artifact>.minisig`. Updates that arrive through the in-app updater are checked | |
| 76 | + | automatically against a separate key; this covers the manual download path, | |
| 77 | + | which nothing checks for you. | |
| 78 | + | ||
| 79 | + | Download both files, then: | |
| 80 | + | ||
| 81 | + | ```bash | |
| 82 | + | minisign -Vm goingson_<version>_<arch>.AppImage -p makecreative.pub | |
| 83 | + | ``` | |
| 84 | + | ||
| 85 | + | The public key is `dist/makecreative.pub` in this repository: | |
| 86 | + | ||
| 87 | + | ``` | |
| 88 | + | untrusted comment: Make Creative, LLC release key (minisign) | |
| 89 | + | RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9 | |
| 90 | + | ``` | |
| 91 | + | ||
| 92 | + | You can also pass it inline instead of saving the file: | |
| 93 | + | ||
| 94 | + | ```bash | |
| 95 | + | minisign -Vm goingson_<version>_<arch>.AppImage -P 'RWSMMbsBuZY5GfFRHPd19bZAVcyFAI4zsUlPjC5RaS/5tL7zT45SpjD9' | |
| 96 | + | ``` | |
| 97 | + | ||
| 98 | + | Expect `Signature and comment signature verified`. Any other result means the | |
| 99 | + | file is not what we published. Do not run it. | |
| 100 | + | ||
| 71 | 101 | ## License | |
| 72 | 102 | ||
| 73 | 103 | PolyForm Noncommercial 1.0.0. Personal, research, and non-commercial use are free. For commercial use, contact `info@makenot.work` to discuss a commercial license. |
| @@ -30,11 +30,29 @@ | |||
| 30 | 30 | "TAURI_SIGNING_PRIVATE_KEY_PASSWORD=$GOINGSON_TAURI_PASSWORD " + | |
| 31 | 31 | "cargo tauri build"); | |
| 32 | 32 | ||
| 33 | - | step("collect"); | |
| 33 | + | // The Tauri updater key above signs the OTA bundle, so an update that arrives | |
| 34 | + | // through the updater is already verified. A user who downloads the AppImage or | |
| 35 | + | // the .deb by hand gets none of that, which is what this covers: a detached | |
| 36 | + | // minisign signature against the key in dist/makecreative.pub, published in | |
| 37 | + | // README.md. It is deliberately a different key from the updater one, because | |
| 38 | + | // this signature is checked by a person rather than by the app. | |
| 39 | + | // sign-artifacts.sh verifies each signature before it returns, so a build host | |
| 40 | + | // holding the wrong key fails here. | |
| 41 | + | // | |
| 34 | 42 | // Resolve concrete paths (collect quotes its source, so a glob would not expand | |
| 35 | 43 | // for a local host, fw13 is the daemon's own host). | |
| 44 | + | step("sign"); | |
| 36 | 45 | let appimage = resolve_artifact(h, r + "/target/release/bundle/appimage/*.AppImage"); | |
| 37 | - | collect(h, appimage, "goingson", v); | |
| 38 | 46 | let deb = resolve_artifact_opt(h, r + "/target/release/bundle/deb/*.deb"); | |
| 39 | - | if deb != "" { collect(h, deb, "goingson", v); } | |
| 40 | - | log("GoingsOn " + target() + " v" + v + " collected."); | |
| 47 | + | let artifacts = "'" + appimage + "'"; | |
| 48 | + | if deb != "" { artifacts = artifacts + " '" + deb + "'"; } | |
| 49 | + | sh_ok(h, "cd " + r + " && ./dist/sign-artifacts.sh " + artifacts); | |
| 50 | + | ||
| 51 | + | step("collect"); | |
| 52 | + | collect(h, appimage, "goingson", v); | |
| 53 | + | collect(h, resolve_artifact(h, appimage + ".minisig"), "goingson", v); | |
| 54 | + | if deb != "" { | |
| 55 | + | collect(h, deb, "goingson", v); | |
| 56 | + | collect(h, resolve_artifact(h, deb + ".minisig"), "goingson", v); | |
| 57 | + | } | |
| 58 | + | log("GoingsOn " + 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 |