fix(install): establish container layer in root phase (real fix for scan noise)

Reverts the 2>/dev/null band-aids and fixes the root cause. The
manager-run install boot scans app configs under /docker/containers AS
the container user (runFileOp). But init.sh's initFolders creates that
dir manager-owned, and the handover to the container user happened later
(start_preinstall), AFTER the boot scans — so the scans ran as the
container user against a dir it didn't own yet: "find:
'/docker/containers/': Permission denied" (cosmetic; the dir is empty
that early, but it's the wrong ownership at the wrong time).

Add initContainerLayer() to init.sh's root phase (after initGIT +
initUpdateConfigs, before the manager-run handoff): rootless-only, it
creates the docker-install user if missing and chowns /docker/containers
to it (751). The later rootless setup is now idempotent — it finds the
user existing and just (re)asserts its password + daemon config (moved
updateDockerInstallPassword out of the create-only branch). Rooted is
unaffected (containers stay manager-owned, which the manager reads).

Result: by the time the boot scans run, /docker/containers is owned by
the user doing the scanning — no permission error, nothing suppressed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-24 22:53:11 +01:00
parent cf87cbeb0f
commit cdb2fc633d
4 changed files with 46 additions and 11 deletions

40
init.sh
View File

@ -830,6 +830,45 @@ initFolders()
isSuccessful "All folders have been created."
}
# Establish the rootless container layer — the docker-install user and ownership
# of /docker/containers — during the ROOT phase, BEFORE the manager-run install
# boots. That boot scans app configs under /docker/containers AS the container
# user (runFileOp), so if the dir is still manager-owned (as initFolders leaves
# it) the scan errors with "Permission denied". Handing the dir to the container
# user here means the scan reads a dir it owns. Rootless-only — rooted keeps
# containers manager/root-owned, which the manager reads fine. Idempotent: the
# later rootless setup finds the user existing and just (re)asserts its password
# + daemon config. Runs after initGIT (config present) + initFolders (dir present).
initContainerLayer()
{
local cfg="$configs_dir/general/general_docker_install"
local dtype duser
dtype=$(grep -h '^CFG_DOCKER_INSTALL_TYPE=' "$cfg" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
[[ "$dtype" != "rootless" ]] && return 0
duser=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$cfg" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
duser="${duser:-dockerinstall}"
isHeader "Container Layer Setup"
if id "$duser" &>/dev/null; then
isSuccessful "Container user '$duser' already exists."
else
# -m + the system login.defs SUB_UID/GID defaults assign its subordinate
# uid/gid ranges (needed for rootless). The later rootless setup sees it
# existing and configures the daemon/linger/password.
sudo useradd -m -s /bin/bash -d "/home/$duser" "$duser" 2>/dev/null
isSuccessful "Created container user '$duser'."
fi
# Hand containers/ to the container user (it owns per-app data in rootless) so
# the manager-run startup config scans can read it. 751: owner full; the
# manager (other) can traverse in to known paths (it lists/writes via runFileOp).
if [[ -d "$containers_dir" ]]; then
sudo chown "$duser:$duser" "$containers_dir"
sudo chmod 751 "$containers_dir"
isSuccessful "containers/ handed to '$duser'."
fi
}
setupConfigsFromRepo()
{
isNotice "Setting up configuration files from repository..."
@ -1468,6 +1507,7 @@ else
initRootHelpers
initLibrePortalCommand
initUpdateConfigs
initContainerLayer
completeInitMessage
elif [[ "$param1" == "uninstall" ]]; then
runFullUninstall

View File

@ -9,10 +9,7 @@ checkApplicationsConfigFilesMissingVariables()
app=$(basename "$live" .config)
remote="$install_containers_dir$app/$app.config"
reconcileConfigFile "$live" "$remote"
# 2>/dev/null: before the docker-type config loads (early install) runFileOp
# falls back to the manager, which can't list the container-owned containers/
# dir — harmless "Permission denied" on a best-effort reconcile (no apps yet).
done < <(runFileOp find "$containers_dir" -maxdepth 2 -type f -name '*.config' ! -name '*.bak' 2>/dev/null)
done < <(runFileOp find "$containers_dir" -maxdepth 2 -type f -name '*.config' ! -name '*.bak')
isSuccessful "Application config reconciliation completed."
}

View File

@ -14,7 +14,10 @@ installDockerRootlessUser()
# rootless). Run unmasked so checkSuccess sees real failures.
runSystem useradd -m -s /bin/bash -d "/home/$CFG_DOCKER_INSTALL_USER" "$CFG_DOCKER_INSTALL_USER"
checkSuccess "Creating $CFG_DOCKER_INSTALL_USER User."
updateDockerInstallPassword;
fi
# (Re)assert the password regardless — the user may have been pre-created
# in init.sh's root phase (so /docker/containers ownership is ready before
# the manager-run boot scans), where the password isn't set.
updateDockerInstallPassword;
fi
}

View File

@ -61,12 +61,7 @@ sourceScanFiles()
source "$file"
# echo "$load_type FILE $file"
fi
# 2>/dev/null: early in an install the docker-type config isn't loaded
# yet, so runFileOp falls back to the manager, which can't list the
# container-owned containers/ dir — a harmless "Permission denied" on a
# best-effort scan (no app configs exist yet). Suppress that noise; the
# -print0 output still flows.
done < <($scan_op find "$folder_dir" -maxdepth 3 -type d \( -name 'resources' \) -prune -o -type f -name "$file_pattern" -print0 2>/dev/null)
done < <($scan_op find "$folder_dir" -maxdepth 3 -type d \( -name 'resources' \) -prune -o -type f -name "$file_pattern" -print0)
fi
# Load the categories from the file into an array