Menu
BifrOSt
publicLatest change 396b54c4e84791c369e5492a4639fe02b58b56b6 - Add AkurAI Build CI/CD pipeline for ISO delivery by Ólafur Búi Ólafsson
#!/usr/bin/env bash
#
# publish-iso.sh - Publish a qualified BifrOSt ISO to the AkurAI Build public
# download endpoint and refresh the bifrost.olibuijr.com landing page.
#
# Invoked by the approval-gated `publish` job of .akurai.yml, but also runnable
# by hand on Titan. It performs the delivery side of an ISO release:
#
# 1. Install the ISO + a SHA-256 sidecar into the AkurAI Build release store
# ($AKURAI_BUILD_HOSTED/releases/BifrOSt/), which the controller serves at
# https://akurai-build.olibuijr.com/downloads/BifrOSt/<name>. Both the
# versioned name (bifrost-<version>-x86_64.iso) and a stable alias
# (bifrost-x86_64.iso) are published, each with its own .sha256.
# 2. Verify the public download endpoint returns 200 with the expected digest.
# 3. Update the landing page's runtime content (BifrOSt-Web data/content.db):
# the version string and download links in the home/is and home/en rows.
# The server re-reads content.db per request, so no rebuild is needed.
# 4. Verify the public page advertises the new version.
#
# ISOs are large (multiple GiB) and MUST NOT travel through the pipeline
# `artifacts:` mechanism (512 MiB/run cap); the package job stages the ISO on
# Titan's filesystem and this script reads it from there.
#
set -Eeuo pipefail
REPO="BifrOSt"
VERSION=""
ISO=""
RELEASES_DIR="${AKURAI_BUILD_HOSTED:-$HOME/.local/share/akurai-build}/releases/$REPO"
WEB_DB="/opt/bifrost-web/current/data/content.db"
BASE_URL="https://akurai-build.olibuijr.com/downloads/$REPO"
PAGE_URL="https://bifrost.olibuijr.com/"
usage() {
cat <<'USAGE'
Usage: publish-iso.sh --version X.Y.0 --iso PATH [options]
Publish a qualified BifrOSt ISO to the AkurAI Build downloads endpoint and
refresh the bifrost.olibuijr.com landing page.
Options:
--version X.Y.0 Release version (must match the ISO); required
--iso PATH Path to the qualified ISO on Titan; required
--releases-dir DIR AkurAI Build release store (default: $AKURAI_BUILD_HOSTED/releases/BifrOSt)
--web-db PATH Deployed BifrOSt-Web content.db (default: /opt/bifrost-web/current/data/content.db)
--base-url URL Public downloads base (default: https://akurai-build.olibuijr.com/downloads/BifrOSt)
--skip-page Publish the ISO only; do not touch the landing page
-h, --help Show this help
USAGE
}
die() { printf 'publish-iso: %s\n' "$*" >&2; exit 1; }
log() { printf '\n==> %s\n' "$*"; }
SKIP_PAGE=0
while (( $# )); do
case "$1" in
--version) [[ $# -ge 2 ]] || die "$1 needs a value"; VERSION="$2"; shift 2 ;;
--iso) [[ $# -ge 2 ]] || die "$1 needs a value"; ISO="$2"; shift 2 ;;
--releases-dir) [[ $# -ge 2 ]] || die "$1 needs a value"; RELEASES_DIR="$2"; shift 2 ;;
--web-db) [[ $# -ge 2 ]] || die "$1 needs a value"; WEB_DB="$2"; shift 2 ;;
--base-url) [[ $# -ge 2 ]] || die "$1 needs a value"; BASE_URL="$2"; shift 2 ;;
--skip-page) SKIP_PAGE=1; shift ;;
-h|--help) usage; exit 0 ;;
*) usage >&2; die "unknown argument: $1" ;;
esac
done
[[ -n "$VERSION" ]] || { usage >&2; die "--version is required"; }
[[ "$VERSION" == *.*.0 ]] || die "refusing to publish $VERSION: ISO releases are minor versions only (X.Y.0)"
[[ -n "$ISO" && -f "$ISO" ]] || die "ISO not found: ${ISO:-<unset>}"
NAME="bifrost-${VERSION}-x86_64.iso"
ALIAS="bifrost-x86_64.iso"
log "Computing source digest"
DIGEST="$(sha256sum "$ISO" | cut -d' ' -f1)"
printf ' %s %s\n' "$DIGEST" "$NAME"
log "Installing into release store: $RELEASES_DIR"
mkdir -p "$RELEASES_DIR"
tmp="$RELEASES_DIR/.$NAME.tmp.$$"
install -m 0644 "$ISO" "$tmp"
[[ "$(sha256sum "$tmp" | cut -d' ' -f1)" == "$DIGEST" ]] || { rm -f "$tmp"; die "copy digest mismatch"; }
mv -f "$tmp" "$RELEASES_DIR/$NAME"
printf '%s %s\n' "$DIGEST" "$NAME" > "$RELEASES_DIR/$NAME.sha256"
ln -sf "$NAME" "$RELEASES_DIR/$ALIAS"
printf '%s %s\n' "$DIGEST" "$ALIAS" > "$RELEASES_DIR/$ALIAS.sha256"
log "Published $NAME (+ stable alias $ALIAS)"
log "Verifying public download endpoint"
served_len="$(curl -fsIL "$BASE_URL/$NAME" | awk 'tolower($1)=="content-length:"{print $2}' | tr -d '\r' | tail -1)"
local_len="$(stat -c %s "$RELEASES_DIR/$NAME")"
[[ -n "$served_len" && "$served_len" == "$local_len" ]] || die "served content-length ($served_len) != local ($local_len)"
served_sha="$(curl -fsSL "$BASE_URL/$NAME.sha256" | cut -d' ' -f1)"
[[ "$served_sha" == "$DIGEST" ]] || die "served sha256 ($served_sha) != built ($DIGEST)"
log "Download endpoint serves $NAME ($served_len bytes, sha $DIGEST)"
if (( SKIP_PAGE )); then
log "Skipping landing-page update (--skip-page)"
exit 0
fi
if [[ ! -f "$WEB_DB" ]]; then
printf 'publish-iso: WARNING landing-page content.db not found at %s; ISO published but page not refreshed.\n' "$WEB_DB" >&2
exit 0
fi
log "Refreshing landing page content: $WEB_DB"
# content.db is root-owned under /opt; the server re-reads it per request.
sudo VERSION="$VERSION" NAME="$NAME" BASE_URL="$BASE_URL" WEB_DB="$WEB_DB" python3 - <<'PY'
import json, os, re, sqlite3
version = os.environ["VERSION"]
name = os.environ["NAME"]
url = f'{os.environ["BASE_URL"]}/{name}'
db = os.environ["WEB_DB"]
conn = sqlite3.connect(db)
try:
changed = 0
for (locale,) in conn.execute("SELECT locale FROM page_content WHERE page='home'").fetchall():
(payload,) = conn.execute(
"SELECT payload FROM page_content WHERE page='home' AND locale=?", (locale,)
).fetchone()
data = json.loads(payload)
# Old version, taken from the release-facts version line (value like 'v0.2.2').
old = None
for fact in data.get("release_facts", []):
m = re.fullmatch(r"v?(\d+\.\d+\.\d+)", str(fact.get("value", "")))
if m:
old = m.group(1)
fact["value"] = f"v{version}"
for section in ("hero", "download"):
block = data.get(section, {})
for key, val in list(block.items()):
if not isinstance(val, str):
continue
new = val.replace(f"bifrost-{old}-x86_64.iso", name) if old else val
if old:
new = re.sub(rf"v{re.escape(old)}\b", f"v{version}", new)
if key.endswith("href") and ("downloads/" in val and name.split('-')[0] in val):
new = url
block[key] = new
conn.execute(
"UPDATE page_content SET payload=?, updated_at=datetime('now') WHERE page='home' AND locale=?",
(json.dumps(data, ensure_ascii=False), locale),
)
changed += 1
conn.commit()
print(f" updated {changed} locale row(s) to v{version}")
finally:
conn.close()
PY
log "Verifying public page advertises v$VERSION"
if curl -fsSL "$PAGE_URL" | grep -q "$NAME"; then
log "Landing page now links $NAME"
else
printf 'publish-iso: WARNING page did not show %s yet (CDN/cache?); verify %s manually.\n' "$NAME" "$PAGE_URL" >&2
fi