homelab infrastructure services
at fix-docker-install 337 lines 10 kB view raw
1#!/bin/bash 2 3# Service-specific rootless Docker installation 4# Implements DEPLOYMENT_STRATEGY.md conventions 5 6set -euo pipefail 7 8# Required parameter 9SERVICE_USER="${1:-}" 10 11log() { 12 echo "[Docker Install] $*" 13} 14 15error() { 16 log "ERROR: $*" >&2 17 exit 1 18} 19 20usage() { 21 echo "Usage: $0 <service_user>" 22 echo " service_user: User to install rootless Docker for (e.g., tinsnip-test)" 23 echo "" 24 echo "Example: $0 tinsnip-test" 25 exit 1 26} 27 28# Helper function to run systemctl with fallback if systemd not available 29systemctl_user_safe() { 30 local username="$1" 31 shift 32 local systemctl_command="$*" 33 34 sudo -u "$username" -i bash << EOF 35 if systemctl --user status >/dev/null 2>&1; then 36 systemctl --user $systemctl_command 37 exit 0 38 else 39 exit 1 40 fi 41EOF 42 43 local result=$? 44 if [[ $result -eq 1 ]]; then 45 log " ⚠️ Systemd not available, skipping: systemctl --user $systemctl_command" 46 fi 47 48 return $result 49} 50 51install_dependencies() { 52 log "Installing rootless Docker dependencies..." 53 54 if sudo apt-get update -qq >/dev/null 2>&1; then 55 log " Package lists updated" 56 else 57 error "Failed to update package lists" 58 fi 59 60 if sudo apt-get install -y uidmap dbus-user-session systemd-container fuse-overlayfs slirp4netns >/dev/null 2>&1; then 61 log " Docker dependencies installed" 62 else 63 error "Failed to install Docker dependencies" 64 fi 65} 66 67enable_privileged_ports() { 68 log "Enabling privileged port binding for rootless Docker..." 69 70 # Find rootlesskit binary path 71 local rootlesskit_path 72 if rootlesskit_path=$(which rootlesskit 2>/dev/null); then 73 log "Setting CAP_NET_BIND_SERVICE on rootlesskit..." 74 sudo setcap cap_net_bind_service=ep "$rootlesskit_path" 75 log "Privileged ports enabled" 76 else 77 log "WARNING: rootlesskit not found, will enable after Docker installation" 78 fi 79} 80 81setup_docker_environment() { 82 local username="$1" 83 84 log "Setting up Docker environment for $username" 85 86 # Find the service .env file (should exist from mount_nas.sh) 87 local service_env_file="/mnt/$username/.env" 88 89 if [[ ! -f "$service_env_file" ]]; then 90 log "WARNING: Service .env file not found at $service_env_file" 91 log "Docker environment will not be persistent across hosts" 92 return 1 93 fi 94 95 # Detect if we need systemd or manual setup 96 if sudo -u "$username" -i bash -c "systemctl --user status >/dev/null 2>&1"; then 97 # Systemd environment 98 local xdg_runtime="/run/user/\$(id -u)" 99 local docker_host="unix:///run/user/\$(id -u)/docker.sock" 100 101 # Start Docker daemon if not running 102 sudo -u "$username" -i bash << 'EOF' 103if ! pgrep -x dockerd >/dev/null; then 104 systemctl --user start docker.service 105fi 106EOF 107 else 108 # Manual/non-systemd environment 109 local xdg_runtime="/home/$username/.docker/run" 110 local docker_host="unix:///home/$username/.docker/run/docker.sock" 111 112 # Create runtime directory first 113 sudo -u "$username" -i bash -c 'mkdir -p "$HOME/.docker/run"' 114 115 # Start Docker daemon if not running (non-systemd) 116 sudo -u "$username" -i bash << 'EOF' 117if ! pgrep -x dockerd >/dev/null; then 118 export XDG_RUNTIME_DIR="$HOME/.docker/run" 119 export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" 120 dockerd-rootless.sh > ~/.docker/docker.log 2>&1 & 121 sleep 5 122fi 123EOF 124 fi 125 126 # Append Docker environment variables to service .env file 127 log "Adding Docker environment to service .env file" 128 sudo -u "$username" -i bash << EOF 129# Remove any existing Docker environment variables from .env 130grep -v "^XDG_RUNTIME_DIR=\|^DOCKER_HOST=\|^PATH=" "$service_env_file" > "${service_env_file}.tmp" && mv "${service_env_file}.tmp" "$service_env_file" 131 132# Append Docker environment variables 133cat >> "$service_env_file" << DOCKER_EOF 134 135# Docker rootless environment 136XDG_RUNTIME_DIR=$xdg_runtime 137DOCKER_HOST=$docker_host 138PATH=/home/$username/bin:\$PATH 139DOCKER_EOF 140EOF 141 142 log "Docker environment added to $service_env_file" 143} 144 145install_rootless_docker() { 146 local username="$1" 147 148 log "Installing rootless Docker for user: $username" 149 150 # Lingering is handled in main function 151 152 # Check if already installed 153 if sudo -u "$username" -i bash -c "command -v docker &>/dev/null"; then 154 log "Docker already installed for $username" 155 # Still need to ensure environment is set up 156 setup_docker_environment "$username" 157 return 0 158 fi 159 160 # Install rootless Docker 161 sudo -u "$username" -i bash << 'EOF' 162# Set up environment 163export XDG_RUNTIME_DIR=/run/user/$(id -u) 164 165# Download and install rootless Docker 166curl -fsSL https://get.docker.com/rootless | sh 167 168# Add Docker to PATH (insert at top to avoid interactive shell return) 169add_to_bashrc_top() { 170 local var="$1" 171 if ! grep -q "^$var" ~/.bashrc 2>/dev/null; then 172 echo "$var" | cat - ~/.bashrc > ~/.bashrc.tmp && mv ~/.bashrc.tmp ~/.bashrc 173 fi 174} 175 176add_to_bashrc_top 'export PATH=$HOME/bin:$PATH' 177add_to_bashrc_top 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' 178add_to_bashrc_top 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' 179 180# Source the new PATH 181source ~/.bashrc 182 183# Enable systemd user session if needed 184if ! systemctl --user status >/dev/null 2>&1; then 185 echo " ⚠️ Systemd user session not available, using manual start..." 186 # Set up for manual Docker start (insert at top to avoid interactive shell return) 187 add_to_bashrc_top 'export XDG_RUNTIME_DIR="$HOME/.docker/run"' 188 add_to_bashrc_top 'export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"' 189 190 # Create XDG_RUNTIME_DIR 191 mkdir -p "$HOME/.docker/run" 192 export XDG_RUNTIME_DIR="$HOME/.docker/run" 193 export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" 194 195 # Start Docker manually 196 dockerd-rootless.sh & 197 sleep 5 198else 199 # Systemd is available 200 loginctl enable-linger $(whoami) 201 systemctl --user enable docker.service 202 systemctl --user start docker.service 203fi 204 205# Wait for Docker to start 206sleep 5 207 208# Test Docker 209if docker version &>/dev/null; then 210 echo " Docker rootless installation successful" 211else 212 echo " ❌ Docker installation failed" 213 exit 1 214fi 215EOF 216 217 if [[ $? -eq 0 ]]; then 218 log "Rootless Docker installation completed for $username" 219 else 220 error "Failed to install Docker for $username" 221 fi 222 223 # Enable privileged ports after installation 224 enable_privileged_ports 225 226 # Restart Docker to apply capability changes 227 log "Restarting Docker to apply configuration..." 228 if systemctl_user_safe "$username" "restart docker.service"; then 229 log " Docker service restarted via systemctl" 230 else 231 log " Systemctl restart failed, but Docker should still be functional" 232 fi 233} 234 235configure_docker() { 236 local username="$1" 237 238 log "Configuring Docker for $username..." 239 240 # Create Docker config directory 241 sudo -u "$username" mkdir -p "/home/$username/.docker" 242 243 # Create daemon.json with optimized settings 244 # Always disable cgroup management for rootless Docker to avoid systemd delegation issues 245 # This is the most reliable approach for tinsnip deployments 246 sudo -u "$username" tee "/home/$username/.docker/daemon.json" > /dev/null << 'EOF' 247{ 248 "log-driver": "json-file", 249 "log-opts": { 250 "max-size": "10m", 251 "max-file": "3" 252 }, 253 "storage-driver": "overlay2", 254 "exec-opts": ["native.cgroupdriver=none"] 255} 256EOF 257 258 # Check if Docker is already working before attempting restart 259 log "Checking Docker status before configuration restart..." 260 if sudo -u "$username" -i bash -c "docker version &>/dev/null"; then 261 log " Docker is already working, skipping restart to avoid issues" 262 else 263 log " Docker not responding, attempting restart..." 264 if systemctl_user_safe "$username" "restart docker.service"; then 265 log " Docker service restarted via systemctl" 266 # Give Docker time to start up after restart 267 log "Waiting for Docker to start..." 268 sleep 10 269 else 270 log " Systemctl restart failed, Docker may already be running in non-systemd mode" 271 fi 272 fi 273 274 log "Docker configuration complete" 275} 276 277verify_installation() { 278 local username="$1" 279 280 log "Verifying Docker installation for $username..." 281 282 # Use the service .env file (source of truth) 283 local service_env_file="/mnt/$username/.env" 284 285 if sudo -u "$username" bash -c "source '$service_env_file' && docker version &>/dev/null"; then 286 log "Docker verification successful!" 287 local docker_version 288 docker_version=$(sudo -u "$username" bash -c "source '$service_env_file' && docker --version") 289 log "Installed: $docker_version" 290 else 291 error "Docker verification failed for $username" 292 fi 293 294 log "Service available for user: $username" 295 log "Privileged ports: enabled" 296} 297 298main() { 299 # Validate parameters 300 if [[ -z "$SERVICE_USER" ]]; then 301 usage 302 fi 303 304 # Check if user exists 305 if ! id "$SERVICE_USER" &>/dev/null; then 306 error "User $SERVICE_USER does not exist. Create the user first." 307 fi 308 309 log "Installing rootless Docker for service user: $SERVICE_USER" 310 311 # Install dependencies 312 install_dependencies 313 314 # Enable systemd lingering for the service user 315 log "Enabling systemd lingering for $SERVICE_USER" 316 if sudo loginctl enable-linger "$SERVICE_USER" 2>/dev/null; then 317 log " Systemd lingering enabled" 318 else 319 log " ⚠️ Could not enable systemd lingering (may not be supported)" 320 fi 321 322 # Install rootless Docker for the service user 323 install_rootless_docker "$SERVICE_USER" 324 325 # Configure Docker 326 configure_docker "$SERVICE_USER" 327 328 # Verify installation 329 verify_installation "$SERVICE_USER" 330 331 log "" 332 log "Rootless Docker installation complete!" 333 log "User: $SERVICE_USER" 334 log "To test: sudo -u $SERVICE_USER docker run hello-world" 335} 336 337main "$@"