homelab infrastructure services
at main 511 lines 17 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 51# Initialize systemd user session for a service user 52init_systemd_user_session() { 53 local username="$1" 54 local user_uid=$(id -u "$username") 55 56 log "Initializing systemd user session for $username..." 57 58 # Create XDG_RUNTIME_DIR if it doesn't exist 59 if [[ ! -d "/run/user/$user_uid" ]]; then 60 log " Creating XDG_RUNTIME_DIR for $username" 61 sudo mkdir -p "/run/user/$user_uid" 62 sudo chown "$username:$username" "/run/user/$user_uid" 63 sudo chmod 700 "/run/user/$user_uid" 64 fi 65 66 # Enable lingering (creates persistent systemd user instance) 67 log " Enabling systemd lingering..." 68 sudo loginctl enable-linger "$username" 69 70 # Start user@.service if not running 71 if ! systemctl is-active --quiet "user@$user_uid.service"; then 72 log " Starting user@$user_uid.service..." 73 sudo systemctl start "user@$user_uid.service" 74 fi 75 76 # Wait for user session to be ready 77 log " Waiting for systemd user session to initialize..." 78 local max_attempts=10 79 local attempt=0 80 81 while [[ $attempt -lt $max_attempts ]]; do 82 if sudo -u "$username" -i bash -c "XDG_RUNTIME_DIR=/run/user/$user_uid systemctl --user status >/dev/null 2>&1"; then 83 log " ✓ Systemd user session ready" 84 return 0 85 fi 86 sleep 1 87 ((attempt++)) 88 done 89 90 log " WARNING: Systemd user session may not be fully initialized" 91 return 1 92} 93 94install_dependencies() { 95 log "Installing rootless Docker dependencies..." 96 97 if sudo apt-get update -qq >/dev/null 2>&1; then 98 log " Package lists updated" 99 else 100 error "Failed to update package lists" 101 fi 102 103 # Install systemd and dbus components first for minimal systems 104 log " Installing systemd components for user sessions..." 105 if ! sudo apt-get install -y systemd systemd-sysv dbus dbus-user-session >/dev/null 2>&1; then 106 log " WARNING: Some systemd components may already be installed" 107 fi 108 109 # Install Docker dependencies 110 if sudo apt-get install -y uidmap systemd-container fuse-overlayfs slirp4netns >/dev/null 2>&1; then 111 log " Docker dependencies installed" 112 else 113 error "Failed to install Docker dependencies" 114 fi 115 116 # Ensure dbus is running (needed for systemd user sessions) 117 if ! systemctl is-active --quiet dbus; then 118 log " Starting dbus service..." 119 sudo systemctl start dbus 120 sudo systemctl enable dbus 121 fi 122} 123 124enable_privileged_ports() { 125 log "Enabling privileged port binding for rootless Docker..." 126 127 # Find rootlesskit binary path 128 local rootlesskit_path 129 if rootlesskit_path=$(which rootlesskit 2>/dev/null); then 130 log "Setting CAP_NET_BIND_SERVICE on rootlesskit..." 131 sudo setcap cap_net_bind_service=ep "$rootlesskit_path" 132 log "Privileged ports enabled" 133 else 134 log "WARNING: rootlesskit not found, will enable after Docker installation" 135 fi 136} 137 138setup_docker_environment() { 139 local username="$1" 140 141 log "Setting up Docker environment for $username" 142 143 # Find the service .env file (should exist from mount_nas.sh) 144 local service_env_file="/mnt/$username/.env" 145 146 if [[ ! -f "$service_env_file" ]]; then 147 log "WARNING: Service .env file not found at $service_env_file" 148 log "Docker environment will not be persistent across hosts" 149 return 1 150 fi 151 152 # Always use systemd runtime directory since lingering is enabled 153 local user_uid 154 user_uid=$(id -u "$username") 155 local xdg_runtime_dir="/run/user/$user_uid" 156 local docker_host="unix:///run/user/$user_uid/docker.sock" 157 158 # Start Docker daemon if not running 159 sudo -u "$username" -i bash << EOF 160if ! pgrep -x dockerd >/dev/null; then 161 export XDG_RUNTIME_DIR="$xdg_runtime_dir" 162 export DOCKER_HOST="$docker_host" 163 dockerd-rootless.sh > ~/.docker/docker.log 2>&1 & 164 sleep 5 165fi 166EOF 167 168 # Update Docker environment variables in service .env file 169 log "Adding Docker environment to service .env file" 170 local service_env_file="/mnt/$username/.env" 171 172 if [[ -f "$service_env_file" ]]; then 173 # Remove existing Docker variables and add correct ones 174 sudo -u "$username" bash -c "grep -v '^XDG_RUNTIME_DIR=\|^DOCKER_HOST=\|^PATH=' '$service_env_file' > '${service_env_file}.tmp' 2>/dev/null && mv '${service_env_file}.tmp' '$service_env_file'" 175 176 # Add Docker environment variables using systemd runtime directory 177 sudo -u "$username" bash -c "cat >> '$service_env_file' << 'DOCKER_EOF' 178 179# Docker rootless environment 180XDG_RUNTIME_DIR=$xdg_runtime_dir 181DOCKER_HOST=$docker_host 182PATH=/home/$username/bin:\$PATH 183DOCKER_EOF" 184 185 log "Docker environment added to $service_env_file" 186 else 187 log "WARNING: Service .env file not found at $service_env_file" 188 fi 189} 190 191install_rootless_docker() { 192 local username="$1" 193 local user_uid=$(id -u "$username") 194 195 log "Installing rootless Docker for user: $username" 196 197 # Check if already installed AND working 198 if sudo -u "$username" -i bash -c "command -v docker &>/dev/null && docker version &>/dev/null"; then 199 log "Docker already installed and working for $username" 200 return 0 201 elif sudo -u "$username" -i bash -c "command -v docker &>/dev/null"; then 202 log "Docker client installed but daemon not running for $username" 203 setup_docker_environment "$username" 204 # Continue to start the daemon below 205 else 206 log "Installing Docker client for $username" 207 fi 208 209 # Install rootless Docker with proper environment handling 210 sudo -u "$username" -i bash << EOF 211# Set up environment explicitly 212export XDG_RUNTIME_DIR=/run/user/$user_uid 213export PATH=\$HOME/bin:/usr/bin:\$PATH 214export DOCKER_HOST=unix:///run/user/$user_uid/docker.sock 215 216echo "Installing rootless Docker with environment:" 217echo " XDG_RUNTIME_DIR: \$XDG_RUNTIME_DIR" 218echo " DOCKER_HOST: \$DOCKER_HOST" 219 220# Download and install rootless Docker 221curl -fsSL https://get.docker.com/rootless | sh 222 223# Try systemd setup first 224if systemctl --user status >/dev/null 2>&1; then 225 echo " Setting up systemd service..." 226 \$HOME/bin/dockerd-rootless-setuptool.sh install >/dev/null 2>&1 || echo " Systemd setup failed, using manual start" 227 228 systemctl --user daemon-reload >/dev/null 2>&1 229 systemctl --user enable docker.service >/dev/null 2>&1 230 systemctl --user start docker.service >/dev/null 2>&1 231 232 # Wait for systemd Docker to start 233 for i in {1..5}; do 234 if docker version >/dev/null 2>&1; then 235 echo " ✓ Docker started via systemd" 236 exit 0 237 fi 238 sleep 2 239 done 240 echo " Systemd start failed, falling back to manual..." 241fi 242 243# Manual startup with explicit environment 244echo " Starting Docker daemon manually..." 245# Create .docker directory first 246mkdir -p ~/.docker 247 248# Start with explicit environment and proper logging 249nohup dockerd-rootless.sh > ~/.docker/docker.log 2>&1 & 250DOCKER_PID=\$! 251echo " Docker daemon started with PID: \$DOCKER_PID" 252 253# Wait for manual Docker to start 254for i in {1..15}; do 255 if docker version >/dev/null 2>&1; then 256 echo " ✓ Docker rootless installation successful (manual startup)" 257 echo " Docker daemon PID: \$DOCKER_PID" 258 exit 0 259 fi 260 echo " Waiting for Docker to start... (\$i/15)" 261 if [[ \$i -eq 5 ]] || [[ \$i -eq 10 ]]; then 262 echo " Checking daemon status..." 263 if ! kill -0 \$DOCKER_PID 2>/dev/null; then 264 echo " ❌ Docker daemon died, checking logs:" 265 tail -n 10 ~/.docker/docker.log 266 fi 267 fi 268 sleep 2 269done 270 271echo " ❌ Docker installation failed" 272echo " Docker log:" 273cat ~/.docker/docker.log 2>/dev/null || echo " No log file found" 274exit 1 275EOF 276 277 if [[ $? -eq 0 ]]; then 278 log "Rootless Docker installation completed for $username" 279 else 280 error "Failed to install Docker for $username" 281 fi 282 283 # Enable privileged ports after installation 284 enable_privileged_ports 285 286 # Restart Docker to apply capability changes 287 log "Restarting Docker to apply configuration..." 288 if systemctl_user_safe "$username" "restart docker.service"; then 289 log " Docker service restarted via systemctl" 290 else 291 log " Systemctl restart failed, but Docker should still be functional" 292 fi 293} 294 295configure_docker() { 296 local username="$1" 297 298 log "Configuring Docker for $username..." 299 300 # Create Docker config directory and local data directory 301 sudo -u "$username" mkdir -p "/home/$username/.docker" 302 sudo -u "$username" mkdir -p "/home/$username/.local/share/docker" 303 304 # Create daemon.json with optimized settings 305 # Always disable cgroup management for rootless Docker to avoid systemd delegation issues 306 # Use local data-root to avoid NFS permission issues with XDG_DATA_HOME on NFS 307 # This is the most reliable approach for tinsnip deployments 308 sudo -u "$username" mkdir -p "/home/$username/.local/share/docker" 309 sudo -u "$username" tee "/home/$username/.docker/daemon.json" > /dev/null << EOF 310{ 311 "data-root": "/home/$username/.local/share/docker", 312 "log-driver": "json-file", 313 "log-opts": { 314 "max-size": "10m", 315 "max-file": "3" 316 }, 317 "storage-driver": "overlay2", 318 "exec-opts": ["native.cgroupdriver=none"] 319} 320EOF 321 322 # Ensure proper ownership of Docker directories 323 sudo -u "$username" chmod 755 "/home/$username/.local/share/docker" 324 325 # Check if Docker is already working before attempting restart 326 log "Checking Docker status before configuration restart..." 327 if sudo -u "$username" -i bash -c "docker version &>/dev/null"; then 328 log " Docker is already working, skipping restart to avoid issues" 329 else 330 log " Docker not responding, attempting restart..." 331 if systemctl_user_safe "$username" "restart docker.service"; then 332 log " Docker service restarted via systemctl" 333 # Give Docker time to start up after restart 334 log "Waiting for Docker to start..." 335 sleep 10 336 else 337 log " Systemctl restart failed, Docker may already be running in non-systemd mode" 338 fi 339 fi 340 341 # Set up Docker environment variables in .env file 342 setup_docker_environment "$username" 343 344 # Create Docker context for rootless socket (before starting daemon) 345 log "Setting up Docker context..." 346 local user_uid 347 user_uid=$(id -u "$username") 348 sudo -u "$username" bash -c "docker context create rootless --docker 'host=unix:///run/user/$user_uid/docker.sock' >/dev/null 2>&1 || true" 349 sudo -u "$username" bash -c "docker context use rootless >/dev/null 2>&1 || true" 350 351 log "Docker configuration complete" 352 353 # Ensure Docker daemon is running after configuration 354 ensure_docker_running "$username" 355} 356 357ensure_docker_running() { 358 local username="$1" 359 local user_uid=$(id -u "$username") 360 361 log "Ensuring Docker daemon is running for $username..." 362 363 # Check if Docker is already responding 364 if sudo -u "$username" -i bash -c "docker version &>/dev/null"; then 365 log " ✓ Docker daemon already running" 366 return 0 367 fi 368 369 log " Docker not responding, starting daemon..." 370 371 # Try systemd first 372 if systemctl_user_safe "$username" "start docker.service"; then 373 log " Started via systemd" 374 # Wait for systemd start 375 for i in {1..10}; do 376 if sudo -u "$username" -i bash -c "docker version &>/dev/null"; then 377 log " ✓ Docker daemon started successfully" 378 return 0 379 fi 380 sleep 2 381 done 382 fi 383 384 # Fall back to manual start 385 log " Starting manually with explicit environment..." 386 sudo -u "$username" -i bash << EOF 387# Kill any existing dockerd processes for this user first 388pkill -f "dockerd-rootless" 2>/dev/null || true 389sleep 2 390 391# Explicitly set environment variables that dockerd-rootless.sh needs 392export XDG_RUNTIME_DIR="/run/user/$user_uid" 393export DOCKER_HOST="unix:///run/user/$user_uid/docker.sock" 394export PATH="/home/$username/bin:\$PATH" 395 396# Clear any environment variables that might override daemon.json 397unset DOCKER_ROOT DOCKER_DATA_ROOT 398 399echo "Environment check:" 400echo " XDG_RUNTIME_DIR: '\$XDG_RUNTIME_DIR'" 401echo " DOCKER_HOST: '\$DOCKER_HOST'" 402echo " Directory exists: \$(test -d "\$XDG_RUNTIME_DIR" && echo "yes" || echo "no")" 403echo " Directory writable: \$(test -w "\$XDG_RUNTIME_DIR" && echo "yes" || echo "no")" 404 405# Start Docker daemon with explicit environment 406mkdir -p ~/.docker 407nohup env -u DOCKER_ROOT -u DOCKER_DATA_ROOT -u XDG_DATA_HOME XDG_RUNTIME_DIR="/run/user/$user_uid" DOCKER_HOST="unix:///run/user/$user_uid/docker.sock" dockerd-rootless.sh --data-root="/home/$username/.local/share/docker" > ~/.docker/docker.log 2>&1 & 408DOCKER_PID=\$! 409echo "Docker daemon started with PID: \$DOCKER_PID" 410 411# Wait for Docker to be ready 412for i in {1..15}; do 413 if XDG_RUNTIME_DIR="/run/user/$user_uid" DOCKER_HOST="unix:///run/user/$user_uid/docker.sock" docker version >/dev/null 2>&1; then 414 echo "✓ Docker daemon ready" 415 exit 0 416 fi 417 echo " Waiting for Docker... (\$i/15)" 418 sleep 2 419done 420 421echo "❌ Failed to start Docker daemon" 422echo "Docker log:" 423tail -n 20 ~/.docker/docker.log 2>/dev/null || echo "No log file found" 424exit 1 425EOF 426 427 if [[ $? -eq 0 ]]; then 428 log " ✓ Docker daemon started manually" 429 else 430 log " ❌ Failed to start Docker daemon" 431 return 1 432 fi 433} 434 435verify_installation() { 436 local username="$1" 437 438 log "Verifying Docker installation for $username..." 439 440 # Use the service .env file (source of truth) 441 local service_env_file="/mnt/$username/.env" 442 443 # Docker context already set up in configure_docker() 444 445 # Debug verification process 446 log "Debugging verification process..." 447 log " Service env file: $service_env_file" 448 449 if [[ -f "$service_env_file" ]]; then 450 log " Environment variables in service .env:" 451 sudo -u "$username" grep "DOCKER\|XDG_RUNTIME" "$service_env_file" | while read line; do 452 log " $line" 453 done 454 else 455 log " WARNING: Service .env file not found!" 456 fi 457 458 # Test Docker verification with detailed output 459 log " Testing Docker command with environment..." 460 if sudo -u "$username" bash -c "source '$service_env_file' 2>/dev/null && docker version"; then 461 log "Docker verification successful!" 462 local docker_version 463 docker_version=$(sudo -u "$username" bash -c "source '$service_env_file' && docker --version") 464 log "Installed: $docker_version" 465 else 466 log "Docker verification failed - showing detailed error:" 467 sudo -u "$username" bash -c "source '$service_env_file' 2>/dev/null && docker version" 2>&1 | while read line; do 468 log " ERROR: $line" 469 done 470 error "Docker verification failed for $username" 471 fi 472 473 log "Service available for user: $username" 474 log "Privileged ports: enabled" 475} 476 477main() { 478 # Validate parameters 479 if [[ -z "$SERVICE_USER" ]]; then 480 usage 481 fi 482 483 # Check if user exists 484 if ! id "$SERVICE_USER" &>/dev/null; then 485 error "User $SERVICE_USER does not exist. Create the user first." 486 fi 487 488 log "Installing rootless Docker for service user: $SERVICE_USER" 489 490 # Install dependencies 491 install_dependencies 492 493 # Initialize systemd user session (handles lingering and XDG_RUNTIME_DIR) 494 init_systemd_user_session "$SERVICE_USER" 495 496 # Configure Docker (before installation to set up proper data directory) 497 configure_docker "$SERVICE_USER" 498 499 # Install rootless Docker for the service user 500 install_rootless_docker "$SERVICE_USER" 501 502 # Verify installation 503 verify_installation "$SERVICE_USER" 504 505 log "" 506 log "Rootless Docker installation complete!" 507 log "User: $SERVICE_USER" 508 log "To test: sudo -u $SERVICE_USER docker run hello-world" 509} 510 511main "$@"