fix(furtka): chmod 755 on version dir + heredoc furtka.json
Two install-path bugs surfaced by SSHing into the hot-fixed test VM:
1. mktemp creates the staging dir with mode 700 by default; the rename
to /opt/furtka/versions/<ver>/ preserved it, and Caddy (running as
the unprivileged `caddy` user) got 403 Forbidden because it couldn't
traverse the version dir. Now the install + self-update both
`chmod 755` after the rename.
2. _furtka_json_cmd was a silent no-op on the 43a39a4 VM — the
base64-encoded body + sed substitution approach layered two sets of
quotes through archinstall's custom_commands eval, and the sed
step either never ran or didn't match. Replaced with a plain
heredoc that interpolates $(date -Iseconds) and $(cat VERSION) at
chroot runtime. Result lands /var/lib/furtka/furtka.json reliably,
which is what the landing page's hostname chip and the settings
page's install-date field depend on.
Both issues confirmed fixed by applying them manually on the VM
(chmod 755 /opt/furtka/versions/26.0-alpha + writing furtka.json by
hand): landing page, /apps, /settings, /furtka.json all now 200 with
correct content.
Tests updated (the chmod 755 gets asserted; the old base64+sed test
gets replaced with a heredoc-shape check; the updater test asserts
0o755 mode on the finished version dir).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c080764c7e
commit
19e72cf5c3
4 changed files with 40 additions and 32 deletions
|
|
@ -319,6 +319,9 @@ def apply_update(tarball: Path, version: str) -> None:
|
|||
if target.exists():
|
||||
shutil.rmtree(target)
|
||||
staging.rename(target)
|
||||
# mktemp-style 700 default on the staging dir carries through the
|
||||
# rename; Caddy (non-root) needs 755 to traverse /opt/furtka/current/.
|
||||
target.chmod(0o755)
|
||||
|
||||
write_state("swapping", latest=version)
|
||||
previous = None
|
||||
|
|
|
|||
|
|
@ -150,6 +150,10 @@ def test_apply_update_happy_path(tmp_path, updater, monkeypatch):
|
|||
|
||||
assert current.resolve() == (versions / "26.1-alpha").resolve()
|
||||
assert (versions / "26.1-alpha" / "VERSION").read_text().strip() == "26.1-alpha"
|
||||
# Version dir is 755 so Caddy can traverse it — the staging dir came
|
||||
# from mktemp-ish extractall which defaults to 700, and carries through
|
||||
# the rename unless we explicitly chmod.
|
||||
assert (versions / "26.1-alpha").stat().st_mode & 0o777 == 0o755
|
||||
# P1-2: Caddyfile was copied into /etc/caddy/ from the new version.
|
||||
assert updater._CADDYFILE_LIVE.read_text() == "# new caddy config\n"
|
||||
state = updater.read_state()
|
||||
|
|
|
|||
|
|
@ -78,9 +78,23 @@ def test_resource_manager_extracts_to_versioned_slot(install_cmds):
|
|||
# An empty VERSION file must abort the install instead of silently
|
||||
# moving the staging dir into versions/ as a subdir.
|
||||
assert '[ -n "$ver" ]' in extract_cmd
|
||||
# Version dir must be 755 so Caddy (non-root) can traverse it.
|
||||
assert 'chmod 755 "/opt/furtka/versions/$ver"' in extract_cmd
|
||||
assert 'ln -sfn "/opt/furtka/versions/$ver" /opt/furtka/current' in extract_cmd
|
||||
|
||||
|
||||
def test_furtka_json_cmd_uses_heredoc_and_interpolates_hostname(install_cmds):
|
||||
# Regression: the previous base64+sed version of this command was a
|
||||
# silent no-op on some installs due to archinstall-side quoting; the
|
||||
# heredoc version is the one that reliably writes furtka.json.
|
||||
cmd = next((c for c in install_cmds if "/var/lib/furtka/furtka.json" in c), None)
|
||||
assert cmd is not None
|
||||
assert "cat > /var/lib/furtka/furtka.json <<EOF" in cmd
|
||||
assert '"hostname": "testhost"' in cmd
|
||||
assert "date -Iseconds" in cmd
|
||||
assert "/opt/furtka/current/VERSION" in cmd
|
||||
|
||||
|
||||
def test_resource_manager_systemctl_links_every_unit(install_cmds):
|
||||
link_cmd = next((c for c in install_cmds if c.startswith("systemctl link ")), None)
|
||||
assert link_cmd is not None, "no systemctl link command"
|
||||
|
|
@ -95,23 +109,6 @@ def test_resource_manager_enables_all_units(install_cmds):
|
|||
assert unit in enable_cmd
|
||||
|
||||
|
||||
def test_furtka_json_written_with_hostname(install_cmds):
|
||||
furtka_json = next(
|
||||
(c for c in install_cmds if "/var/lib/furtka/furtka.json" in c and "mkdir" in c),
|
||||
None,
|
||||
)
|
||||
assert furtka_json is not None
|
||||
# The base64-encoded JSON body should contain the hostname we passed in.
|
||||
content = _extract_written_content(furtka_json, "/var/lib/furtka/furtka.json")
|
||||
assert '"hostname": "testhost"' in content
|
||||
# install_date + version placeholders — the sed below substitutes them at
|
||||
# install time against /opt/furtka/current/VERSION + `date -Iseconds`.
|
||||
assert "__INSTALL_DATE__" in content
|
||||
assert "__VERSION__" in content
|
||||
assert "date -Iseconds" in furtka_json
|
||||
assert "/opt/furtka/current/VERSION" in furtka_json
|
||||
|
||||
|
||||
def test_wrapper_script_points_at_current_symlink():
|
||||
assert "PYTHONPATH=/opt/furtka/current" in app._FURTKA_WRAPPER_SH
|
||||
|
||||
|
|
|
|||
|
|
@ -263,6 +263,11 @@ def _resource_manager_commands():
|
|||
# as a subdir and the symlink target would be invalid.
|
||||
'[ -n "$ver" ] || { echo "empty VERSION in payload" >&2; exit 1; } && '
|
||||
'mv "$staging" "/opt/furtka/versions/$ver" && '
|
||||
# mktemp -d creates the staging dir with mode 700; that survives the
|
||||
# mv and leaves Caddy (which runs as the `caddy` user, not root)
|
||||
# unable to traverse /opt/furtka/current/ when it tries to serve
|
||||
# the landing page. Open up to 755 so file_server can read.
|
||||
'chmod 755 "/opt/furtka/versions/$ver" && '
|
||||
'ln -sfn "/opt/furtka/versions/$ver" /opt/furtka/current'
|
||||
)
|
||||
systemctl_link = "systemctl link " + " ".join(
|
||||
|
|
@ -284,24 +289,23 @@ def _furtka_json_cmd(hostname):
|
|||
at runtime and renders the hostname chip from it. install_date + version
|
||||
ride along so the settings page can display them without hitting the
|
||||
status timer's refresh cycle.
|
||||
|
||||
Heredoc rather than base64 + sed — the previous version had two layers
|
||||
of quoting that archinstall's custom_commands shell-eval path parsed
|
||||
inconsistently, leaving this command as a silent no-op on some installs.
|
||||
The heredoc evaluates `$(date ...)` and `$(cat VERSION)` at chroot
|
||||
runtime and sidesteps the quoting hazard entirely. Hostname has already
|
||||
been validated by validate_step1.
|
||||
"""
|
||||
# Python-side JSON assembly keeps the shell command free of quote-escape
|
||||
# hazards. Hostname is validated up-front in validate_step1 so it's safe
|
||||
# to interpolate.
|
||||
body = json.dumps(
|
||||
{
|
||||
"hostname": hostname,
|
||||
"install_date": "__INSTALL_DATE__",
|
||||
"version": "__VERSION__",
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
return (
|
||||
"mkdir -p /var/lib/furtka && "
|
||||
+ _write_file_cmd("/var/lib/furtka/furtka.json", body)
|
||||
+ ' && sed -i "s/__INSTALL_DATE__/$(date -Iseconds)/;'
|
||||
's|__VERSION__|$(cat /opt/furtka/current/VERSION 2>/dev/null || echo dev)|"'
|
||||
" /var/lib/furtka/furtka.json"
|
||||
"cat > /var/lib/furtka/furtka.json <<EOF\n"
|
||||
"{\n"
|
||||
f' "hostname": "{hostname}",\n'
|
||||
' "install_date": "$(date -Iseconds)",\n'
|
||||
' "version": "$(cat /opt/furtka/current/VERSION 2>/dev/null || echo dev)"\n'
|
||||
"}\n"
|
||||
"EOF"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue