|
| 1 | +#!/bin/bash |
| 2 | +# verify-release.sh <version> — Post-Release-Gate fuer den Nextcloud App Store. |
| 3 | +# |
| 4 | +# Maschinell statt manuell: faengt die Fehlerklassen, die App-Store-201 + integrity:check |
| 5 | +# NICHT bemerken und die in der Vergangenheit zu nicht-installierbaren Releases gefuehrt haben: |
| 6 | +# • Draft-Falle (#23 v4.4.6, #26 v4.4.7): GitHub-Release blieb Draft → kanonische Tag-URL 404, |
| 7 | +# obwohl der App-Store-Eintrag (201) darauf zeigt → `occ app:install` schlaegt fuer ALLE User fehl. |
| 8 | +# • Signatur-Mismatch: falscher/zweiter Build publiziert → NC verwirft die Signatur beim Install. |
| 9 | +# • Packaging-Bug (v4.4.5): `data/` fehlt im Tarball → App laedt, Features ohne Daten. |
| 10 | +# • Versions-Drift: info.xml != Release-Tag. |
| 11 | +# |
| 12 | +# Nach dem App-Store-POST (Checkliste Schritt 9) laufen lassen. Exit 0 = installierbar. |
| 13 | +# |
| 14 | +# Usage: ./scripts/verify-release.sh 4.4.7 |
| 15 | +set -eo pipefail |
| 16 | + |
| 17 | +VERSION="${1:?Usage: verify-release.sh <version, z.B. 4.4.7>}" |
| 18 | +REPO="${LEARNING_REPO:-andremadstop/learning-nc}" |
| 19 | +TAG="v${VERSION}" |
| 20 | +URL="https://github.com/${REPO}/releases/download/${TAG}/learning-${VERSION}.tar.gz" |
| 21 | +PLATFORM="${NC_PLATFORM:-33.0.0}" # App-Store-Feed-Version, den NC-Clients abfragen |
| 22 | + |
| 23 | +fail() { echo "✗ $1" >&2; exit 1; } |
| 24 | + |
| 25 | +# Zertifikat finden (certs/ ist gitignored; Fallback auf globalen NC-Cert-Store) |
| 26 | +CERT="${LEARNING_CERT:-}" |
| 27 | +if [ -z "$CERT" ]; then |
| 28 | + for c in "$(dirname "$0")/../certs/learning.crt" "$HOME/.nextcloud/certificates/learning.crt"; do |
| 29 | + [ -f "$c" ] && CERT="$c" && break |
| 30 | + done |
| 31 | +fi |
| 32 | + |
| 33 | +TMP="$(mktemp -d)" |
| 34 | +trap 'rm -rf "$TMP"' EXIT |
| 35 | + |
| 36 | +echo "=== verify-release v$VERSION ($REPO) ===" |
| 37 | + |
| 38 | +# 1. Genau EIN non-draft Release fuer den Tag (Draft-Leichen aufdecken) |
| 39 | +mapfile -t DRAFTS < <(gh api "repos/$REPO/releases" --jq ".[] | select(.tag_name==\"$TAG\") | .draft") |
| 40 | +[ "${#DRAFTS[@]}" -ge 1 ] || fail "Kein GitHub-Release mit Tag $TAG gefunden" |
| 41 | +[ "${#DRAFTS[@]}" -eq 1 ] || fail "${#DRAFTS[@]} Releases mit Tag $TAG (erwartet 1) — verwaiste Draft(s) loeschen" |
| 42 | +[ "${DRAFTS[0]}" = "false" ] || fail "GitHub-Release $TAG ist noch DRAFT → publizieren: gh release edit $TAG --draft=false --latest" |
| 43 | +echo "✓ GitHub-Release $TAG: non-draft, genau 1 Eintrag" |
| 44 | + |
| 45 | +# 2. Kanonische Tag-URL liefert nach Redirect HTTP 200 (Draft-Falle gibt hier 404) |
| 46 | +CODE="$(curl -sIL -o /dev/null -w '%{http_code}' "$URL")" |
| 47 | +[ "$CODE" = "200" ] || fail "Kanonische Download-URL liefert HTTP $CODE (erwartet 200) — Draft-Falle? $URL" |
| 48 | +echo "✓ Kanonische URL: HTTP 200" |
| 49 | + |
| 50 | +# 3. Publizierten Tarball laden |
| 51 | +curl -sL -o "$TMP/app.tar.gz" "$URL" |
| 52 | +[ -s "$TMP/app.tar.gz" ] || fail "Tarball-Download leer" |
| 53 | + |
| 54 | +# 4. App-Store-Feed: Version vorhanden, download == kanonische URL, Signatur extrahieren |
| 55 | +curl -s "https://apps.nextcloud.com/api/v1/platform/${PLATFORM}/apps.json" -o "$TMP/feed.json" |
| 56 | +python3 - "$VERSION" "$TMP/feed.json" "$TMP/as.b64" "$URL" <<'PY' || fail "App-Store-Feed-Check fehlgeschlagen" |
| 57 | +import json, sys |
| 58 | +ver, feed, out, url = sys.argv[1:5] |
| 59 | +try: |
| 60 | + data = json.load(open(feed)) |
| 61 | +except Exception as e: |
| 62 | + sys.exit(f"App-Store-Feed nicht parsebar: {e}") |
| 63 | +app = [a for a in data if a.get('id') == 'learning'] |
| 64 | +if not app: |
| 65 | + sys.exit("App 'learning' nicht im App-Store-Feed") |
| 66 | +rel = [r for r in app[0].get('releases', []) if r['version'] == ver] |
| 67 | +if not rel: |
| 68 | + sys.exit(f"v{ver} nicht im App-Store-Feed (POST vergessen?)") |
| 69 | +r = rel[0] |
| 70 | +if r.get('download') != url: |
| 71 | + sys.exit(f"App-Store download-URL != kanonische URL:\n store: {r.get('download')}\n canon: {url}") |
| 72 | +sig = r.get('signature') or '' |
| 73 | +if not sig: |
| 74 | + sys.exit(f"Keine Signatur fuer v{ver} im App-Store-Feed") |
| 75 | +open(out, 'w').write(sig) |
| 76 | +PY |
| 77 | +echo "✓ App-Store-Feed: v$VERSION gelistet, download-URL == kanonische URL" |
| 78 | + |
| 79 | +# 5. App-Store-Signatur verifiziert gegen den PUBLIZIERTEN Tarball (byte-genau) |
| 80 | +[ -n "$CERT" ] || fail "Kein Zertifikat gefunden (certs/learning.crt oder ~/.nextcloud/certificates/learning.crt)" |
| 81 | +openssl base64 -d -A < "$TMP/as.b64" > "$TMP/as.bin" |
| 82 | +openssl x509 -in "$CERT" -pubkey -noout > "$TMP/pub.pem" 2>/dev/null \ |
| 83 | + || fail "Public Key konnte nicht aus $CERT extrahiert werden" |
| 84 | +openssl dgst -sha512 -verify "$TMP/pub.pem" -signature "$TMP/as.bin" "$TMP/app.tar.gz" >/dev/null 2>&1 \ |
| 85 | + || fail "App-Store-Signatur passt NICHT zum publizierten Tarball — falscher Build publiziert? (NC verwirft das beim Install)" |
| 86 | +echo "✓ App-Store-Signatur == publizierter Tarball (Verified OK)" |
| 87 | + |
| 88 | +# 6. Packaging-Gates: data/ vorhanden + info.xml-Version == Tag |
| 89 | +tar -xzf "$TMP/app.tar.gz" -C "$TMP" |
| 90 | +DATA_FILES="$(find "$TMP/learning/data" -type f 2>/dev/null | wc -l)" |
| 91 | +[ "$DATA_FILES" -gt 0 ] || fail "Tarball enthaelt KEIN data/ (Packaging-Bug wie v4.4.5)" |
| 92 | +INFO_VER="$(grep -oE '<version>[0-9.]+</version>' "$TMP/learning/appinfo/info.xml" | head -1 | grep -oE '[0-9.]+')" |
| 93 | +[ "$INFO_VER" = "$VERSION" ] || fail "info.xml-Version ($INFO_VER) != $VERSION" |
| 94 | +echo "✓ Packaging: data/ vorhanden ($DATA_FILES files), info.xml == $VERSION" |
| 95 | + |
| 96 | +echo "" |
| 97 | +echo "✅ Release v$VERSION ist installierbar — alle Gates gruen." |
0 commit comments