chore(release): archive ISO build recipes, tarball-only releases

Releases no longer ship as bootable ISOs. Archipelago updates are
distributed as the backend binary plus a frontend tarball referenced by
releases/manifest.json. Nodes OTA-update via scripts/self-update.sh.

Filebrowser and AIUI remain bundled inside the frontend tarball and
deployed atomically, verified present in v1.7.43-alpha release artifact
(189 AIUI files, filebrowser-client bundle).

Archived under image-recipe/_archived/ (resurrectable if ISO distribution
is reintroduced):
  - build-auto-installer-iso.sh
  - build-unbundled-iso.sh
  - test-iso-qemu.sh
  - scripts/convert-iso-to-disk.sh
  - BUILD-ISO-STATUS.md, ISO-BUILD-CHECKLIST.md
  - branding/isohdpfx.bin
  - .gitea/workflows/build-iso-dev.yml

Updated release process docs to drop ISO references:
  - scripts/create-release.sh (next-steps text)
  - docs/BETA-RELEASE-CHECKLIST.md
  - docs/hotfix-process.md
  - README.md
This commit is contained in:
archipelago
2026-04-23 15:36:00 -04:00
parent 0bd4e49a8c
commit ed73e4709b
13 changed files with 58 additions and 16 deletions

View File

@@ -0,0 +1,352 @@
name: Build Archipelago ISO (dev)
on:
push:
branches: [main, dev-iso]
workflow_dispatch:
jobs:
build-iso:
runs-on: iso-builder
timeout-minutes: 60
steps:
- name: Checkout
run: |
# Direct fetch + sync (actions/checkout token is broken on this Gitea)
REPO_DIR="$HOME/archy"
cd "$REPO_DIR" && git fetch origin main && git reset --hard origin/main
echo "=== Source at commit: $(git log --oneline -1) ==="
rsync -a --delete \
--exclude '.git' --exclude 'node_modules' --exclude 'target' \
--exclude 'image-recipe/build' --exclude 'image-recipe/results' \
--exclude 'web/dist' \
"$REPO_DIR/" "$GITHUB_WORKSPACE/"
cd "$GITHUB_WORKSPACE"
echo "=== Workspace version: $(grep '^version' core/archipelago/Cargo.toml) ==="
[ -f "scripts/first-boot-containers.sh" ] && echo " first-boot-containers.sh: PRESENT" || echo " first-boot-containers.sh: MISSING"
grep -q 'network-alias' scripts/first-boot-containers.sh 2>/dev/null && echo " network-alias fix: PRESENT" || echo " network-alias fix: MISSING"
- name: Install ISO build dependencies
run: |
# Skip apt if packages already installed (persistent runner)
if dpkg -s debootstrap squashfs-tools xorriso isolinux syslinux-common mtools \
grub-efi-amd64-bin grub-pc-bin grub-common musl-tools >/dev/null 2>&1; then
echo "ISO build deps already installed, skipping apt"
else
sudo apt-get update -qq
sudo apt-get install -y -qq \
debootstrap squashfs-tools xorriso \
isolinux syslinux-common mtools \
grub-efi-amd64-bin grub-pc-bin grub-common \
musl-tools
fi
# Ensure musl Rust target is available
source $HOME/.cargo/env 2>/dev/null || true
rustup target add x86_64-unknown-linux-musl 2>/dev/null || true
- name: Build backend (incremental, musl static)
run: |
source $HOME/.cargo/env 2>/dev/null || true
# Build in persistent repo dir to reuse target/ cache
cd "$HOME/archy"
export GIT_HASH=$(git rev-parse --short HEAD)
# Static musl build for portability — ensures binary runs regardless
# of glibc version differences between build host and ISO rootfs.
cargo build --release --target x86_64-unknown-linux-musl --manifest-path core/Cargo.toml
# Copy binary to workspace for downstream steps
mkdir -p "$GITHUB_WORKSPACE/core/target/release"
cp core/target/x86_64-unknown-linux-musl/release/archipelago "$GITHUB_WORKSPACE/core/target/release/"
- name: Build frontend
run: |
source $HOME/.nvm/nvm.sh 2>/dev/null || true
cd neode-ui && npm ci && npm run build
- name: Type check frontend
run: |
source $HOME/.nvm/nvm.sh 2>/dev/null || true
cd neode-ui && npx vue-tsc -b --noEmit
- name: Run frontend tests
run: |
source $HOME/.nvm/nvm.sh 2>/dev/null || true
cd neode-ui && npx vitest run
- name: Include AIUI if available
run: |
# AIUI (the Claude chat sidebar) lives outside the Vue build
# and must be copied into the frontend dist BEFORE packaging,
# otherwise OTA-tarball upgrades silently strip it from nodes
# in the field. Try in order: cached on runner, then the
# newest release tarball in this repo's releases/ dir as a
# fallback so a freshly-provisioned runner still gets AIUI.
AIUI_SRC=""
if [ -f "/opt/archipelago/web-ui/aiui/index.html" ]; then
AIUI_SRC="/opt/archipelago/web-ui/aiui"
elif [ -f "$HOME/archy/web/dist/neode-ui/aiui/index.html" ]; then
AIUI_SRC="$HOME/archy/web/dist/neode-ui/aiui"
else
LATEST_FRONTEND=$(ls -t releases/v*/archipelago-frontend-*.tar.gz 2>/dev/null | head -1)
if [ -n "$LATEST_FRONTEND" ]; then
echo "Extracting AIUI from $LATEST_FRONTEND (runner cache miss)"
TMP=$(mktemp -d)
tar xzf "$LATEST_FRONTEND" -C "$TMP" ./aiui 2>/dev/null || true
if [ -f "$TMP/aiui/index.html" ]; then
AIUI_SRC="$TMP/aiui"
fi
fi
fi
if [ -n "$AIUI_SRC" ]; then
mkdir -p web/dist/neode-ui/aiui
cp -r "$AIUI_SRC/"* web/dist/neode-ui/aiui/
echo "AIUI included from $AIUI_SRC ($(du -sh web/dist/neode-ui/aiui | cut -f1))"
else
echo "FAIL: AIUI not found anywhere (runner cache + release tarballs)"
echo " checked: /opt/archipelago/web-ui/aiui"
echo " \$HOME/archy/web/dist/neode-ui/aiui"
echo " releases/v*/archipelago-frontend-*.tar.gz"
exit 1
fi
- name: Configure root podman for insecure registry
run: |
sudo mkdir -p /etc/containers/registries.conf.d
echo '[[registry]]
location = "git.tx1138.com"
insecure = true' | sudo tee /etc/containers/registries.conf.d/archipelago.conf
- name: Build unbundled ISO
run: |
cd image-recipe
export ARCHIPELAGO_BIN="$(pwd)/../core/target/release/archipelago"
if [ ! -x "$ARCHIPELAGO_BIN" ]; then
echo "FAIL: backend binary missing or not executable at $ARCHIPELAGO_BIN"
exit 1
fi
BIN_VERSION=$(strings "$ARCHIPELAGO_BIN" | grep -oE 'archipelago [0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?' | head -1 || true)
EXPECTED=$(grep '^version' ../core/archipelago/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "Binary: $ARCHIPELAGO_BIN ($(du -h "$ARCHIPELAGO_BIN" | cut -f1))"
echo "Embedded version string: ${BIN_VERSION:-unknown}"
echo "Expected version (Cargo.toml): $EXPECTED"
sudo -E UNBUNDLED=1 DEV_SERVER=localhost BUILD_FROM_SOURCE=0 \
ARCHIPELAGO_BIN="$ARCHIPELAGO_BIN" \
./build-auto-installer-iso.sh
- name: Smoke test ISO
run: |
ISO=$(ls image-recipe/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1)
if [ -z "$ISO" ]; then
echo "FAIL: No ISO produced"
exit 1
fi
echo "ISO: $ISO ($(du -h "$ISO" | cut -f1))"
# Mount and verify structure
MNT=$(mktemp -d)
sudo mount -o loop,ro "$ISO" "$MNT"
FAIL=0
for f in live/vmlinuz live/initrd.img live/filesystem.squashfs \
isolinux/isolinux.bin isolinux/isolinux.cfg \
boot/grub/grub.cfg EFI/BOOT/BOOTX64.EFI \
archipelago/auto-install.sh archipelago/rootfs.tar; do
if [ -e "$MNT/$f" ]; then
echo " OK: $f ($(sudo du -h "$MNT/$f" 2>/dev/null | cut -f1))"
else
echo " MISSING: $f"
FAIL=1
fi
done
# Verify initrd has live-boot
INITRD_DIR=$(mktemp -d)
sudo unmkinitramfs "$MNT/live/initrd.img" "$INITRD_DIR" 2>/dev/null
if [ -e "$INITRD_DIR/scripts/live" ] || [ -e "$INITRD_DIR/main/scripts/live" ]; then
echo " OK: initrd has live-boot scripts"
else
echo " MISSING: live-boot scripts in initrd!"
echo " initrd scripts/: $(ls "$INITRD_DIR/scripts/" 2>/dev/null || ls "$INITRD_DIR/main/scripts/" 2>/dev/null)"
FAIL=1
fi
# Check GRUB config has boot=live
if grep -q "boot=live" "$MNT/boot/grub/grub.cfg"; then
echo " OK: grub.cfg has boot=live"
else
echo " MISSING: boot=live in grub.cfg"
FAIL=1
fi
sudo umount "$MNT" 2>/dev/null
rmdir "$MNT" 2>/dev/null
sudo rm -r "$INITRD_DIR" 2>/dev/null
if [ "$FAIL" = "1" ]; then
echo "SMOKE TEST FAILED"
exit 1
fi
echo "SMOKE TEST PASSED"
- name: QEMU boot test
timeout-minutes: 5
continue-on-error: true
run: |
ISO=$(ls image-recipe/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1)
if [ -n "$ISO" ] && command -v qemu-system-x86_64 >/dev/null 2>&1; then
echo "Running headless QEMU boot test..."
bash image-recipe/test-iso-qemu.sh "$ISO" 120
else
echo "Skipping QEMU test (no ISO or QEMU not available)"
fi
- name: Copy to Builds
run: |
ISO=$(ls image-recipe/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1)
if [ -n "$ISO" ]; then
DATE=$(date +%Y%m%d-%H%M)
DEST="/var/lib/archipelago/filebrowser/Builds/archipelago-dev-unbundled-${DATE}.iso"
sudo cp "$ISO" "$DEST"
sudo chown 1000:1000 "$DEST"
echo "ISO: archipelago-dev-unbundled-${DATE}.iso"
echo "Size: $(du -h "$DEST" | cut -f1)"
echo "SHA256: $(sha256sum "$DEST" | cut -d' ' -f1)"
fi
- name: Publish release artifacts and manifest
run: |
VERSION=$(grep '^version' core/archipelago/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
DATE=$(date +%Y-%m-%d)
RELEASE_DIR="/var/lib/archipelago/filebrowser/Builds/releases/v${VERSION}"
sudo mkdir -p "$RELEASE_DIR"
# Copy backend binary
BINARY="core/target/release/archipelago"
if [ -f "$BINARY" ]; then
sudo cp "$BINARY" "$RELEASE_DIR/archipelago"
sudo chmod 755 "$RELEASE_DIR/archipelago"
echo "Backend: $(du -h "$RELEASE_DIR/archipelago" | cut -f1)"
fi
# Create frontend archive
if [ -d "web/dist/neode-ui" ]; then
FRONTEND_ARCHIVE="$RELEASE_DIR/archipelago-frontend-${VERSION}.tar.gz"
sudo tar -czf "$FRONTEND_ARCHIVE" -C web/dist neode-ui
echo "Frontend: $(du -h "$FRONTEND_ARCHIVE" | cut -f1)"
fi
# Generate manifest with SHA256 hashes
BACKEND_HASH=$(sha256sum "$RELEASE_DIR/archipelago" 2>/dev/null | awk '{print $1}')
BACKEND_SIZE=$(stat -c%s "$RELEASE_DIR/archipelago" 2>/dev/null || echo 0)
FRONTEND_NAME="archipelago-frontend-${VERSION}.tar.gz"
FRONTEND_HASH=$(sha256sum "$RELEASE_DIR/$FRONTEND_NAME" 2>/dev/null | awk '{print $1}')
FRONTEND_SIZE=$(stat -c%s "$RELEASE_DIR/$FRONTEND_NAME" 2>/dev/null || echo 0)
# Build download base URL (FileBrowser serves from /Builds/)
HOST=$(hostname -I 2>/dev/null | awk '{print $1}')
BASE_URL="http://${HOST:-192.168.1.228}:8083/Builds/releases/v${VERSION}"
# Generate manifest JSON
python3 -c "
import json
manifest = {
'version': '$VERSION',
'release_date': '$DATE',
'changelog': ['Update to version $VERSION'],
'components': []
}
if '$BACKEND_HASH':
manifest['components'].append({
'name': 'archipelago',
'current_version': '$VERSION',
'new_version': '$VERSION',
'download_url': '$BASE_URL/archipelago',
'sha256': '$BACKEND_HASH',
'size_bytes': int('$BACKEND_SIZE' or '0')
})
if '$FRONTEND_HASH':
manifest['components'].append({
'name': '$FRONTEND_NAME',
'current_version': '$VERSION',
'new_version': '$VERSION',
'download_url': '$BASE_URL/$FRONTEND_NAME',
'sha256': '$FRONTEND_HASH',
'size_bytes': int('$FRONTEND_SIZE' or '0')
})
print(json.dumps(manifest, indent=2))
" | sudo tee "$RELEASE_DIR/manifest.json" > /dev/null
# Also copy manifest to repo releases/ dir for git-based serving
cp "$RELEASE_DIR/manifest.json" releases/manifest.json 2>/dev/null || true
sudo chown -R 1000:1000 "$RELEASE_DIR"
echo ""
echo "Release manifest:"
cat "$RELEASE_DIR/manifest.json"
echo ""
echo "Artifacts published to: $RELEASE_DIR"
- name: Build report
if: always()
continue-on-error: true
run: |
set +eo pipefail
echo "══════════════════════════════════════════"
echo "DEV ISO BUILD REPORT"
echo "══════════════════════════════════════════"
echo "Commit: $(git -C "$HOME/archy" rev-parse --short HEAD 2>/dev/null || echo 'unknown') ($(git -C "$HOME/archy" log -1 --format=%s 2>/dev/null || echo 'unknown'))"
echo "Branch: ${GITHUB_REF_NAME:-dev-iso}"
echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Runner: $(hostname)"
echo ""
echo "── Artifacts ──"
ls -lh image-recipe/results/*.iso 2>/dev/null || echo " No ISO produced"
ls -lh /var/lib/archipelago/filebrowser/Builds/archipelago-dev-*.iso 2>/dev/null | tail -3
echo ""
echo "── Rootfs contents check ──"
ROOTFS=$(ls image-recipe/build/auto-installer/archipelago-rootfs.tar 2>/dev/null) || true
if [ -n "$ROOTFS" ]; then
echo " rootfs.tar: $(sudo du -h "$ROOTFS" 2>/dev/null | cut -f1 || echo 'unknown')"
# List key paths once (podman export omits ./ prefix, so match without it)
ROOTFS_LIST=$(sudo tar tf "$ROOTFS" 2>/dev/null | grep -E '(etc/nginx/sites-available/archipelago|etc/archipelago/ssl/archipelago.crt|usr/local/bin/archipelago-kiosk-launcher|usr/local/bin/archipelago|opt/archipelago/web-ui/index.html)' || true)
for item in \
"nginx config:etc/nginx/sites-available/archipelago" \
"SSL cert:etc/archipelago/ssl/archipelago.crt" \
"kiosk launcher:usr/local/bin/archipelago-kiosk-launcher" \
"backend binary:usr/local/bin/archipelago" \
"web-ui index:opt/archipelago/web-ui/index.html"; do
label="${item%%:*}"; path="${item#*:}"
echo "$ROOTFS_LIST" | grep -q "$path" && echo " $label: PRESENT" || echo " $label: MISSING"
done
else
echo " rootfs.tar not found in workspace"
fi
echo ""
echo "── ISO contents check ──"
ISO=$(ls image-recipe/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1) || true
if [ -n "$ISO" ]; then
echo " ISO size: $(sudo du -h "$ISO" 2>/dev/null | cut -f1 || echo 'unknown')"
ISO_MOUNT=$(mktemp -d)
if sudo mount -o loop,ro "$ISO" "$ISO_MOUNT" 2>/dev/null; then
echo " auto-install.sh: $([ -f "$ISO_MOUNT/archipelago/auto-install.sh" ] && echo 'PRESENT' || echo 'MISSING')"
echo " rootfs.tar: $([ -f "$ISO_MOUNT/archipelago/rootfs.tar" ] && echo "PRESENT ($(sudo du -h "$ISO_MOUNT/archipelago/rootfs.tar" 2>/dev/null | cut -f1))" || echo 'MISSING')"
echo " backend bin: $([ -f "$ISO_MOUNT/archipelago/bin/archipelago" ] && echo "PRESENT ($(sudo du -h "$ISO_MOUNT/archipelago/bin/archipelago" 2>/dev/null | cut -f1))" || echo 'MISSING')"
echo " frontend: $([ -f "$ISO_MOUNT/archipelago/web-ui/index.html" ] && echo 'PRESENT' || echo 'MISSING')"
echo " vmlinuz: $([ -f "$ISO_MOUNT/live/vmlinuz" ] && echo 'PRESENT' || echo 'MISSING')"
echo " initrd: $([ -f "$ISO_MOUNT/live/initrd.img" ] && echo 'PRESENT' || echo 'MISSING')"
echo " squashfs: $([ -f "$ISO_MOUNT/live/filesystem.squashfs" ] && echo "PRESENT ($(sudo du -h "$ISO_MOUNT/live/filesystem.squashfs" 2>/dev/null | cut -f1))" || echo 'MISSING')"
echo " grub theme: $([ -d "$ISO_MOUNT/boot/grub/themes/archipelago" ] && echo 'PRESENT' || echo 'MISSING')"
sudo umount "$ISO_MOUNT" 2>/dev/null || true
else
echo " Could not mount ISO for inspection"
fi
rmdir "$ISO_MOUNT" 2>/dev/null || true
fi
echo "══════════════════════════════════════════"
- name: Fix workspace permissions
if: always()
run: |
sudo chown -R $(id -u):$(id -g) . 2>/dev/null || true
sudo chmod -R u+rwX . 2>/dev/null || true
sudo chown -R $(id -u):$(id -g) "$HOME/.cache/act" 2>/dev/null || true
sudo chmod -R u+rwX "$HOME/.cache/act" 2>/dev/null || true

View File

@@ -0,0 +1,86 @@
# Archipelago ISO Build - Quick Guide
## TL;DR - Build ISO with Live Server State
```bash
cd ~/archy/image-recipe
sudo bash build-auto-installer-iso.sh
```
The script will automatically:
1. Try to capture backend from `/usr/local/bin/archipelago`
2. Try to capture frontend from `/opt/archipelago/web-ui`
3. Fall back to building from source if capture fails
## Build Modes
### Default: Capture from Dev Server (RECOMMENDED)
```bash
# From your Mac (captures from remote dev server):
cd image-recipe
DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh
# From the dev server itself:
cd ~/archy/image-recipe
sudo bash build-auto-installer-iso.sh
```
### Alternative: Build from Source
```bash
BUILD_FROM_SOURCE=1 sudo bash build-auto-installer-iso.sh
```
## Known Issues & Workarounds
### Issue: Can't capture from localhost via SCP
**Problem**: When running on the server itself, `scp localhost:/path` doesn't work.
**Workaround**: Use direct file copy instead:
```bash
# Instead of building on the server, build from your Mac:
cd ~/Projects/archy/image-recipe
DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh
```
### Issue: Podman registry not configured
**Problem**: Podman can't pull images because `/etc/containers/registries.conf` has no unqualified-search registries.
**Fix**:
```bash
ssh archipelago@192.168.1.228
sudo tee -a /etc/containers/registries.conf <<EOF
[registries.search]
registries = ['docker.io']
EOF
```
## Flash ISO to USB
```bash
cd ~/Projects/archy/image-recipe
./write-usb-dd.sh /dev/diskX
```
## What Gets Captured
From your dev server (192.168.1.228):
- ✅ Backend binary: `/usr/local/bin/archipelago` (6.2M)
- ✅ Frontend: `/opt/archipelago/web-ui` (~64M)
- ✅ Nginx config: `/etc/nginx/sites-available/default`
- ✅ Systemd service: `/etc/systemd/system/archipelago.service`
- ✅ App manifests: `~/archy/apps/`
## Current Status
**Latest Working ISO**: `archipelago-debian-13-x86_64.iso` (469M, built 18:28)
- This ISO was built earlier today
- Contains the auto-installer
- **Should be tested** - might already have your live server state
## Next Steps
1. **Flash the existing ISO** and test it on the Dell OptiPlex
2. **Fix the build script** to properly capture from localhost (use `cp` instead of `scp`)
3. **Configure Podman registries** on dev server for fallback source builds

View File

@@ -0,0 +1,155 @@
# ISO Build Checklist
This checklist ensures that all changes from the live development server are properly integrated into the ISO build.
## Pre-Build Steps
### 1. Sync System Configurations from Live Server
```bash
cd image-recipe
./sync-from-live.sh
```
This captures:
- [ ] Systemd service configuration (`archipelago.service`) - **User=root** required for Podman
- [ ] Nginx configuration (`nginx-archipelago.conf`) - includes app proxies (Nextcloud, Vaultwarden, Immich, Penpot)
- [ ] Logrotate configuration (if exists)
- [ ] Any custom scripts in `/opt/archipelago/scripts/`
**Critical**: `build-auto-installer-iso.sh` uses `configs/` for nginx and archipelago.service. Ensure these are synced before building.
### 2. Verify Code Changes
Ensure all code changes are committed:
- [ ] Backend changes in `core/`
- [ ] Frontend changes in `neode-ui/`
- [ ] Script changes in `scripts/`
### 3. Build Components
```bash
cd image-recipe
# Build backend
./scripts/build-backend.sh
# Build frontend
./scripts/build-frontend.sh
```
Verify builds:
- [ ] Backend binary exists: `build/backend/archipelago`
- [ ] Frontend files exist: `build/frontend/index.html`
## Integration Check
### 4. Update Build Scripts
Review and update if needed:
- [ ] `integrate-archipelago.sh` - Includes all config files
- [ ] `build-debian-iso.sh` - Installs to correct paths
### 5. Critical Configuration Values
Verify in `configs/archipelago.service`:
- [ ] `User=root` (required for Podman root context)
- [ ] `Environment="ARCHIPELAGO_DEV_MODE=true"` (enables container detection)
- [ ] `Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"`
Verify in `configs/nginx-archipelago.conf`:
- [ ] Root path: `/opt/archipelago/web-ui`
- [ ] RPC proxy: `/rpc/``http://127.0.0.1:5678`
- [ ] WebSocket proxy: `/ws``http://127.0.0.1:5678`
## Build Process
### 6. Build the ISO
```bash
./build-debian-iso.sh
```
Expected output:
- [ ] ISO created in `results/` directory
- [ ] No build errors
- [ ] File size reasonable (~500MB - 2GB)
### 7. Test in QEMU
```bash
./test-iso-qemu.sh
```
Test checklist:
- [ ] ISO boots successfully
- [ ] Backend service starts: `systemctl status archipelago`
- [ ] Nginx serves frontend
- [ ] Can access UI at `http://localhost:8080` (or mapped port)
- [ ] Container detection works: Check logs for "Detected container"
## App Stack Hardening (Immich, Penpot, etc.)
The first-boot script and deploy script ensure:
- [ ] **Immich**: Old single-container `immich` (wrong port) is removed before creating `immich_server` stack
- [ ] **First-boot**: Waits for postgres (pg_isready) before starting Immich server
- [ ] **Backend**: `package.install` for Immich removes old container before creating stack
- [ ] **Deploy**: Ensures Immich stack on every deploy, cleans up conflicts
## Post-Build
### 8. Write to USB (Optional)
```bash
./write-usb-dd.sh /dev/diskN
```
Or use Balena Etcher to flash the ISO.
### 9. Test on Real Hardware
- [ ] Boot from USB
- [ ] Network configuration works
- [ ] All services start automatically
- [ ] Can access web UI
- [ ] Containers are detected and managed
## Deployment Paths Reference
The ISO build must install to these paths:
| Component | Path | Source |
|-----------|------|--------|
| Backend binary | `/usr/local/bin/archipelago` | `build/backend/archipelago` |
| Frontend files | `/opt/archipelago/web-ui/` | `build/frontend/*` |
| Systemd service | `/etc/systemd/system/archipelago.service` | `configs/archipelago.service` |
| Nginx config | `/etc/nginx/sites-available/archipelago` | `configs/nginx-archipelago.conf` |
| Nginx symlink | `/etc/nginx/sites-enabled/archipelago` | Link to sites-available |
## Common Issues
### Backend Not Detecting Containers
- Verify service runs as `root` user
- Check Podman context: `sudo podman ps` should show containers
- Enable dev mode: `ARCHIPELAGO_DEV_MODE=true`
### UI Not Loading
- Check nginx configuration paths
- Verify frontend files deployed to `/opt/archipelago/web-ui/`
- Check nginx error logs: `/var/log/nginx/error.log`
### Service Fails to Start
- Check binary permissions: Should be executable
- Check systemd logs: `journalctl -u archipelago`
- Test binary manually: `sudo /usr/local/bin/archipelago`
## Version Tracking
When building a new ISO, document:
- Date: _______________
- Git commit: _______________
- Backend version: _______________
- Frontend version: _______________
- ISO filename: _______________
- Tested on hardware: _______________
- Issues found: _______________

View File

@@ -0,0 +1,37 @@
# Archived ISO build recipes
These scripts built the Archipelago auto-installer ISO (bundled and
unbundled variants). As of v1.7.43-alpha, ISOs are no longer part of the
release deliverable. Releases ship as tarballs consumed by
`scripts/self-update.sh` on existing nodes.
Archived here rather than deleted so they can be resurrected if ISO
distribution is reintroduced.
## Contents
- `build-auto-installer-iso.sh` — orchestrator, bundles container images into squashfs
- `build-unbundled-iso.sh` — thin wrapper that sets BUNDLE_IMAGES=0 and delegates
- `test-iso-qemu.sh` — smoke-tests a built ISO under QEMU
- `scripts/convert-iso-to-disk.sh` — converts an ISO to a raw disk image
- `BUILD-ISO-STATUS.md`, `ISO-BUILD-CHECKLIST.md` — contributor guides
- `branding/isohdpfx.bin` — isolinux MBR hybrid image
- `.gitea-workflows/build-iso-dev.yml` — CI workflow that ran the build+smoke-test
## To resurrect
1. `git mv image-recipe/_archived/* image-recipe/` (adjust paths back)
2. Restore `.gitea/workflows/build-iso-dev.yml`
3. Re-add release-process references (see `scripts/create-release.sh`,
`docs/BETA-RELEASE-CHECKLIST.md`, `docs/hotfix-process.md`, `README.md`).
## Why archived
The release flow is simpler and faster as tarball-only:
- `releases/vX.Y.Z-alpha/archipelago` (backend binary)
- `releases/vX.Y.Z-alpha/archipelago-frontend-X.Y.Z-alpha.tar.gz` (frontend + AIUI + filebrowser UI assets)
- `releases/manifest.json` (pointers + changelog)
Nodes pull these via `scripts/self-update.sh` from either Gitea mirror.
Filebrowser and AIUI remain bundled inside the frontend tarball and deployed
atomically by `self-update.sh`.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
#!/bin/bash
#
# Build Archipelago UNBUNDLED Auto-Installer ISO
#
# Same as build-auto-installer-iso.sh but WITHOUT pre-bundled container images.
# Users install all apps on-demand from the Marketplace (requires internet).
#
# Benefits:
# - Much smaller ISO (~1-2GB vs ~8-10GB)
# - Faster build (no image pulling/saving)
# - Faster install (no image copying/loading)
#
# Trade-offs:
# - Internet required after first boot to install apps
# - No apps pre-loaded — everything comes from Marketplace
#
# Usage:
# sudo ./build-unbundled-iso.sh
# DEV_SERVER=archipelago@192.168.1.228 sudo ./build-unbundled-iso.sh
# BUILD_FROM_SOURCE=1 sudo ./build-unbundled-iso.sh
#
set -e
# Configuration
DEV_SERVER="${DEV_SERVER:-archipelago@192.168.1.228}"
BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Delegate to the main build script with UNBUNDLED mode
export UNBUNDLED=1
exec "$SCRIPT_DIR/build-auto-installer-iso.sh" "$@"

View File

@@ -0,0 +1,65 @@
#!/bin/bash
# Convert ISO image to bootable disk image
# Creates a raw disk image that can be flashed directly
set -e
OUTPUT_DIR="${1:-../results}"
ARCHIPELAGO_VERSION="${ARCHIPELAGO_VERSION:-0.1.0}"
ARCH="${ARCH:-x86_64}"
echo "💾 Converting ISO to disk image..."
# Find ISO file
ISO_FILE=$(ls "$OUTPUT_DIR"/*.iso 2>/dev/null | head -1)
if [ -z "$ISO_FILE" ]; then
echo "❌ No ISO file found in $OUTPUT_DIR"
exit 1
fi
echo " Source ISO: $ISO_FILE"
# Create disk image (4GB minimum)
DISK_SIZE=4096 # 4GB in MB
DISK_IMG="$OUTPUT_DIR/archipelago-${ARCHIPELAGO_VERSION}-${ARCH}.img"
echo " Creating disk image: $DISK_IMG"
# Check if we have required tools
if ! command -v dd >/dev/null 2>&1; then
echo "❌ dd not found"
exit 1
fi
# Create empty disk image
dd if=/dev/zero of="$DISK_IMG" bs=1M count=$DISK_SIZE 2>/dev/null || {
echo "❌ Failed to create disk image"
exit 1
}
# Note: Full disk image creation with partitions requires:
# - parted or fdisk
# - mkfs.vfat, mkfs.ext4
# - losetup (Linux only)
# - grub-install
# For now, we'll create a simple approach:
# The ISO can be used directly, or users can use tools like:
# - balenaEtcher (macOS/Linux GUI)
# - Rufus (Windows)
# - dd (command line)
echo "⚠️ Full disk image conversion requires additional tools"
echo " For now, use the ISO file directly with:"
echo " - balenaEtcher (recommended)"
echo " - dd command (see docs)"
echo ""
echo " ISO file: $ISO_FILE"
echo " Size: $(du -h "$ISO_FILE" | cut -f1)"
# Clean up empty image file
rm -f "$DISK_IMG"
echo ""
echo "💡 Tip: Use the ISO file with a USB flashing tool"
echo " The ISO is bootable and can be flashed directly"

View File

@@ -0,0 +1,157 @@
#!/bin/bash
# Test Archipelago ISO in QEMU
#
# Usage:
# ./test-iso-qemu.sh [path-to-iso] [--bios] [--nographic]
#
# Options:
# --bios Force legacy BIOS mode (default: UEFI)
# --nographic No GUI window, serial console only (great for logging)
#
# Serial log is always written to /tmp/archipelago-qemu-serial.log
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SERIAL_LOG="/tmp/archipelago-qemu-serial.log"
FORCE_BIOS=false
NOGRAPHIC=false
TIMEOUT=0
ISO=""
# Simple arg parsing. First non-flag positional is the ISO path. A bare
# numeric (e.g. `120`) is taken as a boot-test timeout in seconds so CI
# can call `test-iso-qemu.sh <iso> 120` without hanging the job. The
# pre-fix version used `case *) ISO=...`, which silently overwrote ISO
# with the timeout value and sent QEMU looking for a file literally
# named "120".
for arg in "$@"; do
case "$arg" in
--bios) FORCE_BIOS=true ;;
--nographic) NOGRAPHIC=true ;;
--timeout=*) TIMEOUT="${arg#--timeout=}" ;;
[0-9]*) TIMEOUT="$arg" ;;
*) ISO="$arg" ;;
esac
done
# A positive TIMEOUT implies headless (no DISPLAY in CI anyway) and keeps
# the entire script wrapped in `timeout` to guarantee the job returns.
if [ "$TIMEOUT" -gt 0 ] 2>/dev/null; then
NOGRAPHIC=true
fi
# Auto-detect ISO
if [ -z "$ISO" ]; then
ISO=$(ls -t "$SCRIPT_DIR"/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1)
fi
if [ -z "$ISO" ] || [ ! -f "$ISO" ]; then
ISO=$(ls -t "$SCRIPT_DIR"/results/archipelago-*.iso 2>/dev/null | head -1)
fi
if [ -z "$ISO" ] || [ ! -f "$ISO" ]; then
echo "ISO not found."
echo ""
echo "Usage: $0 [path-to-iso] [--bios] [--nographic]"
echo ""
echo "Or place an ISO in: $SCRIPT_DIR/results/"
exit 1
fi
echo "Testing Archipelago ISO in QEMU"
echo " ISO: $ISO"
echo " Size: $(du -h "$ISO" | cut -f1)"
echo " RAM: 4GB"
echo " CPU: 2 cores"
echo " Serial: $SERIAL_LOG"
echo ""
# Create test disk if it doesn't exist
DISK="/tmp/archipelago-test-disk.qcow2"
if [ ! -f "$DISK" ]; then
echo "Creating 20GB test disk..."
qemu-img create -f qcow2 "$DISK" 20G
fi
# Common QEMU args
QEMU_ARGS=(
-m 4G
-smp 2
-boot d
-cdrom "$ISO"
-drive if=virtio,format=qcow2,file="$DISK"
-net nic,model=virtio -net user,hostfwd=tcp::2222-:22,hostfwd=tcp::8100-:80
-serial file:"$SERIAL_LOG"
)
# Display mode
if [ "$NOGRAPHIC" = true ]; then
QEMU_ARGS+=(-nographic -append "console=ttyS0")
else
QEMU_ARGS+=(-vga virtio -display default)
fi
echo "Starting VM..."
echo "(Serial console logging to $SERIAL_LOG)"
echo "(Press Ctrl+Alt+G to release mouse, Ctrl+C to stop VM)"
echo ""
# Detect UEFI firmware
OVMF=""
if [ "$FORCE_BIOS" = false ]; then
if [ -f "/opt/homebrew/share/qemu/edk2-x86_64-code.fd" ]; then
OVMF="/opt/homebrew/share/qemu/edk2-x86_64-code.fd"
elif [ -f "/usr/share/OVMF/OVMF_CODE.fd" ]; then
OVMF="/usr/share/OVMF/OVMF_CODE.fd"
fi
fi
run_qemu() {
if [ -n "$OVMF" ]; then
echo " Boot: UEFI ($OVMF)"
qemu-system-x86_64 \
-machine q35 \
-drive if=pflash,format=raw,readonly=on,file="$OVMF" \
"${QEMU_ARGS[@]}"
else
echo " Boot: Legacy BIOS"
qemu-system-x86_64 \
-machine pc \
"${QEMU_ARGS[@]}"
fi
}
# Wrap the QEMU invocation in `timeout` when a CI caller passed one so
# the script always returns instead of hanging on a VM that never exits
# its boot loop. Exit 124 from coreutils' timeout is treated as "VM
# reached the timeout", which for a CI boot test is success as long as
# the serial log shows a kernel reaching userspace — we inspect that
# after the QEMU process ends.
if [ "$TIMEOUT" -gt 0 ] 2>/dev/null; then
timeout --foreground --preserve-status "${TIMEOUT}s" bash -c "$(declare -f run_qemu); run_qemu"
rc=$?
if [ $rc -eq 124 ] || [ $rc -eq 137 ]; then
echo "(QEMU terminated after ${TIMEOUT}s boot-test window)"
rc=0
fi
else
run_qemu
rc=$?
fi
echo ""
echo "VM stopped. Serial log: $SERIAL_LOG"
echo "Last 20 lines:"
tail -20 "$SERIAL_LOG" 2>/dev/null
# Boot-sanity check: the CI wrapper wants a non-zero exit only when the
# kernel never reached userspace. Look for a well-known marker emitted
# by live-boot/systemd early in the sequence. If the marker never
# appeared, surface the real failure; otherwise treat "timeout reached
# with a live kernel" as a pass.
if [ "$TIMEOUT" -gt 0 ] 2>/dev/null && [ -f "$SERIAL_LOG" ]; then
if grep -qE "Welcome to Debian|Reached target|systemd\[1\]:" "$SERIAL_LOG"; then
echo " Boot sanity: OK (systemd reached in serial log)"
exit 0
fi
echo " Boot sanity: FAIL — no systemd markers in serial log within ${TIMEOUT}s"
exit 1
fi
exit "${rc:-0}"