Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262#!/bin/bash# upload-release.sh — Upload a built notepat vmlinuz to Digital Ocean Spaces# Publishes: native-notepat-latest.vmlinuz, .sha256, .version, and updates releases.json## Usage: ./upload-release.sh [vmlinuz_path]# ./upload-release.sh --image [image_path] # Upload template disk image only# Credentials auto-loaded from aesthetic-computer-vault/fedac/native/upload.env
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"IMAGE_ONLY=0if [ "${1:-}" = "--image" ]; then IMAGE_ONLY=1 IMAGE_PATH="${2:-${SCRIPT_DIR}/../build/ac-os.img}" if [ ! -f "$IMAGE_PATH" ]; then echo "Error: image not found at $IMAGE_PATH" >&2 exit 1 fi VMLINUZ="/dev/null" # not used but needed for cred loading belowelse VMLINUZ="${1:-${SCRIPT_DIR}/../build/vmlinuz}" if [ ! -f "$VMLINUZ" ]; then echo "Error: vmlinuz not found at $VMLINUZ" >&2 exit 1 fifi
# Credentials — check env (set by ac-os), session cache, plaintext vault, or GPG decryptif [ -z "${DO_SPACES_KEY:-}" ] || [ -z "${DO_SPACES_SECRET:-}" ]; then # Session cache (written by ac-os load_vault_creds) [ -f "/tmp/.ac-upload-env" ] && { set -a; source "/tmp/.ac-upload-env"; set +a; }fiif [ -z "${DO_SPACES_KEY:-}" ] || [ -z "${DO_SPACES_SECRET:-}" ]; then # Plaintext vault file, or in-repo fallback (oven ships upload.env in-tree). # The oven's clone has no aesthetic-computer-vault/ sibling, so without # this fallback the upload silently failed with "DO_SPACES_KEY not set". for candidate in \ "${SCRIPT_DIR}/../../../aesthetic-computer-vault/fedac/native/upload.env" \ "${SCRIPT_DIR}/../upload.env"; do [ -f "$candidate" ] && { set -a; source "$candidate"; set +a; break; } donefiif [ -z "${DO_SPACES_KEY:-}" ] || [ -z "${DO_SPACES_SECRET:-}" ]; then # GPG decrypt from vault for gpg_file in \ "${SCRIPT_DIR}/../upload.env.gpg" \ "${SCRIPT_DIR}/../../../aesthetic-computer-vault/fedac/native/upload.env.gpg"; do if [ -f "$gpg_file" ]; then echo "[upload] Decrypting $(basename "$gpg_file")..." DECRYPTED=$(gpg --pinentry-mode loopback -d "$gpg_file" 2>/dev/null | grep "=") || true if [ -n "$DECRYPTED" ]; then set -a; eval "$DECRYPTED"; set +a break fi fi donefi
: "${DO_SPACES_KEY:?DO_SPACES_KEY not set}": "${DO_SPACES_SECRET:?DO_SPACES_SECRET not set}"DO_SPACES_BUCKET="${DO_SPACES_BUCKET:-releases-aesthetic-computer}"DO_SPACES_REGION="${DO_SPACES_REGION:-sfo3}"
BASE_URL="https://${DO_SPACES_BUCKET}.${DO_SPACES_REGION}.digitaloceanspaces.com"
# Build version string from git. Dirty/conflicted uploads are blocked by default.GIT_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)GIT_CWD="$SCRIPT_DIR"GIT_NATIVE_PATHS=()if [ -n "$GIT_ROOT" ]; then GIT_CWD="$GIT_ROOT" GIT_NATIVE_PATHS=(fedac/native fedac/nixos)fiGIT_HASH="${AC_GIT_HASH:-$(git -C "$GIT_CWD" rev-parse --short HEAD 2>/dev/null || echo "unknown")}"CONFLICT_FILES=$(git -C "$GIT_CWD" diff --name-only --diff-filter=U -- "${GIT_NATIVE_PATHS[@]}" 2>/dev/null || true)if [ -n "$CONFLICT_FILES" ]; then echo "Error: refusing upload with unresolved merge conflicts:" >&2 echo "$CONFLICT_FILES" >&2 exit 1fiDIRTY_TRACKED=0if ! git -C "$GIT_CWD" diff --quiet HEAD -- "${GIT_NATIVE_PATHS[@]}" 2>/dev/null; then DIRTY_TRACKED=1fiif [ "$DIRTY_TRACKED" -eq 1 ] && [ "${ALLOW_DIRTY_UPLOAD:-0}" != "1" ]; then echo "Error: refusing dirty upload. Commit/stash/reset fedac/native or fedac/nixos changes first." >&2 git -C "$GIT_CWD" status --porcelain --untracked-files=no -- "${GIT_NATIVE_PATHS[@]}" 2>/dev/null >&2 || true echo "Override only for emergencies: ALLOW_DIRTY_UPLOAD=1 ./scripts/upload-release.sh ..." >&2 exit 1fiif [ "$DIRTY_TRACKED" -eq 1 ]; then GIT_HASH="${GIT_HASH}-dirty"fiBUILD_TS="${AC_BUILD_TS:-$(date -u '+%Y-%m-%dT%H:%M')}"# Format must match AC_GIT_HASH "-" AC_BUILD_TS in js-bindings.c / MakefileVERSION="${GIT_HASH}-${BUILD_TS}"
# Compute SHA256SHA256=$(sha256sum "$VMLINUZ" | awk '{print $1}')SIZE=$(stat -c%s "$VMLINUZ")
# Build name: prefer AC_BUILD_NAME (set by oven/Makefile at compile time)# so the uploaded name matches what the kernel displays on boot.# Falls back to MongoDB counter if not set (local builds).BUILD_NAME="${AC_BUILD_NAME:-}"BUILD_NUM=""if [ -z "$BUILD_NAME" ] && command -v node &>/dev/null; then NAME_JSON=$(node "$SCRIPT_DIR/track-build.mjs" next-name 2>/dev/null || echo '{}') BUILD_NAME=$(echo "$NAME_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('name',''))" 2>/dev/null || true) BUILD_NUM=$(echo "$NAME_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('buildNum',''))" 2>/dev/null || true)fiif [ -z "$BUILD_NAME" ]; then BUILD_NAME="local-$(date -u +%s)"fi
echo "Uploading notepat release: $VERSION"echo " name: $BUILD_NAME (#${BUILD_NUM:-?})"echo " vmlinuz: $(du -sh "$VMLINUZ" | cut -f1)"echo " sha256: $SHA256"
# Helper: upload file to DO Spaces via curl (AWS Sig v2)# x-amz-acl must be in CanonicalizedAmzHeaders in the string-to-signdo_upload() { local src="$1" local dest_key="$2" local content_type="${3:-application/octet-stream}" local acl="public-read"
local date_val date_val=$(date -u '+%a, %d %b %Y %H:%M:%S GMT') local md5_val md5_val=$(openssl md5 -binary < "$src" | base64) local sig sig=$(printf 'PUT\n%s\n%s\n%s\nx-amz-acl:%s\n/%s/%s' \ "$md5_val" "$content_type" "$date_val" "$acl" "$DO_SPACES_BUCKET" "$dest_key" \ | openssl dgst -sha1 -hmac "$DO_SPACES_SECRET" -binary | base64)
# Use -T (upload-file) for streaming — avoids loading entire file into memory # (--data-binary loads the whole file into RAM, OOM on 1GB+ files) curl -sf -X PUT \ -H "Date: $date_val" \ -H "Content-Type: $content_type" \ -H "Content-MD5: $md5_val" \ -H "x-amz-acl: $acl" \ -H "Authorization: AWS ${DO_SPACES_KEY}:${sig}" \ -T "$src" \ "${BASE_URL}/${dest_key}" \ && echo " uploaded: $dest_key" \ || { echo " ERROR uploading $dest_key" >&2; return 1; }}
# Write .version and .sha256 to temp filesTMP=$(mktemp -d)trap "rm -rf $TMP" EXIT
# Include build name in version string for display on device# Line 1: version string, Line 2: kernel size in bytesFULL_VERSION="${BUILD_NAME} ${VERSION}"printf '%s\n%s' "$FULL_VERSION" "$SIZE" > "$TMP/version.txt"printf '%s' "$SHA256" > "$TMP/sha256.txt"
# OTA channel prefix (empty for default C build, "cl-" for Common Lisp variant)CHANNEL_PREFIX=""if [ -n "${OTA_CHANNEL:-}" ]; then CHANNEL_PREFIX="${OTA_CHANNEL}-" echo " channel: ${OTA_CHANNEL}"fi
# Image-only mode: upload disk image + version + sha256, then exitif [ "$IMAGE_ONLY" = "1" ]; then IMAGE_SHA256=$(sha256sum "$IMAGE_PATH" | awk '{print $1}') IMAGE_SIZE=$(stat -c%s "$IMAGE_PATH" 2>/dev/null || stat -f%z "$IMAGE_PATH") printf '%s\n%s' "${FULL_VERSION}" "$IMAGE_SIZE" > "$TMP/version.txt" printf '%s' "$IMAGE_SHA256" > "$TMP/sha256.txt" echo "Uploading image: $(du -sh "$IMAGE_PATH" | cut -f1) sha256=${IMAGE_SHA256:0:16}..." do_upload "$TMP/version.txt" "os/${CHANNEL_PREFIX}native-notepat-latest.version" "text/plain" do_upload "$TMP/sha256.txt" "os/${CHANNEL_PREFIX}native-notepat-latest.sha256" "text/plain" do_upload "$IMAGE_PATH" "os/${CHANNEL_PREFIX}native-notepat-latest.img" "application/octet-stream" echo "Image published: ${BASE_URL}/os/${CHANNEL_PREFIX}native-notepat-latest.img" exit 0fi
# Upload files (vmlinuz last — it's large, others are the canary)do_upload "$TMP/version.txt" "os/${CHANNEL_PREFIX}native-notepat-latest.version" "text/plain"do_upload "$TMP/sha256.txt" "os/${CHANNEL_PREFIX}native-notepat-latest.sha256" "text/plain"do_upload "$VMLINUZ" "os/${CHANNEL_PREFIX}native-notepat-latest.vmlinuz" "application/octet-stream"
# Fetch existing releases.json (or start fresh)RELEASES_JSON="$TMP/releases.json"curl -sf "${BASE_URL}/os/${CHANNEL_PREFIX}releases.json" -o "$RELEASES_JSON" 2>/dev/null \ || echo '{"releases":[]}' > "$RELEASES_JSON"
# Append new entry (keep last 50)COMMIT_MSG="${AC_COMMIT_MSG:-$(git -C "$GIT_CWD" log -1 --format="%s" 2>/dev/null || echo "")}"BUILD_HANDLE="${AC_HANDLE:-}"
python3 - "$RELEASES_JSON" "$FULL_VERSION" "$SHA256" "$SIZE" "$GIT_HASH" "$BUILD_TS" "$BUILD_NAME" "$CHANNEL_PREFIX" "$COMMIT_MSG" "$BUILD_HANDLE" <<'PYEOF'import sys, jsonpath, version, sha256, size, git_hash, build_ts, name, channel_prefix, commit_msg, handle = sys.argv[1:]with open(path) as f: data = json.load(f)releases = data.get("releases", [])entry = { "version": version, "name": name, "sha256": sha256, "size": int(size), "git_hash": git_hash, "build_ts": build_ts, "url": f"https://releases-aesthetic-computer.sfo3.digitaloceanspaces.com/os/{channel_prefix}native-notepat-latest.vmlinuz", "commit_msg": commit_msg, "handle": handle,}releases.insert(0, entry)data["releases"] = releases[:50]data["latest"] = versiondata["latest_name"] = namewith open(path, "w") as f: json.dump(data, f, indent=2)PYEOF
do_upload "$RELEASES_JSON" "os/${CHANNEL_PREFIX}releases.json" "application/json"
# Record build in MongoDBif command -v node &>/dev/null; then BUILD_NUM_JSON="${BUILD_NUM:-null}" echo "{\"name\":\"$BUILD_NAME\",\"buildNum\":$BUILD_NUM_JSON,\"version\":\"$FULL_VERSION\",\"sha256\":\"$SHA256\",\"size\":$SIZE,\"git_hash\":\"$GIT_HASH\",\"build_ts\":\"$BUILD_TS\",\"url\":\"${BASE_URL}/os/native-notepat-latest.vmlinuz\"}" \ | node "$SCRIPT_DIR/track-build.mjs" record 2>&1 || truefi
# Upload slim kernel + initramfs for universal Mac/ThinkPad bootSLIM_SIBLING="$(dirname "$VMLINUZ")/vmlinuz-slim"INITRAMFS_SIBLING="$(dirname "$VMLINUZ")/initramfs.cpio.gz"if [ -f "$SLIM_SIBLING" ]; then echo " Uploading slim kernel ($(du -sh "$SLIM_SIBLING" | cut -f1))..." do_upload "$SLIM_SIBLING" "os/${CHANNEL_PREFIX}native-notepat-latest.vmlinuz-slim" "application/octet-stream"fiif [ -f "$INITRAMFS_SIBLING" ]; then echo " Uploading initramfs ($(du -sh "$INITRAMFS_SIBLING" | cut -f1))..." do_upload "$INITRAMFS_SIBLING" "os/${CHANNEL_PREFIX}native-notepat-latest.initramfs.cpio.gz" "application/octet-stream"fi
# Also upload a template disk image if it exists (non-fatal)IMAGE_SIBLING="$(dirname "$VMLINUZ")/ac-os.img"if [ -f "$IMAGE_SIBLING" ]; then echo " Uploading image ($(du -sh "$IMAGE_SIBLING" | cut -f1))..." do_upload "$IMAGE_SIBLING" "os/${CHANNEL_PREFIX}native-notepat-latest.img" "application/octet-stream" || echo " Image upload failed (non-fatal)"fi
echo ""echo "Release published: $BUILD_NAME ($FULL_VERSION)"echo " ${BASE_URL}/os/${CHANNEL_PREFIX}native-notepat-latest.vmlinuz"if [ -f "$SLIM_SIBLING" ]; then echo " ${BASE_URL}/os/${CHANNEL_PREFIX}native-notepat-latest.vmlinuz-slim" echo " ${BASE_URL}/os/${CHANNEL_PREFIX}native-notepat-latest.initramfs.cpio.gz"fiecho " ${BASE_URL}/os/${CHANNEL_PREFIX}releases.json"if [ -f "$IMAGE_SIBLING" ]; then echo " ${BASE_URL}/os/${CHANNEL_PREFIX}native-notepat-latest.img"fi