#!/bin/bash # Service-specific rootless Docker installation # Implements DEPLOYMENT_STRATEGY.md conventions set -euo pipefail # Required parameter SERVICE_USER="${1:-}" log() { echo "[Docker Install] $*" } error() { log "ERROR: $*" >&2 exit 1 } usage() { echo "Usage: $0 " echo " service_user: User to install rootless Docker for (e.g., tinsnip-test)" echo "" echo "Example: $0 tinsnip-test" exit 1 } # Helper function to run systemctl with fallback if systemd not available systemctl_user_safe() { local username="$1" shift local systemctl_command="$*" sudo -u "$username" -i bash << EOF if systemctl --user status >/dev/null 2>&1; then systemctl --user $systemctl_command exit 0 else exit 1 fi EOF local result=$? if [[ $result -eq 1 ]]; then log " ⚠️ Systemd not available, skipping: systemctl --user $systemctl_command" fi return $result } install_dependencies() { log "Installing rootless Docker dependencies..." if sudo apt-get update -qq >/dev/null 2>&1; then log " Package lists updated" else error "Failed to update package lists" fi if sudo apt-get install -y uidmap dbus-user-session systemd-container fuse-overlayfs slirp4netns >/dev/null 2>&1; then log " Docker dependencies installed" else error "Failed to install Docker dependencies" fi } enable_privileged_ports() { log "Enabling privileged port binding for rootless Docker..." # Find rootlesskit binary path local rootlesskit_path if rootlesskit_path=$(which rootlesskit 2>/dev/null); then log "Setting CAP_NET_BIND_SERVICE on rootlesskit..." sudo setcap cap_net_bind_service=ep "$rootlesskit_path" log "Privileged ports enabled" else log "WARNING: rootlesskit not found, will enable after Docker installation" fi } setup_docker_environment() { local username="$1" log "Setting up Docker environment for $username" # Find the service .env file (should exist from mount_nas.sh) local service_env_file="/mnt/$username/.env" if [[ ! -f "$service_env_file" ]]; then log "WARNING: Service .env file not found at $service_env_file" log "Docker environment will not be persistent across hosts" return 1 fi # Detect if we need systemd or manual setup if sudo -u "$username" -i bash -c "systemctl --user status >/dev/null 2>&1"; then # Systemd environment local xdg_runtime="/run/user/\$(id -u)" local docker_host="unix:///run/user/\$(id -u)/docker.sock" # Start Docker daemon if not running sudo -u "$username" -i bash << 'EOF' if ! pgrep -x dockerd >/dev/null; then systemctl --user start docker.service fi EOF else # Manual/non-systemd environment local xdg_runtime="/home/$username/.docker/run" local docker_host="unix:///home/$username/.docker/run/docker.sock" # Create runtime directory first sudo -u "$username" -i bash -c 'mkdir -p "$HOME/.docker/run"' # Start Docker daemon if not running (non-systemd) sudo -u "$username" -i bash << 'EOF' if ! pgrep -x dockerd >/dev/null; then export XDG_RUNTIME_DIR="$HOME/.docker/run" export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" dockerd-rootless.sh > ~/.docker/docker.log 2>&1 & sleep 5 fi EOF fi # Append Docker environment variables to service .env file log "Adding Docker environment to service .env file" sudo -u "$username" -i bash << EOF # Remove any existing Docker environment variables from .env grep -v "^XDG_RUNTIME_DIR=\|^DOCKER_HOST=\|^PATH=" "$service_env_file" > "${service_env_file}.tmp" && mv "${service_env_file}.tmp" "$service_env_file" # Append Docker environment variables cat >> "$service_env_file" << DOCKER_EOF # Docker rootless environment XDG_RUNTIME_DIR=$xdg_runtime DOCKER_HOST=$docker_host PATH=/home/$username/bin:\$PATH DOCKER_EOF EOF log "Docker environment added to $service_env_file" } install_rootless_docker() { local username="$1" log "Installing rootless Docker for user: $username" # Lingering is handled in main function # Check if already installed if sudo -u "$username" -i bash -c "command -v docker &>/dev/null"; then log "Docker already installed for $username" # Still need to ensure environment is set up setup_docker_environment "$username" return 0 fi # Install rootless Docker sudo -u "$username" -i bash << 'EOF' # Set up environment export XDG_RUNTIME_DIR=/run/user/$(id -u) # Download and install rootless Docker curl -fsSL https://get.docker.com/rootless | sh # Add Docker to PATH (insert at top to avoid interactive shell return) add_to_bashrc_top() { local var="$1" if ! grep -q "^$var" ~/.bashrc 2>/dev/null; then echo "$var" | cat - ~/.bashrc > ~/.bashrc.tmp && mv ~/.bashrc.tmp ~/.bashrc fi } add_to_bashrc_top 'export PATH=$HOME/bin:$PATH' add_to_bashrc_top 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' add_to_bashrc_top 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' # Source the new PATH source ~/.bashrc # Enable systemd user session if needed if ! systemctl --user status >/dev/null 2>&1; then echo " ⚠️ Systemd user session not available, using manual start..." # Set up for manual Docker start (insert at top to avoid interactive shell return) add_to_bashrc_top 'export XDG_RUNTIME_DIR="$HOME/.docker/run"' add_to_bashrc_top 'export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"' # Create XDG_RUNTIME_DIR mkdir -p "$HOME/.docker/run" export XDG_RUNTIME_DIR="$HOME/.docker/run" export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" # Start Docker manually dockerd-rootless.sh & sleep 5 else # Systemd is available loginctl enable-linger $(whoami) systemctl --user enable docker.service systemctl --user start docker.service fi # Wait for Docker to start sleep 5 # Test Docker if docker version &>/dev/null; then echo " Docker rootless installation successful" else echo " ❌ Docker installation failed" exit 1 fi EOF if [[ $? -eq 0 ]]; then log "Rootless Docker installation completed for $username" else error "Failed to install Docker for $username" fi # Enable privileged ports after installation enable_privileged_ports # Restart Docker to apply capability changes log "Restarting Docker to apply configuration..." if systemctl_user_safe "$username" "restart docker.service"; then log " Docker service restarted via systemctl" else log " Systemctl restart failed, but Docker should still be functional" fi } configure_docker() { local username="$1" log "Configuring Docker for $username..." # Create Docker config directory sudo -u "$username" mkdir -p "/home/$username/.docker" # Create daemon.json with optimized settings # Always disable cgroup management for rootless Docker to avoid systemd delegation issues # This is the most reliable approach for tinsnip deployments sudo -u "$username" tee "/home/$username/.docker/daemon.json" > /dev/null << 'EOF' { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "storage-driver": "overlay2", "exec-opts": ["native.cgroupdriver=none"] } EOF # Check if Docker is already working before attempting restart log "Checking Docker status before configuration restart..." if sudo -u "$username" -i bash -c "docker version &>/dev/null"; then log " Docker is already working, skipping restart to avoid issues" else log " Docker not responding, attempting restart..." if systemctl_user_safe "$username" "restart docker.service"; then log " Docker service restarted via systemctl" # Give Docker time to start up after restart log "Waiting for Docker to start..." sleep 10 else log " Systemctl restart failed, Docker may already be running in non-systemd mode" fi fi log "Docker configuration complete" } verify_installation() { local username="$1" log "Verifying Docker installation for $username..." # Use the service .env file (source of truth) local service_env_file="/mnt/$username/.env" if sudo -u "$username" bash -c "source '$service_env_file' && docker version &>/dev/null"; then log "Docker verification successful!" local docker_version docker_version=$(sudo -u "$username" bash -c "source '$service_env_file' && docker --version") log "Installed: $docker_version" else error "Docker verification failed for $username" fi log "Service available for user: $username" log "Privileged ports: enabled" } main() { # Validate parameters if [[ -z "$SERVICE_USER" ]]; then usage fi # Check if user exists if ! id "$SERVICE_USER" &>/dev/null; then error "User $SERVICE_USER does not exist. Create the user first." fi log "Installing rootless Docker for service user: $SERVICE_USER" # Install dependencies install_dependencies # Enable systemd lingering for the service user log "Enabling systemd lingering for $SERVICE_USER" if sudo loginctl enable-linger "$SERVICE_USER" 2>/dev/null; then log " Systemd lingering enabled" else log " ⚠️ Could not enable systemd lingering (may not be supported)" fi # Install rootless Docker for the service user install_rootless_docker "$SERVICE_USER" # Configure Docker configure_docker "$SERVICE_USER" # Verify installation verify_installation "$SERVICE_USER" log "" log "Rootless Docker installation complete!" log "User: $SERVICE_USER" log "To test: sudo -u $SERVICE_USER docker run hello-world" } main "$@"