fix(bootstrap): open up a nano-only config dir an older run left 0750

write_nano_config creates a missing /etc/felis as 0755, but it left
an existing one alone. On a nano-only host an older installer made
that directory with a bare mkdir -p, so under a root umask of 027 it
is 0750. The DynamicUser unit cannot search it, so felis-nano cannot
read its config, and a re-run stops at the service check instead of
repairing the directory.

An existing directory is now set to 0755 unless it holds the full
install's secrets.env or bootstrap.done. The full install locks the
directory to 0700 and writes secrets.env right after, so its directory
keeps that mode, and install_nano_service still reports the lockout
rather than this widening it. The mode cases run only where chmod
works; on a filesystem that ignores it the harness skips them.
This commit is contained in:
flyemoji committed 2026-09-22 13:54:18 +09:00
1 parent 2458ee1722
commit cf65ffdae5
2 files changed
+21 -9

No files matched your search

+9 -4
View File
@@ -2358,10 +2358,15 @@ acquire_nano_binary() {
write_nano_config() { write_nano_config() {
local target="${STATE_DIR}/felis.toml" local target="${STATE_DIR}/felis.toml"
# The unit is a DynamicUser, so it can read felis.toml only if it can search this # The unit is a DynamicUser, so it can read felis.toml only if it can search this
# directory. The mode is explicit because a hardened root umask (027) would leave it 0750. # directory. The mode is explicit because a hardened root umask (027) would leave it 0750,
# An existing directory keeps its mode: the full install locks it to 0700 for its secrets, # which is what older installers did to nano-only hosts. Only the full install keeps its
# and install_nano_service reports that lockout rather than this widening it. # own 0700: that directory holds secrets, and install_nano_service reports the lockout
[ -d "$STATE_DIR" ] || mkdir -p -m 0755 "$STATE_DIR" # rather than this widening it.
if [ ! -d "$STATE_DIR" ]; then
mkdir -p -m 0755 "$STATE_DIR"
elif [ ! -e "$SECRETS_ENV" ] && [ ! -e "$BOOTSTRAP_DONE" ]; then
chmod 0755 "$STATE_DIR"
fi
if [ -e "$target" ]; then if [ -e "$target" ]; then
ok "config already present at ${target}; leaving it (edit it to add [[auth_source]] roots)" ok "config already present at ${target}; leaving it (edit it to add [[auth_source]] roots)"
return 0 return 0
+12 -5
View File
@@ -194,16 +194,16 @@ done
# --- write_nano_config leaves the unit able to read its config --------------------------- # --- write_nano_config leaves the unit able to read its config ---------------------------
# felis-nano runs as a DynamicUser, so the directory must be searchable by others under a # felis-nano runs as a DynamicUser, so the directory must be searchable by others under a
# hardened umask too -- but an existing one, which the full install locks to 0700 for its # hardened umask too, including one an older installer left at 0750 -- but the full
# secrets, must not be widened. # install's, locked to 0700 for its secrets, must not be widened.
wblock="$(awk '/^write_nano_config\(\) \{/,/^}/' "$BS")" wblock="$(awk '/^write_nano_config\(\) \{/,/^}/' "$BS")"
[ -n "$wblock" ] || { echo "FAIL: no write_nano_config found in $BS"; exit 1; } [ -n "$wblock" ] || { echo "FAIL: no write_nano_config found in $BS"; exit 1; }
[ "$(printf '%s\n' "$wblock" | wc -l)" -lt 40 ] \ [ "$(printf '%s\n' "$wblock" | wc -l)" -lt 50 ] \
|| { echo "FAIL: the extracted block is not the function -- did its closing brace move?"; exit 1; } || { echo "FAIL: the extracted block is not the function -- did its closing brace move?"; exit 1; }
run_nano_config() { # state-dir run_nano_config() { # state-dir
STATE_DIR="$1" bash -c 'umask 027 STATE_DIR="$1" SECRETS_ENV="$1/secrets.env" BOOTSTRAP_DONE="$1/bootstrap.done" bash -c 'umask 027
ok() { printf "OK: %s\n" "$*"; } ok() { printf "OK: %s\n" "$*"; }
'"$wblock"' '"$wblock"'
write_nano_config' write_nano_config'
@@ -213,8 +213,15 @@ mkdir "$sdir/probe" && chmod 0700 "$sdir/probe"
if [ "$(stat -c %a "$sdir/probe")" = 700 ]; then if [ "$(stat -c %a "$sdir/probe")" = 700 ]; then
run_nano_config "$sdir/nano" >/dev/null run_nano_config "$sdir/nano" >/dev/null
expect "a fresh config dir is searchable under umask 027" 755 "$(stat -c %a "$sdir/nano")" expect "a fresh config dir is searchable under umask 027" 755 "$(stat -c %a "$sdir/nano")"
mkdir "$sdir/old" && chmod 0750 "$sdir/old"
run_nano_config "$sdir/old" >/dev/null
expect "a nano-only 0750 dir is opened up" 755 "$(stat -c %a "$sdir/old")"
: > "$sdir/probe/secrets.env"
run_nano_config "$sdir/probe" >/dev/null run_nano_config "$sdir/probe" >/dev/null
expect "an existing 0700 dir is not widened" 700 "$(stat -c %a "$sdir/probe")" expect "a dir holding the full install's secrets is not widened" 700 "$(stat -c %a "$sdir/probe")"
mkdir "$sdir/done" && chmod 0700 "$sdir/done" && : > "$sdir/done/bootstrap.done"
run_nano_config "$sdir/done" >/dev/null
expect "a dir marked as a full install is not widened" 700 "$(stat -c %a "$sdir/done")"
else else
echo "SKIP directory modes: this filesystem ignores chmod" echo "SKIP directory modes: this filesystem ignores chmod"
fi fi