2026-04-13 23:55:58 +02:00
|
|
|
#!/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/"
|
|
|
|
|
|
feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
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.
|
2026-04-15 09:10:20 +02:00
|
|
|
#
|
|
|
|
|
# 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.
|
feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
find "$PROFILE_WORK/grub" "$PROFILE_WORK/syslinux" "$PROFILE_WORK/efiboot" \
|
|
|
|
|
-type f \( -name "*.cfg" -o -name "*.conf" \) -print0 \
|
|
|
|
|
| xargs -0 sed -i \
|
2026-04-15 09:10:20 +02:00
|
|
|
-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/'
|
feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
|
2026-04-14 17:00:39 +02:00
|
|
|
# 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"
|
|
|
|
|
|
2026-04-13 23:55:58 +02:00
|
|
|
mkdir -p "$PROFILE_WORK/airootfs/opt/furtka"
|
|
|
|
|
cp -a "$REPO_ROOT/webinstaller/." "$PROFILE_WORK/airootfs/opt/furtka/"
|
refactor(webinstaller): extract inline payload constants to furtka/assets/
Slice 1a of the self-update story. Every HTML/CSS/shell-script/systemd-
unit payload that used to live as a triple-quoted string constant inside
webinstaller/app.py now lives as a real file under furtka/assets/:
furtka/assets/Caddyfile
furtka/assets/VERSION (new — matches pyproject.toml)
furtka/assets/www/{index.html, settings/index.html, style.css, status.json}
furtka/assets/bin/{furtka-status, furtka-welcome}
furtka/assets/systemd/furtka-{api,reconcile,status,welcome}.service
furtka/assets/systemd/furtka-status.timer
The installer now pulls each file from disk via _read_asset(). Byte-for-
byte identical output at install time — a fresh-ISO install should land
the same files in the same places with the same contents, verified by
tests/test_webinstaller_assets.py which reconstructs each base64 blob
and asserts equality against the on-disk asset.
iso/build.sh also copies furtka/assets/ next to the webinstaller source
at /opt/furtka/assets on the live ISO so _resolve_assets_dir() finds
them with a "next to me" lookup. In dev the same function walks two
levels up to the repo copy, so pytest works without any env vars.
furtka-status.sh drops the /etc/furtka/version TODO — it now reads
/opt/furtka/VERSION directly, which Slice 1b will upgrade to
/opt/furtka/current/VERSION once the symlink layout lands.
_FURTKA_WRAPPER_SH (the 5-line /usr/local/bin/furtka shim) stays inline;
it's tiny and not asset-shaped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:08:53 +02:00
|
|
|
# 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/furtka/assets" "$PROFILE_WORK/airootfs/opt/furtka/assets"
|
2026-04-13 23:55:58 +02:00
|
|
|
rm -rf "$PROFILE_WORK/airootfs/opt/furtka/__pycache__"
|
|
|
|
|
|
feat(furtka): ship resource manager + fileshare app on the ISO — slice 3
Closes the loop end-to-end. The ISO build now bundles the furtka/
package and the apps/ tree as a tarball; webinstaller hands it to
archinstall via custom_commands; the installed system gets the
`furtka` CLI, a boot-scan systemd unit, and the fileshare app
ready to install.
- iso/build.sh: stages furtka/ + apps/ into a tmpdir, drops
__pycache__, tarballs into airootfs/opt/furtka-resource-manager.tar.gz.
- webinstaller/app.py: _resource_manager_commands() reads the staged
payload at request-time, base64-encodes it into a single untar
command, and writes /usr/local/bin/furtka (PYTHONPATH wrapper, no
pip needed) + furtka-reconcile.service. Python pacstrapped so the
wrapper has an interpreter.
- Graceful degradation: dev box / CI without an ISO build has no
payload tarball, so those commands are skipped (logs a warning).
Tests cover both branches.
- furtka-reconcile.service is conditionally enabled only if the unit
file actually landed — keeps the systemctl enable line green when
the payload was absent.
- apps/fileshare/: first real Furtka app. dperson/samba on host
network, single named volume, .env.example with placeholder creds.
Manifest matches the schema locked in slice 1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:06:01 +02:00
|
|
|
# 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/, and gets a working `furtka` CLI + the fileshare app.
|
|
|
|
|
echo "==> Bundling resource manager payload"
|
|
|
|
|
PAYLOAD_STAGE="$(mktemp -d)"
|
|
|
|
|
cp -a "$REPO_ROOT/furtka" "$PAYLOAD_STAGE/"
|
|
|
|
|
cp -a "$REPO_ROOT/apps" "$PAYLOAD_STAGE/"
|
|
|
|
|
find "$PAYLOAD_STAGE" -type d -name __pycache__ -exec rm -rf {} +
|
|
|
|
|
tar -czf "$PROFILE_WORK/airootfs/opt/furtka-resource-manager.tar.gz" \
|
|
|
|
|
-C "$PAYLOAD_STAGE" .
|
|
|
|
|
rm -rf "$PAYLOAD_STAGE"
|
|
|
|
|
|
2026-04-13 23:55:58 +02:00
|
|
|
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"
|