fix(furtka): move assets/ to repo top level so Caddy + systemd find it
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

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>
This commit is contained in:
Daniel Maksymilian Syrnicki 2026-04-16 15:26:10 +02:00
parent 661f51e91a
commit c080764c7e
16 changed files with 14 additions and 8 deletions

View file

@ -76,7 +76,7 @@ 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/furtka/assets" "$PROFILE_WORK/airootfs/opt/furtka/assets"
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
@ -89,6 +89,9 @@ 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.

View file

@ -23,6 +23,9 @@ trap 'rm -rf "$STAGE"' EXIT
cp -a "$REPO_ROOT/furtka" "$STAGE/"
cp -a "$REPO_ROOT/apps" "$STAGE/"
# assets/ ships at the tarball root — Caddy + systemd + updater resolve
# everything from /opt/furtka/current/assets/, never from inside the package.
cp -a "$REPO_ROOT/assets" "$STAGE/"
find "$STAGE" -type d -name __pycache__ -exec rm -rf {} +
echo "$VERSION" > "$STAGE/VERSION"

View file

@ -26,7 +26,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "webinstaller"))
import app # noqa: E402
REPO_ROOT = Path(__file__).resolve().parent.parent
ASSETS = REPO_ROOT / "furtka" / "assets"
ASSETS = REPO_ROOT / "assets"
# (install target path, asset path under furtka/assets/) — only the files we

View file

@ -148,7 +148,7 @@ def build_disk_config(boot_drive):
# console shows a welcome banner pointing at the URL.
#
# Asset files (HTML, CSS, shell scripts, systemd units, Caddyfile) live in
# furtka/assets/ in the repo — at ISO build time they end up on the live ISO
# assets/ in the repo — at ISO build time they end up on the live ISO
# as part of the webinstaller's source tree AND inside the resource-manager
# payload tarball. The installer reads them from the live-ISO copy, base64-
# encodes them, and hands them to archinstall so the chroot recreates each
@ -157,7 +157,7 @@ def build_disk_config(boot_drive):
# ---------------------------------------------------------------------------
# Tarball built by iso/build.sh containing the furtka/ Python package + the
# bundled apps/ tree (plus furtka/assets/). The webinstaller reads it from the
# bundled apps/ tree (plus assets/). The webinstaller reads it from the
# live ISO at request-time and base64-encodes it into a custom_command for
# archinstall.
RESOURCE_MANAGER_PAYLOAD = Path("/opt/furtka-resource-manager.tar.gz")
@ -165,9 +165,9 @@ RESOURCE_MANAGER_PAYLOAD = Path("/opt/furtka-resource-manager.tar.gz")
# Asset root. Two layouts we have to handle:
# dev / tests — webinstaller/app.py sits at repo_root/webinstaller/ and
# assets live at repo_root/furtka/assets/.
# assets live at repo_root/assets/.
# live ISO — iso/build.sh copies webinstaller/ to /opt/furtka/ AND
# copies furtka/assets/ to /opt/furtka/assets/ right next to
# copies assets/ to /opt/assets/ right next to
# app.py, so the same "assets next to me" lookup works.
# Probe the sibling path first (ISO case), fall back to the repo layout.
def _resolve_assets_dir() -> Path:
@ -175,7 +175,7 @@ def _resolve_assets_dir() -> Path:
sibling = here / "assets"
if sibling.is_dir():
return sibling
repo_copy = here.parent / "furtka" / "assets"
repo_copy = here.parent / "assets"
if repo_copy.is_dir():
return repo_copy
raise FileNotFoundError(
@ -187,7 +187,7 @@ _ASSETS_DIR = _resolve_assets_dir()
def _read_asset(relpath: str) -> str:
"""Return the UTF-8 contents of an on-disk asset shipped under furtka/assets/.
"""Return the UTF-8 contents of an on-disk asset shipped under assets/.
Raises FileNotFoundError if the asset is missing, which is loud by design:
an install that tries to write an asset that isn't there is broken before