LibrePortal/scripts/release/lib/release_index.sh
librelad 8351863719 feat(release): make_app.sh — publish app definition bundles to the artifact index
The marketplace publisher tool: packs containers/<slug>/ (the drop-in app
contract, unchanged) into a deterministic tarball (commit-clamped mtimes +
gzip -n; unchanged apps repack byte-identical and keep their published
version), signs it, copies a sha256-pinned catalog icon, and upserts a
type:"app" / payload.kind:"bundle" envelope into the same team-signed
index hotfixes ride. Catalog metadata (title/category/descriptions) is
parsed line-wise from the app's own .config — one source of truth with the
App Center generators. auto:false is hard-forced: apps never auto-apply.

The index-upsert/serial/freshness/publishers-map and signing logic is
factored out of make_hotfix.sh into lib/release_index.sh, shared by both
tools (make_hotfix.sh behavior preserved; regression-tested alongside an
app entry: serial bump, both payload kinds coexist, valid_until refreshed).
LP_INDEX_VALID_DAYS is the shared freshness knob (LP_HOTFIX_VALID_DAYS
kept as a legacy alias).

Verified: speedtest publish → deterministic repack (identical sha256) →
served via local http.server → real lpFetchIndex/accessors harness 10/10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 20:45:02 +01:00

72 lines
3.6 KiB
Bash

# Shared publisher-side helpers for the artifact index (sourced by
# make_hotfix.sh / make_app.sh — release tooling only, never the CLI tree).
#
# The index is the team-signed catalog every install fetches + verifies
# (scripts/source/artifacts.sh, docs/roadmap/updates-and-distribution.md §8).
# These helpers own the parts every publisher tool shares: signing when a key
# is configured, reading the publisher pubkey for the publishers map, and the
# upsert-entry + bump-serial + refresh-freshness index rewrite.
#
# Env (shared by all publisher tools):
# LP_MINISIGN_SECKEY offline secret key — set to SIGN (required for a real
# publish; unset = unsigned, local-testing only).
# LP_MINISIGN_PUBKEY public key file for the publishers map (default: repo
# libreportal.pub). Installs verify against the
# ROOT-OWNED footprint key, so this must be that key.
# LP_INDEX_VALID_DAYS freshness window written to valid_until (default 30;
# LP_HOTFIX_VALID_DAYS honoured as a legacy alias).
# releaseSignIfKeyed <file> <trusted-comment> — minisign-sign in place when a
# key is configured; a no-op otherwise (unsigned local-testing mode).
releaseSignIfKeyed() {
[[ -n "${LP_MINISIGN_SECKEY:-}" ]] || return 0
command -v minisign >/dev/null 2>&1 || { echo "release: LP_MINISIGN_SECKEY set but 'minisign' isn't installed" >&2; exit 1; }
minisign -Sm "$1" -s "$LP_MINISIGN_SECKEY" -t "$2" >/dev/null
}
# releaseSignedNote — the human-readable signing status for the summary line.
releaseSignedNote() {
if [[ -n "${LP_MINISIGN_SECKEY:-}" ]]; then echo "signed"; else
echo "unsigned (set LP_MINISIGN_SECKEY to sign — required for a real publish)"
fi
}
# releaseReadPubkey <pubkey-file> — echo the bare key line for the publishers
# map ("" when the file is missing; callers warn, signing still proceeds).
releaseReadPubkey() {
[[ -f "${1:-}" ]] || { echo ""; return 0; }
grep -v -i '^untrusted comment' "$1" | head -1 | tr -d ' \t\r\n'
}
# releaseIndexUpsert <envelope-json> <index-file> <publisher> <pubkey>
# Load-or-init the index, replace any same-id entry with the envelope, bump
# index_serial, refresh valid_until/generated_at, and upsert the publisher
# into the publishers map (role: official for "libreportal", else community).
# Echoes the new serial (callers sign the index with it in the comment).
releaseIndexUpsert() {
local envelope="$1" index="$2" publisher="$3" pubkey="$4"
local cur serial days valid_until now default_role
cur='{"schema":1,"index_serial":0,"publishers":{},"artifacts":[]}'
[[ -f "$index" ]] && cur="$(cat "$index")"
serial=$(( $(jq -r '.index_serial // 0' <<<"$cur") + 1 ))
days="${LP_INDEX_VALID_DAYS:-${LP_HOTFIX_VALID_DAYS:-30}}"
valid_until=$(( $(date +%s) + days * 86400 ))
now="$(date -Iseconds 2>/dev/null || date)"
default_role="community"; [[ "$publisher" == "libreportal" ]] && default_role="official"
jq \
--argjson env "$envelope" \
--arg pub "$publisher" --arg pubkey "$pubkey" --arg role "$default_role" \
--argjson serial "$serial" --argjson vu "$valid_until" --arg now "$now" '
.schema = 1
| .index_serial = $serial
| .valid_until = $vu
| .generated_at = $now
| .publishers = (.publishers // {})
| .publishers[$pub] = ((.publishers[$pub] // {display: $pub, role: $role})
+ (if $pubkey != "" then {key: $pubkey} else {} end))
| .artifacts = (((.artifacts // []) | map(select(.id != $env.id))) + [$env])
' <<<"$cur" > "$index"
echo "$serial"
}