slirp4netns --version prints multiple lines (version, commit, libslirp,
SLIRP_CONFIG_VERSION_MAX). The old 'awk {print $2}' ran on every line and
also picked the literal word 'version' from line 1, producing a multi-line
blob that leaked into the 'is outdated' notice. Read only the first line and
take field 3 (the actual number), strip the leading v from the GitHub tag so
the comparison is meaningful, and skip the check if the tag fetch fails.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
244 lines
12 KiB
Bash
Executable File
244 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 update and install
|
|
if ! command -v slirp4netns &> /dev/null; then
|
|
isNotice "slirp4netns is not installed. Installing..."
|
|
local result; result=$(runSystem apt-get install -y slirp4netns)
|
|
checkSuccess "Installing slirp4netns"
|
|
else
|
|
isNotice "slirp4netns is already installed"
|
|
# `slirp4netns --version` prints several lines (version, commit, libslirp,
|
|
# SLIRP_CONFIG_VERSION_MAX); read only the first line and take the version field.
|
|
installed_version=$(slirp4netns --version | head -n1 | awk '{print $3}')
|
|
latest_version=$(curl -s https://api.github.com/repos/rootless-containers/slirp4netns/releases/latest | grep tag_name | cut -d '"' -f 4)
|
|
latest_version=${latest_version#v} # GitHub tags are vX.Y.Z; strip the v to compare
|
|
if [[ -n "$latest_version" && "$installed_version" != "$latest_version" ]]; then
|
|
isNotice "slirp4netns version $installed_version is outdated."
|
|
isNotice "Installing version $latest_version..."
|
|
local result; result=$(runSystem apt-get update)
|
|
checkSuccess "Updating apt packages"
|
|
local result; result=$(runSystem apt-get install -y slirp4netns)
|
|
checkSuccess "Installing slirp4netns"
|
|
else
|
|
isSuccessful "slirp4netns version $installed_version is up to date"
|
|
fi
|
|
fi
|
|
|
|
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"
|
|
|
|
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=$CFG_NETWORK_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"
|
|
|
|
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
|
|
}
|