furtka/iso/build.sh
Daniel Maksymilian Syrnicki c080764c7e
All checks were successful
Build ISO / build-iso (push) Successful in 17m5s
CI / lint (push) Successful in 27s
CI / test (push) Successful in 40s
CI / validate-json (push) Successful in 25s
CI / markdown-links (push) Successful in 12s
fix(furtka): move assets/ to repo top level so Caddy + systemd find it
Root cause of today's 403 on a fresh install: assets/ lived inside the
Python package at furtka/assets/, so the resource-manager tarball
extracted to /opt/furtka/versions/<ver>/furtka/assets/. But Caddyfile
has `root * /opt/furtka/current/assets/www`, systemd units point at
/opt/furtka/current/assets/bin/furtka-status, and the install-time
`systemctl link /opt/furtka/current/assets/systemd/*.service` expected
the top-level layout. All three found nothing:

- Caddy → 403 Forbidden (empty/missing document root)
- systemctl link → silent no-op, nothing ever linked into
  /etc/systemd/system/
- furtka-api.service + furtka-reconcile.service → "inactive" because
  they were never registered

Nothing in the Python package ever imported furtka.assets — these are
shell scripts, HTML/CSS, systemd units, and a Caddyfile, which is
config data, not package data. Promoting assets/ to the repo root
matches how it's referenced everywhere downstream and eliminates the
path mismatch.

Changes:
- git mv furtka/assets assets
- iso/build.sh: tarball-staging step now also `cp -a "$REPO_ROOT/assets"`
  so the tarball ships ./assets at its root, and the live-ISO copy
  reads from $REPO_ROOT/assets instead of $REPO_ROOT/furtka/assets.
- scripts/build-release-tarball.sh: same for release tarballs.
- webinstaller/app.py: _resolve_assets_dir's dev fallback walks one
  level up to REPO_ROOT/assets/.
- tests/test_webinstaller_assets.py: ASSETS constant updated.

Tests still green (150/150) because both paths were fs-level — no
code imports changed. Next ISO build will land assets at the path
everything downstream expects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:26:10 +02:00

