From c080764c7eee17f7dd17de5f09c7594892a8ef8c Mon Sep 17 00:00:00 2001 From: Daniel Maksymilian Syrnicki Date: Thu, 16 Apr 2026 15:26:10 +0200 Subject: [PATCH] fix(furtka): move assets/ to repo top level so Caddy + systemd find it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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//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) --- {furtka/assets => assets}/Caddyfile | 0 {furtka/assets => assets}/bin/furtka-status | 0 {furtka/assets => assets}/bin/furtka-welcome | 0 {furtka/assets => assets}/systemd/furtka-api.service | 0 .../systemd/furtka-reconcile.service | 0 .../assets => assets}/systemd/furtka-status.service | 0 .../assets => assets}/systemd/furtka-status.timer | 0 .../assets => assets}/systemd/furtka-welcome.service | 0 {furtka/assets => assets}/www/index.html | 0 {furtka/assets => assets}/www/settings/index.html | 0 {furtka/assets => assets}/www/status.json | 0 {furtka/assets => assets}/www/style.css | 0 iso/build.sh | 5 ++++- scripts/build-release-tarball.sh | 3 +++ tests/test_webinstaller_assets.py | 2 +- webinstaller/app.py | 12 ++++++------ 16 files changed, 14 insertions(+), 8 deletions(-) rename {furtka/assets => assets}/Caddyfile (100%) rename {furtka/assets => assets}/bin/furtka-status (100%) rename {furtka/assets => assets}/bin/furtka-welcome (100%) rename {furtka/assets => assets}/systemd/furtka-api.service (100%) rename {furtka/assets => assets}/systemd/furtka-reconcile.service (100%) rename {furtka/assets => assets}/systemd/furtka-status.service (100%) rename {furtka/assets => assets}/systemd/furtka-status.timer (100%) rename {furtka/assets => assets}/systemd/furtka-welcome.service (100%) rename {furtka/assets => assets}/www/index.html (100%) rename {furtka/assets => assets}/www/settings/index.html (100%) rename {furtka/assets => assets}/www/status.json (100%) rename {furtka/assets => assets}/www/style.css (100%) diff --git a/furtka/assets/Caddyfile b/assets/Caddyfile similarity index 100% rename from furtka/assets/Caddyfile rename to assets/Caddyfile diff --git a/furtka/assets/bin/furtka-status b/assets/bin/furtka-status similarity index 100% rename from furtka/assets/bin/furtka-status rename to assets/bin/furtka-status diff --git a/furtka/assets/bin/furtka-welcome b/assets/bin/furtka-welcome similarity index 100% rename from furtka/assets/bin/furtka-welcome rename to assets/bin/furtka-welcome diff --git a/furtka/assets/systemd/furtka-api.service b/assets/systemd/furtka-api.service similarity index 100% rename from furtka/assets/systemd/furtka-api.service rename to assets/systemd/furtka-api.service diff --git a/furtka/assets/systemd/furtka-reconcile.service b/assets/systemd/furtka-reconcile.service similarity index 100% rename from furtka/assets/systemd/furtka-reconcile.service rename to assets/systemd/furtka-reconcile.service diff --git a/furtka/assets/systemd/furtka-status.service b/assets/systemd/furtka-status.service similarity index 100% rename from furtka/assets/systemd/furtka-status.service rename to assets/systemd/furtka-status.service diff --git a/furtka/assets/systemd/furtka-status.timer b/assets/systemd/furtka-status.timer similarity index 100% rename from furtka/assets/systemd/furtka-status.timer rename to assets/systemd/furtka-status.timer diff --git a/furtka/assets/systemd/furtka-welcome.service b/assets/systemd/furtka-welcome.service similarity index 100% rename from furtka/assets/systemd/furtka-welcome.service rename to assets/systemd/furtka-welcome.service diff --git a/furtka/assets/www/index.html b/assets/www/index.html similarity index 100% rename from furtka/assets/www/index.html rename to assets/www/index.html diff --git a/furtka/assets/www/settings/index.html b/assets/www/settings/index.html similarity index 100% rename from furtka/assets/www/settings/index.html rename to assets/www/settings/index.html diff --git a/furtka/assets/www/status.json b/assets/www/status.json similarity index 100% rename from furtka/assets/www/status.json rename to assets/www/status.json diff --git a/furtka/assets/www/style.css b/assets/www/style.css similarity index 100% rename from furtka/assets/www/style.css rename to assets/www/style.css diff --git a/iso/build.sh b/iso/build.sh index 56923ca..1317a87 100755 --- a/iso/build.sh +++ b/iso/build.sh @@ -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// # directory name and /opt/furtka/current/VERSION reports it at runtime. diff --git a/scripts/build-release-tarball.sh b/scripts/build-release-tarball.sh index e71b795..17b238a 100755 --- a/scripts/build-release-tarball.sh +++ b/scripts/build-release-tarball.sh @@ -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" diff --git a/tests/test_webinstaller_assets.py b/tests/test_webinstaller_assets.py index e24604b..42a1e4d 100644 --- a/tests/test_webinstaller_assets.py +++ b/tests/test_webinstaller_assets.py @@ -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 diff --git a/webinstaller/app.py b/webinstaller/app.py index 402bc08..f9e7007 100644 --- a/webinstaller/app.py +++ b/webinstaller/app.py @@ -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