fix(install): make one-shot retry() retry, and drop root grants on user files (#606)

retry() in one-shot-install.sh used `if ! "$@"; then status=$?`, where $? is
the status of the negation -- always 0. A failed command was never retried
and retry() reported success, so a failed `git clone` carried on until a
later check noticed the missing checkout. It now retries (3 attempts) and
returns the command's status. The two apt steps stay non-fatal: warning and
continuing is what they effectively did before, and making them fatal would
stop installs that work today. A clone that keeps failing stops the install,
as it already did, just sooner and with the one-shot's own error message.

Both installers granted the web user NOPASSWD root on display_controller.py,
start_display.sh and stop_display.sh. Those files are owned by the user after
Step 11's chown, so the grant let the web user rewrite them and run them as
root, and nothing ever ran them through sudo. Removed from both installers,
with a test that every project file granted as root is a root-owned
fix_perms helper.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-23 10:34:20 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 342e9164b8
commit e1ce7189f1
5 changed files with 102 additions and 25 deletions
+65
View File
@@ -0,0 +1,65 @@
"""retry() in scripts/install/one-shot-install.sh retries, and reports failure.
It used `if ! "$@"; then status=$?`, where $? is the status of the negation,
always 0: a failed command was never retried and retry() returned success,
so a failed `git clone` carried on until a later check noticed the missing
checkout. The apt steps now retry for real but stay non-fatal, as they
effectively were; a clone that keeps failing stops the install.
"""
import re
import subprocess
import sys
from pathlib import Path
import pytest
ONE_SHOT = Path(__file__).resolve().parent.parent / "scripts" / "install" / "one-shot-install.sh"
pytestmark = pytest.mark.skipif(
not sys.platform.startswith("linux"), reason="runs the installer's bash under Linux"
)
def _function(name):
text = ONE_SHOT.read_text(encoding="utf-8")
m = re.search(rf"^{name}\(\) \{{\n.*?^\}}\n", text, re.S | re.M)
assert m, f"{name}() not found in one-shot-install.sh"
return m.group(0)
def _run(snippet):
script = (
"set -Eeuo pipefail\n"
"trap 'echo ERR_TRAP_FIRED >&2; exit 99' ERR\n"
"print_error() { echo \"E: $*\" >&2; }\n"
"print_warning() { echo \"W: $*\" >&2; }\n"
"sleep() { :; }\n"
f"{_function('retry')}\n"
f"{snippet}\n"
)
return subprocess.run(["bash", "-c", script], capture_output=True, text=True)
def test_failure_is_retried_and_reported():
r = _run("n=0; f() { n=$((n+1)); return 7; }\n"
"if retry f; then echo OK; else echo \"FAILED $? after $n\"; fi")
assert r.stdout.strip() == "FAILED 7 after 3", r.stdout + r.stderr
def test_success_on_a_later_attempt():
r = _run("n=0; f() { n=$((n+1)); [ $n -ge 2 ]; }\n"
"retry f && echo \"OK after $n\"")
assert r.stdout.strip() == "OK after 2", r.stdout + r.stderr
def test_a_plain_call_that_keeps_failing_stops_the_script():
r = _run("retry false\necho SHOULD_NOT_RUN")
assert "SHOULD_NOT_RUN" not in r.stdout
assert "ERR_TRAP_FIRED" in r.stderr
def test_apt_steps_stay_non_fatal():
text = ONE_SHOT.read_text(encoding="utf-8")
for line in text.splitlines():
if re.search(r"\bretry (sudo )?apt-get ", line):
assert "||" in line, f"apt step would now abort the install: {line.strip()}"
+15
View File
@@ -129,3 +129,18 @@ def test_every_granted_helper_is_hardened_in_first_time_install_after_chown():
assert loop.start() > project_chown, (
"helper hardening runs before Step 11's project-wide chown, which undoes it")
assert _granted_helpers() <= set(loop.group(1).split())
def test_no_grant_runs_a_file_the_web_user_can_edit():
"""Every project file granted as root must be a fix_perms helper, which
both installers chown root:root (checked above). Anything else under the
project root is owned by the user after Step 11's chown, so a NOPASSWD
rule for it lets the web user rewrite the file and run it as root. The
grants for display_controller.py, start_display.sh and stop_display.sh
were exactly that, and nothing ever ran them through sudo."""
for installer in (FIRST_TIME, CONFIGURE):
for _, command in _grants(installer):
for token in command.split():
if token.startswith("$PROJECT_ROOT/"):
assert token.startswith("$PROJECT_ROOT/scripts/fix_perms/"), (
f"{installer.name} grants root on a user-owned file: {command}")