114 lines
5.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# Build a Furtka live ISO.
#
# From the repo root or from iso/ on any host with Docker:
# ./iso/build.sh
#
# The build runs inside a privileged `archlinux:latest` container because
# mkarchiso needs root + loop mounts + an Arch package manager, which
# Ubuntu doesn't provide natively. Output ISO goes to iso/out/.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUT_DIR="$SCRIPT_DIR/out"
if [[ "${FURTKA_ISO_INNER:-0}" != "1" ]]; then
mkdir -p "$OUT_DIR"
echo "==> Launching build container"
exec docker run --rm --privileged \
-v "$REPO_ROOT:/work" \
-w /work \
-e FURTKA_ISO_INNER=1 \
archlinux:latest \
bash /work/iso/build.sh
fi
# ---- inside the container from here on ----
echo "==> Syncing pacman, installing archiso"
pacman -Syu --noconfirm --needed archiso
PROFILE_SRC="/usr/share/archiso/configs/releng"
PROFILE_WORK="/tmp/furtka-profile"
BUILD_WORK="/tmp/furtka-build"
OUT_IN_CONTAINER="/work/iso/out"
rm -rf "$PROFILE_WORK" "$BUILD_WORK"
cp -a "$PROFILE_SRC" "$PROFILE_WORK"
echo "==> Overlaying Furtka customizations"
cat "$SCRIPT_DIR/overlay/packages.extra" >> "$PROFILE_WORK/packages.x86_64"
cat "$SCRIPT_DIR/overlay/profiledef.sh" >> "$PROFILE_WORK/profiledef.sh"
cp -a "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE_WORK/airootfs/"
echo "==> Rebranding boot menu (GRUB + syslinux + systemd-boot)"
# releng ships menu entries labelled "Arch Linux install medium" across three
# bootloader configs (BIOS syslinux, GRUB, systemd-boot for UEFI). Rewrite to
# our brand. Done with sed (not a static overlay) so upstream archiso file
# moves don't silently leave stale Arch labels behind.
#
# Also rebrands the syslinux menu header ("MENU TITLE Arch Linux") and the
# per-entry HELP text shown at the bottom of the BIOS screen. GRUB/efiboot
# don't ship equivalent long descriptions, so menu-entry rename is enough there.
find "$PROFILE_WORK/grub" "$PROFILE_WORK/syslinux" "$PROFILE_WORK/efiboot" \
-type f \( -name "*.cfg" -o -name "*.conf" \) -print0 \
| xargs -0 sed -i \
-e 's/Arch Linux install medium/Furtka Live Installer/g' \
-e 's/Arch Linux live medium/Furtka Live Installer/g' \
-e 's/install Arch Linux or perform system maintenance/install Furtka or perform system maintenance/g' \
-e 's/^MENU TITLE Arch Linux$/MENU TITLE Furtka/'
# Mark the default entry as (Recommended) so first-time users know which to
# pick. Targets the main entry only — speech/accessibility variants stay
# unlabeled to avoid suggesting they're the normal choice.
sed -i 's/^title Furtka Live Installer (%ARCH%, UEFI)$/title (Recommended) Furtka Live Installer (%ARCH%, UEFI)/' \
"$PROFILE_WORK/efiboot/loader/entries/01-archiso-linux.conf"
sed -i 's/^MENU LABEL Furtka Live Installer (%ARCH%, BIOS)$/MENU LABEL (Recommended) Furtka Live Installer (%ARCH%, BIOS)/' \
"$PROFILE_WORK/syslinux/archiso_sys-linux.cfg"
sed -i "/--id 'archlinux'/s/menuentry \"Furtka Live Installer/menuentry \"(Recommended) Furtka Live Installer/" \
"$PROFILE_WORK/grub/grub.cfg" "$PROFILE_WORK/grub/loopback.cfg"
mkdir -p "$PROFILE_WORK/airootfs/opt/furtka"
cp -a "$REPO_ROOT/webinstaller/." "$PROFILE_WORK/airootfs/opt/furtka/"
# Ship the post-install asset tree (HTML, CSS, systemd units, scripts, …)
# next to webinstaller/app.py so _resolve_assets_dir() finds it at runtime.
cp -a "$REPO_ROOT/assets" "$PROFILE_WORK/airootfs/opt/furtka/assets"
rm -rf "$PROFILE_WORK/airootfs/opt/furtka/__pycache__"
# Pack the resource manager (furtka/ Python package + bundled apps/) as a
# tarball that webinstaller hands to archinstall via custom_commands. Lives at
# a fixed path in the live ISO; the installed system reads it back, untars
# into /opt/furtka/versions/<VERSION>/, and gets a working `furtka` CLI + the
# fileshare app. Same tarball shape as Phase-2 self-update releases, so an
# ISO-installed box and an updated box converge on the same layout.
echo "==> Bundling resource manager payload"
PAYLOAD_STAGE="$(mktemp -d)"
cp -a "$REPO_ROOT/furtka" "$PAYLOAD_STAGE/"
cp -a "$REPO_ROOT/apps" "$PAYLOAD_STAGE/"
# assets/ ships at the tarball root (not inside the Python package) because
# Caddy, systemd, and the updater all expect it at /opt/furtka/current/assets/.
cp -a "$REPO_ROOT/assets" "$PAYLOAD_STAGE/"
find "$PAYLOAD_STAGE" -type d -name __pycache__ -exec rm -rf {} +
# VERSION at tarball root: the installer reads it to choose the versions/<ver>/
# directory name and /opt/furtka/current/VERSION reports it at runtime.
grep -E '^version = ' "$REPO_ROOT/pyproject.toml" | head -1 \
| sed 's/.*= "\(.*\)"/\1/' > "$PAYLOAD_STAGE/VERSION"
tar -czf "$PROFILE_WORK/airootfs/opt/furtka-resource-manager.tar.gz" \
-C "$PAYLOAD_STAGE" .
rm -rf "$PAYLOAD_STAGE"
mkdir -p "$PROFILE_WORK/airootfs/etc/systemd/system/avahi-daemon.service.d"
ln -sf /usr/lib/systemd/system/avahi-daemon.service \
"$PROFILE_WORK/airootfs/etc/systemd/system/multi-user.target.wants/avahi-daemon.service"
echo "==> Building ISO (mkarchiso)"
mkdir -p "$OUT_IN_CONTAINER"
mkarchiso -v -w "$BUILD_WORK" -o "$OUT_IN_CONTAINER" "$PROFILE_WORK"
echo
echo "==> Done. ISO(s) in $OUT_IN_CONTAINER (on host: iso/out/):"
ls -lh "$OUT_IN_CONTAINER"