homelab infrastructure services
at main 290 lines 12 kB view raw
1#!/bin/bash 2# Docker installation and management functions for tinsnip 3 4# Source core functions 5LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 6source "$LIB_DIR/core.sh" 7 8# Helper function to run systemctl with fallback if systemd not available 9systemctl_user_safe() { 10 local username="$1" 11 shift 12 local systemctl_command="$*" 13 14 sudo -u "$username" -i bash << EOF 15 if systemctl --user status >/dev/null 2>&1; then 16 systemctl --user $systemctl_command 17 exit 0 18 else 19 exit 1 20 fi 21EOF 22 23 local result=$? 24 if [[ $result -eq 1 ]]; then 25 log_with_prefix "Docker Setup" "⚠️ Systemd not available, skipping: systemctl --user $systemctl_command" 26 fi 27 28 return $result 29} 30 31# Install Docker for a specific service user (rootless) 32install_docker_for_user() { 33 local service_user="$1" 34 35 if [[ -z "$service_user" ]]; then 36 error_with_prefix "Docker Setup" "Service user parameter required" 37 fi 38 39 log_with_prefix "Docker Setup" "Installing rootless Docker for user: $service_user" 40 41 # Check if user exists 42 if ! id "$service_user" &>/dev/null; then 43 error_with_prefix "Docker Setup" "User $service_user does not exist" 44 fi 45 46 # Install system Docker if not present (needed for rootless setup) 47 if ! command -v docker >/dev/null 2>&1; then 48 log_with_prefix "Docker Setup" "Installing system Docker..." 49 50 # Add Docker's official GPG key 51 sudo apt-get update -qq 52 sudo apt-get install -y ca-certificates curl gnupg 53 sudo install -m 0755 -d /etc/apt/keyrings 54 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 55 sudo chmod a+r /etc/apt/keyrings/docker.gpg 56 57 # Add the repository to Apt sources 58 echo "deb [arch=\"$(dpkg --print-architecture)\" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 59 60 sudo apt-get update -qq 61 sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 62 63 log_with_prefix "Docker Setup" "System Docker installed" 64 else 65 log_with_prefix "Docker Setup" "System Docker already installed" 66 fi 67 68 # Install rootless Docker for the service user 69 log_with_prefix "Docker Setup" "Setting up rootless Docker for $service_user..." 70 71 # Enable lingering for the user (allows user services to run without login) 72 sudo loginctl enable-linger "$service_user" 2>/dev/null || true 73 74 # Install rootless Docker as the service user 75 sudo -u "$service_user" -i bash << 'EOF' 76 # Check if rootless Docker is already installed 77 if [[ -f "$HOME/bin/docker" ]] && "$HOME/bin/docker" --version >/dev/null 2>&1; then 78 echo "Rootless Docker already installed for $(whoami)" 79 exit 0 80 fi 81 82 # Download and install rootless Docker 83 # Pin to version 26.1.4 to avoid Docker 27.0+ regression with rootless mode 84 # See: https://github.com/moby/moby/issues/48064 85 86 # Manual install to pin specific version 87 DOCKER_VERSION="26.1.4" 88 ARCH=$(uname -m) 89 90 # Download Docker binaries 91 mkdir -p "$HOME/bin" 92 cd /tmp 93 curl -fsSL "https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz" -o docker.tgz 94 tar xzf docker.tgz --strip-components=1 -C "$HOME/bin" docker/docker docker/dockerd docker/docker-init docker/docker-proxy 95 rm docker.tgz 96 97 # Download rootless extras 98 curl -fsSL "https://download.docker.com/linux/static/stable/${ARCH}/docker-rootless-extras-${DOCKER_VERSION}.tgz" -o docker-rootless-extras.tgz 99 tar xzf docker-rootless-extras.tgz --strip-components=1 -C "$HOME/bin" 100 rm docker-rootless-extras.tgz 101 102 chmod +x "$HOME/bin/"* 103 104 # Run rootless setup tool 105 export SKIP_IPTABLES=1 106 "$HOME/bin/dockerd-rootless-setuptool.sh" install --skip-iptables 107 108 # Add Docker bindir to PATH in shell configs 109 grep -q 'export PATH="$HOME/bin:$PATH"' ~/.bashrc || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc 110 grep -q 'export PATH="$HOME/bin:$PATH"' ~/.profile || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.profile 111 112 # Set up Docker environment variables 113 grep -q 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' ~/.bashrc || echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc 114 grep -q 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' ~/.profile || echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.profile 115EOF 116 117 # Start rootless Docker daemon 118 log_with_prefix "Docker Setup" "Starting rootless Docker daemon..." 119 if systemctl_user_safe "$service_user" "start docker"; then 120 log_with_prefix "Docker Setup" "Docker daemon started" 121 122 # Enable Docker to start on boot 123 if systemctl_user_safe "$service_user" "enable docker"; then 124 log_with_prefix "Docker Setup" "Docker daemon enabled for auto-start" 125 fi 126 else 127 warn_with_prefix "Docker Setup" "Could not start Docker daemon via systemctl" 128 fi 129 130 # Verify installation 131 log_with_prefix "Docker Setup" "Verifying Docker installation..." 132 if sudo -u "$service_user" -i bash -c 'export PATH="$HOME/bin:$PATH" && docker --version' >/dev/null 2>&1; then 133 log_with_prefix "Docker Setup" "✅ Docker installation verified" 134 135 # Configure Docker to use local disk (not NFS) for data storage 136 configure_docker_data_root "$service_user" 137 138 # Setup environment integration (.env file) 139 setup_docker_environment "$service_user" 140 141 # Ensure Docker daemon is running 142 ensure_docker_running "$service_user" 143 144 # Test with hello-world (optional, may fail if daemon not ready) 145 sudo -u "$service_user" -i bash -c 'export PATH="$HOME/bin:$PATH" && docker run --rm hello-world' >/dev/null 2>&1 && \ 146 log_with_prefix "Docker Setup" "✅ Docker test container ran successfully" || \ 147 warn_with_prefix "Docker Setup" "Docker test container failed (daemon may need time to start)" 148 else 149 error_with_prefix "Docker Setup" "Docker installation verification failed" 150 return 1 151 fi 152 153 log_with_prefix "Docker Setup" "Rootless Docker installation completed for $service_user" 154} 155 156# Setup Docker environment variables and machine.env file integration 157setup_docker_environment() { 158 local username="$1" 159 160 log_with_prefix "Docker Setup" "Setting up Docker environment for $username" 161 162 # Find machine.env via .machine symlink (created by metadata setup) 163 local machine_env_file="/mnt/$username/.machine/machine.env" 164 165 if [[ ! -f "$machine_env_file" ]]; then 166 warn_with_prefix "Docker Setup" "Machine environment file not found at $machine_env_file" 167 warn_with_prefix "Docker Setup" "Docker environment will not be persistent across hosts" 168 return 1 169 fi 170 171 # Detect runtime directory based on systemd availability 172 local user_uid 173 user_uid=$(id -u "$username") 174 local xdg_runtime_dir 175 local docker_host 176 177 # Check if systemd is available on the system (not user session, which may not exist yet) 178 if systemctl --version >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then 179 # Systemd available - Docker will use standard runtime directory when it runs 180 xdg_runtime_dir="/run/user/$user_uid" 181 docker_host="unix:///run/user/$user_uid/docker.sock" 182 log_with_prefix "Docker Setup" "Systemd detected - using standard runtime directory: $xdg_runtime_dir" 183 else 184 # No systemd - use home directory (rootless Docker fallback) 185 xdg_runtime_dir="/home/$username/.docker/run" 186 docker_host="unix:///home/$username/.docker/run/docker.sock" 187 log_with_prefix "Docker Setup" "Systemd not detected - using home directory: $xdg_runtime_dir" 188 fi 189 190 # Update Docker environment variables in machine.env file (as station-prod) 191 log_with_prefix "Docker Setup" "Adding Docker environment to machine.env" 192 193 # Remove existing Docker variables and add correct ones 194 sudo -u station-prod bash -c "grep -v '^XDG_RUNTIME_DIR=\|^DOCKER_HOST=\|^PATH=\|^# Docker' '$machine_env_file' > '${machine_env_file}.tmp' 2>/dev/null && mv '${machine_env_file}.tmp' '$machine_env_file'" 195 196 # Add Docker environment variables 197 sudo -u station-prod tee -a "$machine_env_file" > /dev/null << DOCKER_EOF 198 199# Docker Environment (added after Docker installation) 200DOCKER_HOST=$docker_host 201PATH=/home/$username/.local/bin:/usr/local/bin:/usr/bin:/bin 202DOCKER_EOF 203 204 log_with_prefix "Docker Setup" "Docker environment added to $machine_env_file" 205 return 0 206} 207 208# Configure Docker daemon to use local disk (not NFS) for data storage 209configure_docker_data_root() { 210 local username="$1" 211 212 log_with_prefix "Docker Setup" "Configuring Docker data root for $username" 213 214 # Create daemon config directory 215 sudo -u "$username" mkdir -p "/home/$username/.config/docker" 216 217 # Set data-root to local disk to avoid NFS permission issues 218 # See: https://github.com/moby/moby/issues/47962 219 # When systemd is not available, use cgroupfs driver 220 local config_file="/home/$username/.config/docker/daemon.json" 221 222 cat << 'EOF' | sudo -u "$username" tee "$config_file" > /dev/null 223{ 224 "data-root": "/home/$USER/.local/share/docker", 225 "storage-driver": "fuse-overlayfs", 226 "exec-opts": ["native.cgroupdriver=cgroupfs"] 227} 228EOF 229 230 # Expand $USER variable in the config file 231 sudo -u "$username" bash -c "sed -i 's|\$USER|$username|g' '$config_file'" 232 233 log_with_prefix "Docker Setup" "Docker configured to use local disk: /home/$username/.local/share/docker" 234} 235 236# Ensure Docker daemon is running for the user 237ensure_docker_running() { 238 local username="$1" 239 240 log_with_prefix "Docker Setup" "Ensuring Docker daemon is running for $username" 241 242 local user_uid 243 user_uid=$(id -u "$username") 244 local xdg_runtime_dir 245 local docker_host 246 247 # Check if systemd is available on the system (not user session, which may not exist yet) 248 if systemctl --version >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then 249 # Systemd available - Docker will use standard runtime directory when it runs 250 xdg_runtime_dir="/run/user/$user_uid" 251 docker_host="unix:///run/user/$user_uid/docker.sock" 252 else 253 # No systemd - use home directory (rootless Docker fallback) 254 xdg_runtime_dir="/home/$username/.docker/run" 255 docker_host="unix:///home/$username/.docker/run/docker.sock" 256 fi 257 258 # Start Docker daemon if not running 259 if ! sudo -u "$username" -i bash -c "export XDG_RUNTIME_DIR='$xdg_runtime_dir' && export DOCKER_HOST='$docker_host' && docker version &>/dev/null"; then 260 log_with_prefix "Docker Setup" "Starting Docker daemon for $username..." 261 262 sudo -u "$username" -i bash << EOF 263# Check if this user's dockerd is already running 264if ! pgrep -u "$user_uid" -x dockerd >/dev/null 2>&1; then 265 mkdir -p ~/.docker 266 # Unset data-related environment variables to prevent Docker from using NFS 267 # Pass data-root and cgroup settings explicitly via command line 268 nohup env -u DOCKER_ROOT -u DOCKER_DATA_ROOT -u XDG_DATA_HOME \ 269 XDG_RUNTIME_DIR="$xdg_runtime_dir" \ 270 DOCKER_HOST="$docker_host" \ 271 dockerd-rootless.sh \ 272 --data-root="/home/$username/.local/share/docker" \ 273 --exec-opt native.cgroupdriver=cgroupfs \ 274 > ~/.docker/docker.log 2>&1 & 275 sleep 5 276else 277 echo "Docker daemon already running for $username" 278fi 279EOF 280 281 # Verify it started 282 if sudo -u "$username" -i bash -c "export XDG_RUNTIME_DIR='$xdg_runtime_dir' && export DOCKER_HOST='$docker_host' && docker version &>/dev/null"; then 283 log_with_prefix "Docker Setup" "✅ Docker daemon started successfully" 284 else 285 warn_with_prefix "Docker Setup" "Docker daemon may need more time to start" 286 fi 287 else 288 log_with_prefix "Docker Setup" "✅ Docker daemon already running" 289 fi 290}