From 5c928fe9c067451259809c96f3daea5d38934a57 Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 23 May 2026 20:35:18 +0100 Subject: [PATCH] feat(privilege): mode-aware privileged-op helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single place that decides how a privileged op runs by Docker mode: - runFileOp / runFileWrite: /docker data-plane ops — rooted uses sudo (identical to today), rootless runs as the unprivileged install user (no root). - runSystem: genuine system-admin ops, sudo in both modes, funnelled here so it can later be confined to a scoped sudoers allowlist. Call sites converted to these are byte-for-byte unchanged under rooted, so existing/live boxes can't regress; rootless gets the de-privileged path. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- scripts/docker/command/run_privileged.sh | 46 +++++++++++++++++++++ scripts/source/files/arrays/files_docker.sh | 1 + 2 files changed, 47 insertions(+) create mode 100644 scripts/docker/command/run_privileged.sh diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh new file mode 100644 index 0000000..861ceab --- /dev/null +++ b/scripts/docker/command/run_privileged.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Mode-aware privileged operations. +# +# The privilege a file operation needs depends on the Docker mode: +# rooted — app/container files under /docker are root-owned, so ops run via +# sudo. This is byte-for-byte the historical behaviour. +# rootless — those files are owned by the unprivileged Docker install user, so +# ops run AS that user over the existing SSH channel and need no +# root at all. +# Centralising the branch here means each call site is written once and is +# correct in both modes, and rooted installs (incl. live boxes) are unchanged. + +# Run a /docker data-plane command — mkdir/chown/rm/cp/mv/find/sqlite3/etc. on +# app or container files. +# rooted -> sudo +# rootless -> run as the Docker install user (no sudo) +# Note: for stdin-fed writes (e.g. `… | sudo tee file`) use runFileWrite below; +# this helper is for self-contained commands. +runFileOp() { + if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then + dockerCommandRunInstallUser "$*" + else + sudo "$@" + fi +} + +# Write stdin to a path with the right privilege (replaces `… | sudo tee path`). +# rooted -> sudo tee +# rootless -> tee as the Docker install user +# Usage: some_command | runFileWrite /path/to/file +runFileWrite() { + local dest="$1" + if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then + dockerCommandRunInstallUser "tee '$dest' >/dev/null" + else + sudo tee "$dest" >/dev/null + fi +} + +# Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc +# edits). Needs real root in both modes; kept as sudo and funnelled through one +# place so it can later be confined to a scoped sudoers allowlist. +runSystem() { + sudo "$@" +} diff --git a/scripts/source/files/arrays/files_docker.sh b/scripts/source/files/arrays/files_docker.sh index 2995f27..0390b8c 100755 --- a/scripts/source/files/arrays/files_docker.sh +++ b/scripts/source/files/arrays/files_docker.sh @@ -31,6 +31,7 @@ docker_scripts=( "docker/checks/running_for_user.sh" "docker/command/docker_run_install.sh" "docker/command/docker_run.sh" + "docker/command/run_privileged.sh" "docker/compose/copy_build_context.sh" "docker/compose/restart_after_update.sh" "docker/compose/setup_compose_yml.sh"