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>
This commit is contained in:
parent
fd5facc7cf
commit
8351863719
71
scripts/release/lib/release_index.sh
Normal file
71
scripts/release/lib/release_index.sh
Normal file
@ -0,0 +1,71 @@
|
||||
# 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"
|
||||
}
|
||||
186
scripts/release/make_app.sh
Normal file
186
scripts/release/make_app.sh
Normal file
@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Publish a signed LibrePortal APP BUNDLE into the channel's artifact index.
|
||||
#
|
||||
# An app bundle is the drop-in definition folder (containers/<slug>/ — see
|
||||
# docs/contributing/development.md "an app is a self-contained drop-in") packed
|
||||
# into a deterministic tarball and catalogued as a type:"app" envelope in the
|
||||
# same team-signed index hotfixes ride (docs/roadmap/updates-and-distribution.md
|
||||
# §8). Boxes surface it in the App Center as an "Available — Add" card;
|
||||
# `libreportal app add <slug>` verifies + drops the definition in.
|
||||
#
|
||||
# dist/<channel>/payloads/<id>.tar.gz(.minisig) the app definition bundle
|
||||
# dist/<channel>/payloads/icons/<slug>.<ext> sha256-pinned catalog icon
|
||||
# dist/<channel>/index.json(.minisig) the catalog (entry upserted, serial bumped)
|
||||
# laid out exactly like the host serves it, so you can test locally:
|
||||
# ( cd dist && python3 -m http.server 8000 )
|
||||
# LP_RELEASE_BASE_URL=http://localhost:8000 libreportal artifact index
|
||||
#
|
||||
# Usage: scripts/release/make_app.sh <app-slug> [channel] [spec.json]
|
||||
# app-slug folder name under containers/ (e.g. speedtest)
|
||||
# channel stable (default) | edge
|
||||
# spec.json optional overlay for envelope fields the .config can't provide:
|
||||
# { "id", "version", "why", "severity", "supersedes": [],
|
||||
# "publisher", "trust", "applies_when": {"min_lp","max_lp","max_footprint"} }
|
||||
# Defaults: id=app-<slug>, version=auto-bump, severity=tweak,
|
||||
# publisher=libreportal, trust=official. `auto` is ALWAYS false —
|
||||
# apps are never auto-applied; the box enforces the same rule.
|
||||
#
|
||||
# Catalog metadata (title/category/description/long description) comes from the
|
||||
# app's own .config — one source of truth with the App Center generators.
|
||||
#
|
||||
# Env: LP_MINISIGN_SECKEY / LP_MINISIGN_PUBKEY / LP_INDEX_VALID_DAYS — see
|
||||
# lib/release_index.sh.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$REPO_ROOT/scripts/release/lib/release_index.sh"
|
||||
|
||||
SLUG="${1:-}"
|
||||
CHANNEL="${2:-stable}"
|
||||
SPEC="${3:-}"
|
||||
|
||||
[[ -n "$SLUG" ]] || { echo "make_app: usage: make_app.sh <app-slug> [channel] [spec.json]" >&2; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "make_app: jq is required" >&2; exit 1; }
|
||||
|
||||
# Slug contract (matches the box-side bundle validator): lowercase shell-safe
|
||||
# folder name — CFG_<SLUG^^>_* must be a valid shell identifier, so no dashes.
|
||||
[[ "$SLUG" =~ ^[a-z0-9][a-z0-9_]{0,31}$ ]] || { echo "make_app: slug '$SLUG' must match ^[a-z0-9][a-z0-9_]{0,31}\$" >&2; exit 1; }
|
||||
case " template libreportal tools scripts resources " in
|
||||
*" $SLUG "*) echo "make_app: slug '$SLUG' is reserved" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
APP_DIR="$REPO_ROOT/containers/$SLUG"
|
||||
CONFIG_FILE="$APP_DIR/$SLUG.config"
|
||||
[[ -d "$APP_DIR" ]] || { echo "make_app: containers/$SLUG/ not found" >&2; exit 1; }
|
||||
[[ -f "$CONFIG_FILE" ]] || { echo "make_app: $SLUG.config not found — not an app definition" >&2; exit 1; }
|
||||
[[ -f "$APP_DIR/docker-compose.yml" ]] || { echo "make_app: docker-compose.yml not found — not an app definition" >&2; exit 1; }
|
||||
if [[ -n "$SPEC" ]]; then
|
||||
[[ -f "$SPEC" ]] || { echo "make_app: spec file not found: $SPEC" >&2; exit 1; }
|
||||
jq empty "$SPEC" 2>/dev/null || { echo "make_app: $SPEC is not valid JSON" >&2; exit 1; }
|
||||
fi
|
||||
|
||||
# --- catalog metadata from the app's .config (line-wise, never sourced) -------
|
||||
# Same parse the App Center generator uses (webui_config.sh): strip quotes/CR,
|
||||
# trim whitespace; TITLE + CATEGORY are what make a definition recognizable.
|
||||
APP_UPPER="${SLUG^^}"
|
||||
TITLE=""; DESCRIPTION=""; LONG_DESCRIPTION=""; CATEGORY=""
|
||||
while IFS='=' read -r var_name var_value || [[ -n "$var_name" ]]; do
|
||||
[[ -z "$var_name" || "$var_name" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ "$var_name" == CFG_${APP_UPPER}_* ]] || continue
|
||||
var_value="${var_value#\"}"; var_value="${var_value%\"}"
|
||||
var_value="${var_value//$'\r'/}"
|
||||
var_value="${var_value#"${var_value%%[![:space:]]*}"}"
|
||||
var_value="${var_value%"${var_value##*[![:space:]]}"}"
|
||||
case "$var_name" in
|
||||
"CFG_${APP_UPPER}_TITLE") TITLE="$var_value" ;;
|
||||
"CFG_${APP_UPPER}_DESCRIPTION") DESCRIPTION="$var_value" ;;
|
||||
"CFG_${APP_UPPER}_LONG_DESCRIPTION") LONG_DESCRIPTION="$var_value" ;;
|
||||
"CFG_${APP_UPPER}_CATEGORY") CATEGORY="$var_value" ;;
|
||||
esac
|
||||
done < "$CONFIG_FILE"
|
||||
[[ -n "$TITLE" && -n "$CATEGORY" ]] || { echo "make_app: $SLUG.config must set CFG_${APP_UPPER}_TITLE and CFG_${APP_UPPER}_CATEGORY" >&2; exit 1; }
|
||||
|
||||
OUT="$REPO_ROOT/dist/$CHANNEL"
|
||||
PAYDIR="$OUT/payloads"
|
||||
ICONDIR="$PAYDIR/icons"
|
||||
mkdir -p "$PAYDIR" "$ICONDIR"
|
||||
INDEX="$OUT/index.json"
|
||||
|
||||
# --- envelope fields (spec overlay over derived defaults) ---------------------
|
||||
specget() { [[ -n "$SPEC" ]] && jq -r "$1 // empty" "$SPEC" || echo ""; }
|
||||
ID="$(specget '.id')"; ID="${ID:-app-$SLUG}"
|
||||
[[ "$ID" =~ ^[A-Za-z0-9._-]+$ ]] || { echo "make_app: id '$ID' has unsafe characters" >&2; exit 1; }
|
||||
PUBLISHER="$(specget '.publisher')"; PUBLISHER="${PUBLISHER:-libreportal}"
|
||||
TRUST="$(specget '.trust')"; TRUST="${TRUST:-official}"
|
||||
SEVERITY="$(specget '.severity')"; SEVERITY="${SEVERITY:-tweak}"
|
||||
WHY="$(specget '.why')"; WHY="${WHY:-$DESCRIPTION}"; WHY="${WHY:-Adds $TITLE to the app catalog.}"
|
||||
SUPERSEDES="[]"; [[ -n "$SPEC" ]] && SUPERSEDES="$(jq -c '.supersedes // []' "$SPEC")"
|
||||
|
||||
# version: explicit in the spec, else auto — bump the existing catalog entry
|
||||
# only when the packed bundle actually changed (resolved after packing).
|
||||
VERSION_NUM="$(specget '.version')"
|
||||
[[ -z "$VERSION_NUM" || "$VERSION_NUM" =~ ^[0-9]+$ ]] || { echo "make_app: version must be an integer" >&2; exit 1; }
|
||||
|
||||
# applies_when: spec gates kept, but .app is always the slug being packed.
|
||||
APPLIES='{}'; [[ -n "$SPEC" ]] && APPLIES="$(jq -c '.applies_when // {}' "$SPEC")"
|
||||
APPLIES="$(jq -c --arg app "$SLUG" '.app = $app' <<<"$APPLIES")"
|
||||
|
||||
# --- pack the bundle (deterministic for a given commit) ------------------------
|
||||
# Top-level dir inside the tarball == the slug (the box validator requires it).
|
||||
# mtimes are clamped to the app tree's last commit so re-packing an unchanged
|
||||
# app yields byte-identical output; gzip -n keeps the stream timestamp-free.
|
||||
STAGE="$(mktemp -d)"
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
cp -a "$APP_DIR" "$STAGE/$SLUG"
|
||||
EPOCH="$(git -C "$REPO_ROOT" log -1 --format=%ct -- "containers/$SLUG" 2>/dev/null || true)"
|
||||
[[ -n "$EPOCH" ]] || EPOCH="$(date +%s)"
|
||||
PAYLOAD="$PAYDIR/$ID.tar.gz"
|
||||
tar --sort=name --owner=0 --group=0 --numeric-owner --mtime="@$EPOCH" \
|
||||
-cf - -C "$STAGE" "$SLUG" | gzip -n > "$PAYLOAD"
|
||||
SHA="$(sha256sum "$PAYLOAD" | cut -d' ' -f1)"
|
||||
|
||||
SIGNED_NOTE="$(releaseSignedNote)"
|
||||
releaseSignIfKeyed "$PAYLOAD" "libreportal app $ID ($CHANNEL)"
|
||||
|
||||
# auto-version: same bundle bytes keep the published version; new bytes bump it.
|
||||
if [[ -z "$VERSION_NUM" ]]; then
|
||||
VERSION_NUM=1
|
||||
if [[ -f "$INDEX" ]]; then
|
||||
prev="$(jq -r --arg id "$ID" '[.artifacts[]? | select(.id==$id)] | last | .version // 0' "$INDEX")"
|
||||
prev_sha="$(jq -r --arg id "$ID" '[.artifacts[]? | select(.id==$id)] | last | .payload.sha256 // ""' "$INDEX")"
|
||||
if (( prev > 0 )); then
|
||||
VERSION_NUM="$prev"
|
||||
[[ "$prev_sha" != "$SHA" ]] && VERSION_NUM=$(( prev + 1 ))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- catalog icon (sha256-pinned; the box scan stores it locally) --------------
|
||||
META_ICON=""; META_ICON_SHA=""
|
||||
for ext in svg png; do
|
||||
if [[ -f "$APP_DIR/$SLUG.$ext" ]]; then
|
||||
cp "$APP_DIR/$SLUG.$ext" "$ICONDIR/$SLUG.$ext"
|
||||
META_ICON="$CHANNEL/payloads/icons/$SLUG.$ext"
|
||||
META_ICON_SHA="$(sha256sum "$ICONDIR/$SLUG.$ext" | cut -d' ' -f1)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
META="$(jq -cn \
|
||||
--arg category "$CATEGORY" --arg description "$DESCRIPTION" \
|
||||
--arg long_description "$LONG_DESCRIPTION" \
|
||||
--arg icon "$META_ICON" --arg icon_sha "$META_ICON_SHA" '
|
||||
{category:$category, description:$description, long_description:$long_description}
|
||||
+ (if $icon != "" then {icon:$icon, icon_sha256:$icon_sha} else {} end)')"
|
||||
|
||||
# --- build the envelope (§8.1 shape; type:"app", payload.kind:"bundle") -------
|
||||
envelope="$(jq -cn \
|
||||
--arg id "$ID" --argjson version "$VERSION_NUM" --argjson supersedes "$SUPERSEDES" \
|
||||
--arg publisher "$PUBLISHER" --arg trust "$TRUST" --arg severity "$SEVERITY" \
|
||||
--arg title "$TITLE" --arg why "$WHY" --argjson applies "$APPLIES" \
|
||||
--arg url "$CHANNEL/payloads/$ID.tar.gz" --arg sha "$SHA" \
|
||||
--arg sig "$CHANNEL/payloads/$ID.tar.gz.minisig" --argjson meta "$META" '
|
||||
{ id:$id, type:"app", version:$version, supersedes:$supersedes,
|
||||
reversible:true, publisher:$publisher, trust:$trust, severity:$severity,
|
||||
auto:false, title:$title, why:$why, applies_when:$applies,
|
||||
payload:{kind:"bundle", url:$url, sha256:$sha, sig:$sig}, meta:$meta }')"
|
||||
|
||||
# --- the publisher's public key for the index publishers map -------------------
|
||||
PUBKEY_FILE="${LP_MINISIGN_PUBKEY:-$REPO_ROOT/libreportal.pub}"
|
||||
PUBKEY="$(releaseReadPubkey "$PUBKEY_FILE")"
|
||||
|
||||
# --- upsert into the index (serial bump + freshness), then sign it -------------
|
||||
serial="$(releaseIndexUpsert "$envelope" "$INDEX" "$PUBLISHER" "$PUBKEY")"
|
||||
releaseSignIfKeyed "$INDEX" "libreportal artifact index serial $serial ($CHANNEL)"
|
||||
|
||||
echo "✓ $PAYLOAD ($SHA)"
|
||||
[[ -n "$META_ICON" ]] && echo "✓ $ICONDIR/${META_ICON##*/} (pinned)"
|
||||
echo "✓ $INDEX (serial=$serial, entries=$(jq '.artifacts | length' "$INDEX"))"
|
||||
echo " $ID: $TITLE [$CATEGORY] v$VERSION_NUM"
|
||||
echo " bundle + index: $SIGNED_NOTE"
|
||||
[[ -z "$PUBKEY" ]] && echo " ⚠ no publisher pubkey found ($PUBKEY_FILE) — publishers.$PUBLISHER.key is empty"
|
||||
echo ""
|
||||
echo "Test locally:"
|
||||
echo " ( cd '$REPO_ROOT/dist' && python3 -m http.server 8000 )"
|
||||
echo " LP_RELEASE_BASE_URL=http://localhost:8000 libreportal artifact index"
|
||||
@ -16,13 +16,8 @@
|
||||
# spec.json the hotfix envelope + an embedded "ops" array (see below)
|
||||
# channel stable (default) | edge
|
||||
#
|
||||
# Env:
|
||||
# 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). The install verifies the index against
|
||||
# the ROOT-OWNED footprint key, so this must be that key.
|
||||
# LP_HOTFIX_VALID_DAYS freshness window written to valid_until (default 30).
|
||||
# Env: LP_MINISIGN_SECKEY / LP_MINISIGN_PUBKEY / LP_INDEX_VALID_DAYS — see
|
||||
# lib/release_index.sh (LP_HOTFIX_VALID_DAYS still honoured as a legacy alias).
|
||||
#
|
||||
# Spec shape (the maintainer writes this):
|
||||
# {
|
||||
@ -44,6 +39,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$REPO_ROOT/scripts/release/lib/release_index.sh"
|
||||
SPEC="${1:-}"
|
||||
CHANNEL="${2:-stable}"
|
||||
[[ -n "$SPEC" && -f "$SPEC" ]] || { echo "make_hotfix: usage: make_hotfix.sh <spec.json> [channel]" >&2; exit 1; }
|
||||
@ -89,19 +85,12 @@ jq -n --argjson ops "$new_ops" '{schema:1, ops:$ops}' > "$PAYLOAD"
|
||||
SHA="$(sha256sum "$PAYLOAD" | cut -d' ' -f1)"
|
||||
|
||||
# --- sign the payload (if a key is configured) --------------------------------
|
||||
SIGNED_NOTE="unsigned (set LP_MINISIGN_SECKEY to sign — required for a real publish)"
|
||||
if [[ -n "${LP_MINISIGN_SECKEY:-}" ]]; then
|
||||
command -v minisign >/dev/null 2>&1 || { echo "make_hotfix: LP_MINISIGN_SECKEY set but 'minisign' isn't installed" >&2; exit 1; }
|
||||
minisign -Sm "$PAYLOAD" -s "$LP_MINISIGN_SECKEY" -t "libreportal hotfix $ID ($CHANNEL)" >/dev/null
|
||||
SIGNED_NOTE="signed"
|
||||
fi
|
||||
SIGNED_NOTE="$(releaseSignedNote)"
|
||||
releaseSignIfKeyed "$PAYLOAD" "libreportal hotfix $ID ($CHANNEL)"
|
||||
|
||||
# --- the publisher's public key for the index publishers map ------------------
|
||||
PUBKEY_FILE="${LP_MINISIGN_PUBKEY:-$REPO_ROOT/libreportal.pub}"
|
||||
PUBKEY=""
|
||||
if [[ -f "$PUBKEY_FILE" ]]; then
|
||||
PUBKEY="$(grep -v -i '^untrusted comment' "$PUBKEY_FILE" | head -1 | tr -d ' \t\r\n')"
|
||||
fi
|
||||
PUBKEY="$(releaseReadPubkey "$PUBKEY_FILE")"
|
||||
PUBLISHER="$(jq -r '.publisher // "libreportal"' "$SPEC")"
|
||||
|
||||
# --- build the envelope (spec minus ops, plus a signed payload ref) -----------
|
||||
@ -109,34 +98,10 @@ envelope="$(jq -c \
|
||||
--arg url "$CHANNEL/payloads/$ID.json" --arg sha "$SHA" --arg sig "$CHANNEL/payloads/$ID.json.minisig" \
|
||||
'del(.ops) | .payload = {kind:"ops", url:$url, sha256:$sha, sig:$sig}' "$SPEC")"
|
||||
|
||||
# --- upsert into the index, bump serial, refresh freshness --------------------
|
||||
# --- upsert into the index (serial bump + freshness), then sign it ------------
|
||||
INDEX="$OUT/index.json"
|
||||
cur='{"schema":1,"index_serial":0,"publishers":{},"artifacts":[]}'
|
||||
[[ -f "$INDEX" ]] && cur="$(cat "$INDEX")"
|
||||
serial=$(( $(jq -r '.index_serial // 0' <<<"$cur") + 1 ))
|
||||
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"
|
||||
|
||||
# --- sign the index -----------------------------------------------------------
|
||||
if [[ -n "${LP_MINISIGN_SECKEY:-}" ]]; then
|
||||
minisign -Sm "$INDEX" -s "$LP_MINISIGN_SECKEY" -t "libreportal artifact index serial $serial ($CHANNEL)" >/dev/null
|
||||
fi
|
||||
serial="$(releaseIndexUpsert "$envelope" "$INDEX" "$PUBLISHER" "$PUBKEY")"
|
||||
releaseSignIfKeyed "$INDEX" "libreportal artifact index serial $serial ($CHANNEL)"
|
||||
|
||||
echo "✓ $PAYLOAD ($SHA)"
|
||||
echo "✓ $INDEX (serial=$serial, entries=$(jq '.artifacts | length' "$INDEX"))"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user