ci(release): publish signed updater assets alongside tarball
After packaging, stage the bare AniTrack-linux-amd64 binary (the updater swaps os.Executable in place; the user tarball single top-level directory would fail the single-entry rule), sign it with wails3 updater sign using UPDATER_SIGNING_KEY (missing secret is a hard error, key shredded after use), split the output into .sha512/.sig sidecars, and extend the idempotent replace-upload loop from 1 asset to all 4. Tarball flow untouched; rc tags get signed assets too, which the beta-channel live proof needs.
This commit is contained in:
@@ -245,7 +245,38 @@ jobs:
|
|||||||
echo "Created new release for ${TAG}"
|
echo "Created new release for ${TAG}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload release archive
|
- name: Build updater asset and sign
|
||||||
|
env:
|
||||||
|
UPDATER_SIGNING_KEY: ${{ secrets.UPDATER_SIGNING_KEY }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${VERSION:?VERSION missing from version-guard step}"
|
||||||
|
export PATH="${HOME}/go/bin:${PATH}"
|
||||||
|
: "${UPDATER_SIGNING_KEY:?Missing secret UPDATER_SIGNING_KEY. Add the updater private key under Settings → Secrets → Actions.}"
|
||||||
|
# Bare updater binary: the updater swaps os.Executable() in place,
|
||||||
|
# so it takes the single binary, not the user tarball (whose
|
||||||
|
# top-level directory would fail the single-entry rule).
|
||||||
|
mkdir -p updater-dist
|
||||||
|
cp build/bin/AniTrack updater-dist/AniTrack-linux-amd64
|
||||||
|
printf '%s' "${UPDATER_SIGNING_KEY}" > updater-dist/updater.key
|
||||||
|
chmod 600 updater-dist/updater.key
|
||||||
|
wails3 updater sign -key updater-dist/updater.key updater-dist/AniTrack-linux-amd64 > updater-dist/sign.json
|
||||||
|
# Split the sign output into the sidecar assets the Gitea provider
|
||||||
|
# fetches: <name>.sha512 (sha512sum format) + <name>.sig (base64).
|
||||||
|
python3 - <<'EOF'
|
||||||
|
import base64, binascii, json
|
||||||
|
entries = json.load(open('updater-dist/sign.json'))
|
||||||
|
entry = next(e for e in entries if e['filename'].endswith('AniTrack-linux-amd64'))
|
||||||
|
assert entry['digestAlgo'] == 'sha512', entry
|
||||||
|
assert entry['signatureAlgo'] == 'ed25519ph', entry
|
||||||
|
digest_hex = binascii.hexlify(base64.b64decode(entry['digest'])).decode()
|
||||||
|
open('updater-dist/AniTrack-linux-amd64.sha512', 'w').write(f"{digest_hex} AniTrack-linux-amd64\n")
|
||||||
|
open('updater-dist/AniTrack-linux-amd64.sig', 'w').write(entry['signature'].strip() + '\n')
|
||||||
|
EOF
|
||||||
|
shred -u updater-dist/updater.key
|
||||||
|
ls -la updater-dist/
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
REPO: ${{ gitea.repository }}
|
REPO: ${{ gitea.repository }}
|
||||||
@@ -255,7 +286,8 @@ jobs:
|
|||||||
: "${ARCHIVE:?ARCHIVE missing from packaging step}"
|
: "${ARCHIVE:?ARCHIVE missing from packaging step}"
|
||||||
API="https://git.linuxhg.com/api/v1/repos/${REPO}/releases"
|
API="https://git.linuxhg.com/api/v1/repos/${REPO}/releases"
|
||||||
AUTH="Authorization: token ${TOKEN}"
|
AUTH="Authorization: token ${TOKEN}"
|
||||||
test -f "${ARCHIVE}" || { echo "::error::${ARCHIVE} not found" >&2; exit 1; }
|
for ASSET in "${ARCHIVE}" updater-dist/AniTrack-linux-amd64 updater-dist/AniTrack-linux-amd64.sha512 updater-dist/AniTrack-linux-amd64.sig; do
|
||||||
|
test -f "${ASSET}" || { echo "::error::${ASSET} not found" >&2; exit 1; }
|
||||||
|
|
||||||
RID="$(curl -sS -H "${AUTH}" "${API}/tags/${TAG}" | jq -r '.id // empty')"
|
RID="$(curl -sS -H "${AUTH}" "${API}/tags/${TAG}" | jq -r '.id // empty')"
|
||||||
if [ -z "${RID}" ]; then
|
if [ -z "${RID}" ]; then
|
||||||
@@ -264,18 +296,20 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Replace a same-named asset so re-runs stay idempotent.
|
# Replace a same-named asset so re-runs stay idempotent.
|
||||||
AID="$(curl -sS -H "${AUTH}" "${API}/${RID}/assets" | jq -r --arg n "${ARCHIVE}" '.[] | select(.name==$n) | .id // empty')"
|
ANAME="$(basename "${ASSET}")"
|
||||||
|
AID="$(curl -sS -H "${AUTH}" "${API}/${RID}/assets" | jq -r --arg n "${ANAME}" '.[] | select(.name==$n) | .id // empty')"
|
||||||
if [ -n "${AID}" ]; then
|
if [ -n "${AID}" ]; then
|
||||||
curl -sS -X DELETE -H "${AUTH}" "${API}/${RID}/assets/${AID}" >/dev/null
|
curl -sS -X DELETE -H "${AUTH}" "${API}/${RID}/assets/${AID}" >/dev/null
|
||||||
echo "Deleted existing asset id=${AID} (${ARCHIVE})"
|
echo "Deleted existing asset id=${AID} (${ANAME})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
resp="$(curl -sS -w '\n%{http_code}' -X POST -H "${AUTH}" \
|
resp="$(curl -sS -w '\n%{http_code}' -X POST -H "${AUTH}" \
|
||||||
-F "attachment=@${ARCHIVE}" "${API}/${RID}/assets?name=${ARCHIVE}")"
|
-F "attachment=@${ASSET}" "${API}/${RID}/assets?name=${ANAME}")"
|
||||||
code="$(printf '%s' "${resp}" | tail -n1)"
|
code="$(printf '%s' "${resp}" | tail -n1)"
|
||||||
rbody="$(printf '%s' "${resp}" | sed '$d')"
|
rbody="$(printf '%s' "${resp}" | sed '$d')"
|
||||||
if [ "${code}" -ge 400 ]; then
|
if [ "${code}" -ge 400 ]; then
|
||||||
echo "::error::Asset upload ${code}: ${rbody}" >&2
|
echo "::error::Asset upload ${code}: ${rbody}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Uploaded ${ARCHIVE} to release id=${RID}"
|
echo "Uploaded ${ANAME} to release id=${RID}"
|
||||||
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user