A fresh install creates an empty containers root but leaves the rootless daemon's own container state untouched. Restarting the daemon then runs its container-restore pass, which resurrects the previous install's containers — and Docker materialises each missing bind-mount source first, creating an empty DIRECTORY even where the mount is a file. That is the trigger behind the <app>.config stub directories: at 19:38:59 the daemon re-created every missing mount source for a container built 40 minutes earlier, runc then failed with "not a directory: Are you trying to mount a directory onto a file", and the abandoned stubs collided with the install's own copies 18 seconds later. Add dockerRemoveStrandedContainers, run right after the rootless daemon restart: remove containers whose compose project directory no longer exists, so the next restart has nothing to resurrect. Scoped to project directories under the LibrePortal containers root, so unrelated containers on the host are never touched, and gated on the daemon answering. Signed-off-by: librelad <librelad@digitalangels.vip>
242 lines
12 KiB
Bash
Executable File
242 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
installDockerRootless()
|
|
{
|
|
if [[ $CFG_DOCKER_INSTALL_TYPE == "rootless" ]]; then
|
|
isHeader "Install Docker Rootless"
|
|
|
|
#dockerComposeDownAllApps root;
|
|
#dockerServiceStop root;
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Installing System Requirements."
|
|
echo ""
|
|
|
|
local docker_install_user_id=$(id -u "$CFG_DOCKER_INSTALL_USER")
|
|
local docker_install_bashrc="/home/$CFG_DOCKER_INSTALL_USER/.bashrc"
|
|
|
|
isNotice "Fetching and installing the necessary packages...this may take a moment..."
|
|
local result; result=$(runSystem apt-get install -y apt-transport-https ca-certificates curl gnupg software-properties-common uidmap dbus-user-session fuse-overlayfs passt)
|
|
checkSuccess "Installing necessary packages"
|
|
|
|
local result; result=$(runSystem systemctl disable --now docker.service docker.socket)
|
|
checkSuccess "Disabling Docker service & Socket"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Installing slirp4netns."
|
|
echo ""
|
|
|
|
# slirp4netns — ensure it's installed via apt (idempotent: installs if missing,
|
|
# no-op if already present). We install from apt, so there's no point chasing the
|
|
# GitHub-latest release.
|
|
local result; result=$(runSystem apt-get install -y slirp4netns)
|
|
checkSuccess "Installing slirp4netns"
|
|
# `slirp4netns --version` prints several lines (version, commit, libslirp,
|
|
# SLIRP_CONFIG_VERSION_MAX); read only the first line, field 3 is the number.
|
|
installed_version=$(slirp4netns --version 2>/dev/null | head -n1 | awk '{print $3}')
|
|
isSuccessful "slirp4netns ${installed_version:-installed} is ready"
|
|
|
|
if [[ $(lsb_release -rs) == "10" ]]; then
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Updating the sysctl file for Updating Debian 10."
|
|
echo ""
|
|
if sudo grep -qs "kernel.unprivileged_userns_clone=1" $sysctl; then
|
|
isNotice "kernel.unprivileged_userns_clone=1 already exists in $sysctl"
|
|
else
|
|
local result; result=$(echo "kernel.unprivileged_userns_clone=1" | sudo tee -a $sysctl > /dev/null)
|
|
checkSuccess "Adding kernel.unprivileged_userns_clone=1 to $sysctl..."
|
|
local result; result=$(runSystem sysctl --system)
|
|
checkSuccess "Running runAsManager sysctl --system..."
|
|
fi
|
|
fi
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Update the .bashrc file."
|
|
echo ""
|
|
|
|
if ! grep -qF "# DOCKER ROOTLESS BASHRC START" "$docker_install_bashrc"; then
|
|
local result; result=$(echo '# DOCKER ROOTLESS BASHRC START' | sudo tee -a "$docker_install_bashrc" > /dev/null)
|
|
checkSuccess "Adding rootless header to .bashrc"
|
|
|
|
local result; result=$(echo 'export XDG_RUNTIME_DIR=/run/user/${UID}' | sudo tee -a "$docker_install_bashrc" > /dev/null)
|
|
checkSuccess "Adding export path to .bashrc"
|
|
|
|
local result; result=$(echo 'export PATH=/usr/bin:$PATH' | sudo tee -a "$docker_install_bashrc" > /dev/null)
|
|
checkSuccess "Adding export path to .bashrc"
|
|
|
|
local result; result=$(echo 'export DOCKER_HOST=unix:///run/user/${UID}/docker.sock' | sudo tee -a "$docker_install_bashrc" > /dev/null)
|
|
checkSuccess "Adding export DOCKER_HOST path to .bashrc"
|
|
|
|
local result; result=$(echo 'export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${UID}/bus"' | sudo tee -a "$docker_install_bashrc" > /dev/null)
|
|
checkSuccess "Adding export DBUS_SESSION_BUS_ADDRESS path to .bashrc"
|
|
|
|
local result; result=$(echo '# DOCKER ROOTLESS BASHRC END' | sudo tee -a "$docker_install_bashrc" > /dev/null)
|
|
checkSuccess "Adding rootless header to .bashrc"
|
|
|
|
isSuccessful "Added $CFG_DOCKER_INSTALL_USER to bashrc file"
|
|
else
|
|
isNotice "Rootless .bashrc already configured for $CFG_DOCKER_INSTALL_USER — skipping"
|
|
fi
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Setting up Rootless Docker."
|
|
echo ""
|
|
|
|
local result; result=$(runSystem loginctl enable-linger $CFG_DOCKER_INSTALL_USER)
|
|
checkSuccess "Adding automatic start (linger)"
|
|
|
|
# Install rootless Docker and enable the user service, but do NOT
|
|
# start it here. The rootless network override (override.conf,
|
|
# written further down) and the daemon-reload that picks it up
|
|
# haven't happened yet, so a start at this point comes up with no
|
|
# valid net/port-driver config and fails ("Job for docker.service
|
|
# failed") — a guaranteed, harmless first-start error that only
|
|
# noises the error report. The first real start is the
|
|
# `systemctl --user restart docker` below, once the override is in
|
|
# place.
|
|
rootless_install=$(cat <<EOF
|
|
curl -fsSL https://get.docker.com/rootless | sh && \
|
|
systemctl --user enable docker && \
|
|
exit
|
|
EOF
|
|
)
|
|
local result; result=$(dockerCommandRunInstallUser "$rootless_install")
|
|
checkSuccess "Setting up Rootless for $CFG_DOCKER_INSTALL_USER"
|
|
|
|
((menu_number++))
|
|
|
|
# The net namespace driver and the rootlesskit port driver are a matched
|
|
# pair — mixing them makes rootlesskit reject the config and the daemon
|
|
# silently never starts (this is why an earlier pasta+builtin attempt
|
|
# bricked the install). The user picks one network stack via
|
|
# CFG_ROOTLESS_NET; we derive its only valid port driver here:
|
|
# pasta -> implicit (fastest; propagates the real client IP)
|
|
# slirp4netns -> builtin (legacy fallback)
|
|
local rootless_net="${CFG_ROOTLESS_NET:-pasta}"
|
|
local rootless_port_driver
|
|
case "$rootless_net" in
|
|
pasta) rootless_port_driver="implicit" ;;
|
|
slirp4netns) rootless_port_driver="builtin" ;;
|
|
*)
|
|
isNotice "Unknown CFG_ROOTLESS_NET='$rootless_net'; falling back to pasta."
|
|
rootless_net="pasta"
|
|
rootless_port_driver="implicit"
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "---- $menu_number. Configuring rootless networking ($rootless_net + $rootless_port_driver port driver)."
|
|
echo ""
|
|
|
|
systemd_user_dir="/home/$CFG_DOCKER_INSTALL_USER/.config/systemd/user"
|
|
local result; result=$(dockerCommandRunInstallUser "mkdir -p $systemd_user_dir")
|
|
checkSuccess "Create the systemd user directory if it doesn't exist"
|
|
|
|
local result; result=$(dockerCommandRunInstallUser "mkdir -p $systemd_user_dir/docker.service.d")
|
|
checkSuccess "Create the docker.service.d directory if it doesn't exist"
|
|
|
|
override_conf_file="$systemd_user_dir/docker.service.d/override.conf"
|
|
local result; result=$(sudo touch $override_conf_file)
|
|
checkSuccess "Create the override.conf in docker.service.d"
|
|
|
|
# Resolve CFG_NETWORK_MTU to a concrete, path-safe value (auto = probe).
|
|
# This is the MTU the daemon PULLS over — a blind 1500 on a lower-path-MTU
|
|
# link (Qubes/NAT/VPN) black-holes large image-layer downloads (EOF).
|
|
local resolved_mtu; resolved_mtu="$(networkRedetectMtu)"
|
|
isNotice "Rootless uplink MTU: $resolved_mtu"
|
|
|
|
sudo bash -c "cat <<EOL > '$override_conf_file'
|
|
[Service]
|
|
Environment='DOCKERD_ROOTLESS_ROOTLESSKIT_NET=$rootless_net'
|
|
Environment='DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=$rootless_port_driver'
|
|
Environment='DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=$resolved_mtu'
|
|
EOL"
|
|
|
|
local result; result=$(sudo chown $CFG_DOCKER_INSTALL_USER:$CFG_DOCKER_INSTALL_USER $override_conf_file)
|
|
checkSuccess "Updating ownership for override.conf"
|
|
|
|
# Pasta needs explicit AppArmor permissions that the Debian-shipped
|
|
# passt profile doesn't include by default (ptrace_read on the
|
|
# rootlesskit child + a couple of /proc + /run paths). Skip silently
|
|
# when slirp4netns is selected — that path doesn't go through passt.
|
|
if [[ "$rootless_net" == "pasta" ]]; then
|
|
installRootlessApparmorForPasta
|
|
fi
|
|
|
|
# NOTE: we deliberately do NOT set "userland-proxy": false here. Disabling
|
|
# it makes rootless dockerd require br_netfilter
|
|
# (/proc/sys/net/bridge/bridge-nf-call-iptables), which isn't present in
|
|
# the rootless netns on Debian — the daemon then fails to create the
|
|
# default bridge and won't start. The userland proxy's lack of source-IP
|
|
# propagation doesn't matter here: apps sit behind Traefik, which carries
|
|
# the real client IP via X-Forwarded-For at L7.
|
|
|
|
local result; result=$(dockerCommandRunInstallUser "systemctl --user daemon-reload")
|
|
checkSuccess "Reload the systemd user manager configuration"
|
|
|
|
isNotice "Restarting docker service...this may take a moment..."
|
|
local result; result=$(dockerCommandRunInstallUser "systemctl --user restart docker")
|
|
checkSuccess "Reload the systemd user docker service"
|
|
|
|
# The restart above runs the daemon's container-restore pass, which
|
|
# resurrects containers left over from a previous install whose project
|
|
# directory this install already wiped. Clear them now so the next
|
|
# restart can't re-create their bind-mount sources as stub directories.
|
|
dockerRemoveStrandedContainers "loud"
|
|
|
|
local result; result=$(sudo cp $sysctl $sysctl.bak)
|
|
checkSuccess "Backing up sysctl file"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Setting up sysctl file to work with LetsEncrypt."
|
|
echo ""
|
|
|
|
# Update sysctl file
|
|
if ! grep -qsF "# DOCKER ROOTLESS SYSCTL START" "$sysctl"; then
|
|
|
|
local result; result=$(echo '# DOCKER ROOTLESS SYSCTL START' | sudo tee -a "$sysctl" > /dev/null)
|
|
checkSuccess "Adding rootless header to sysctl"
|
|
|
|
local result; result=$(echo 'net.ipv4.ip_unprivileged_port_start=0' | sudo tee -a "$sysctl" > /dev/null)
|
|
checkSuccess "Adding ip_unprivileged_port_start to sysctl"
|
|
|
|
local result; result=$(echo 'kernel.unprivileged_userns_clone=1' | sudo tee -a "$sysctl" > /dev/null)
|
|
checkSuccess "Adding unprivileged_userns_clone to sysctl"
|
|
|
|
local result; result=$(echo '# DOCKER ROOTLESS SYSCTL END' | sudo tee -a "$sysctl" > /dev/null)
|
|
checkSuccess "Adding rootless end to sysctl"
|
|
|
|
isSuccessful "Updated the sysctl with Docker Rootless configuration"
|
|
fi
|
|
|
|
# Enabling unprivileged user namespaces (needed for rootless) widens the
|
|
# kernel attack surface reachable by unprivileged users. Offset that by
|
|
# closing the surfaces that local-privilege-escalation chains lean on:
|
|
# kptr_restrict hides kernel pointers (info-leak primitives), ptrace_scope
|
|
# blocks cross-process ptrace (credential theft post-compromise), and
|
|
# bpf_jit_harden hardens the JIT against spraying. Written under
|
|
# /etc/sysctl.d/ so `sysctl --system` actually loads it — LibrePortal's
|
|
# own $sysctl path (/etc/sysctl/…) is non-standard and is NOT read by
|
|
# `sysctl --system`.
|
|
local hardening_conf="/etc/sysctl.d/99-libreportal-hardening.conf"
|
|
sudo bash -c "cat > '$hardening_conf'" <<'EOL'
|
|
# LibrePortal kernel LPE-surface hardening (paired with rootless unprivileged userns)
|
|
kernel.kptr_restrict=2
|
|
kernel.yama.ptrace_scope=1
|
|
net.core.bpf_jit_harden=2
|
|
EOL
|
|
checkSuccess "Writing kernel LPE-surface hardening to $hardening_conf"
|
|
|
|
local result; result=$(runSystem sysctl --system)
|
|
checkSuccess "Applying changes to sysctl"
|
|
|
|
menu_number=0
|
|
fi
|
|
}
|