#!/bin/bash # Docker installation and management functions for tinsnip # Source core functions LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$LIB_DIR/core.sh" # 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_with_prefix "Docker Setup" "⚠️ Systemd not available, skipping: systemctl --user $systemctl_command" fi return $result } # Install Docker for a specific service user (rootless) install_docker_for_user() { local service_user="$1" if [[ -z "$service_user" ]]; then error_with_prefix "Docker Setup" "Service user parameter required" fi log_with_prefix "Docker Setup" "Installing rootless Docker for user: $service_user" # Check if user exists if ! id "$service_user" &>/dev/null; then error_with_prefix "Docker Setup" "User $service_user does not exist" fi # Install system Docker if not present (needed for rootless setup) if ! command -v docker >/dev/null 2>&1; then log_with_prefix "Docker Setup" "Installing system Docker..." # Add Docker's official GPG key sudo apt-get update -qq sudo apt-get install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add the repository to Apt sources 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 sudo apt-get update -qq sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin log_with_prefix "Docker Setup" "System Docker installed" else log_with_prefix "Docker Setup" "System Docker already installed" fi # Install rootless Docker for the service user log_with_prefix "Docker Setup" "Setting up rootless Docker for $service_user..." # Enable lingering for the user (allows user services to run without login) sudo loginctl enable-linger "$service_user" 2>/dev/null || true # Install rootless Docker as the service user sudo -u "$service_user" -i bash << 'EOF' # Check if rootless Docker is already installed if [[ -f "$HOME/bin/docker" ]] && "$HOME/bin/docker" --version >/dev/null 2>&1; then echo "Rootless Docker already installed for $(whoami)" exit 0 fi # Download and install rootless Docker # Pin to version 26.1.4 to avoid Docker 27.0+ regression with rootless mode # See: https://github.com/moby/moby/issues/48064 # Manual install to pin specific version DOCKER_VERSION="26.1.4" ARCH=$(uname -m) # Download Docker binaries mkdir -p "$HOME/bin" cd /tmp curl -fsSL "https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz" -o docker.tgz tar xzf docker.tgz --strip-components=1 -C "$HOME/bin" docker/docker docker/dockerd docker/docker-init docker/docker-proxy rm docker.tgz # Download rootless extras curl -fsSL "https://download.docker.com/linux/static/stable/${ARCH}/docker-rootless-extras-${DOCKER_VERSION}.tgz" -o docker-rootless-extras.tgz tar xzf docker-rootless-extras.tgz --strip-components=1 -C "$HOME/bin" rm docker-rootless-extras.tgz chmod +x "$HOME/bin/"* # Run rootless setup tool export SKIP_IPTABLES=1 "$HOME/bin/dockerd-rootless-setuptool.sh" install --skip-iptables # Add Docker bindir to PATH in shell configs grep -q 'export PATH="$HOME/bin:$PATH"' ~/.bashrc || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc grep -q 'export PATH="$HOME/bin:$PATH"' ~/.profile || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.profile # Set up Docker environment variables grep -q 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' ~/.bashrc || echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc grep -q 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' ~/.profile || echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.profile EOF # Start rootless Docker daemon log_with_prefix "Docker Setup" "Starting rootless Docker daemon..." if systemctl_user_safe "$service_user" "start docker"; then log_with_prefix "Docker Setup" "Docker daemon started" # Enable Docker to start on boot if systemctl_user_safe "$service_user" "enable docker"; then log_with_prefix "Docker Setup" "Docker daemon enabled for auto-start" fi else warn_with_prefix "Docker Setup" "Could not start Docker daemon via systemctl" fi # Verify installation log_with_prefix "Docker Setup" "Verifying Docker installation..." if sudo -u "$service_user" -i bash -c 'export PATH="$HOME/bin:$PATH" && docker --version' >/dev/null 2>&1; then log_with_prefix "Docker Setup" "✅ Docker installation verified" # Configure Docker to use local disk (not NFS) for data storage configure_docker_data_root "$service_user" # Setup environment integration (.env file) setup_docker_environment "$service_user" # Ensure Docker daemon is running ensure_docker_running "$service_user" # Test with hello-world (optional, may fail if daemon not ready) sudo -u "$service_user" -i bash -c 'export PATH="$HOME/bin:$PATH" && docker run --rm hello-world' >/dev/null 2>&1 && \ log_with_prefix "Docker Setup" "✅ Docker test container ran successfully" || \ warn_with_prefix "Docker Setup" "Docker test container failed (daemon may need time to start)" else error_with_prefix "Docker Setup" "Docker installation verification failed" return 1 fi log_with_prefix "Docker Setup" "Rootless Docker installation completed for $service_user" } # Setup Docker environment variables and machine.env file integration setup_docker_environment() { local username="$1" log_with_prefix "Docker Setup" "Setting up Docker environment for $username" # Find machine.env via .machine symlink (created by metadata setup) local machine_env_file="/mnt/$username/.machine/machine.env" if [[ ! -f "$machine_env_file" ]]; then warn_with_prefix "Docker Setup" "Machine environment file not found at $machine_env_file" warn_with_prefix "Docker Setup" "Docker environment will not be persistent across hosts" return 1 fi # Detect runtime directory based on systemd availability local user_uid user_uid=$(id -u "$username") local xdg_runtime_dir local docker_host # Check if systemd is available on the system (not user session, which may not exist yet) if systemctl --version >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then # Systemd available - Docker will use standard runtime directory when it runs xdg_runtime_dir="/run/user/$user_uid" docker_host="unix:///run/user/$user_uid/docker.sock" log_with_prefix "Docker Setup" "Systemd detected - using standard runtime directory: $xdg_runtime_dir" else # No systemd - use home directory (rootless Docker fallback) xdg_runtime_dir="/home/$username/.docker/run" docker_host="unix:///home/$username/.docker/run/docker.sock" log_with_prefix "Docker Setup" "Systemd not detected - using home directory: $xdg_runtime_dir" fi # Update Docker environment variables in machine.env file (as station-prod) log_with_prefix "Docker Setup" "Adding Docker environment to machine.env" # Remove existing Docker variables and add correct ones 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'" # Add Docker environment variables sudo -u station-prod tee -a "$machine_env_file" > /dev/null << DOCKER_EOF # Docker Environment (added after Docker installation) DOCKER_HOST=$docker_host PATH=/home/$username/.local/bin:/usr/local/bin:/usr/bin:/bin DOCKER_EOF log_with_prefix "Docker Setup" "Docker environment added to $machine_env_file" return 0 } # Configure Docker daemon to use local disk (not NFS) for data storage configure_docker_data_root() { local username="$1" log_with_prefix "Docker Setup" "Configuring Docker data root for $username" # Create daemon config directory sudo -u "$username" mkdir -p "/home/$username/.config/docker" # Set data-root to local disk to avoid NFS permission issues # See: https://github.com/moby/moby/issues/47962 # When systemd is not available, use cgroupfs driver local config_file="/home/$username/.config/docker/daemon.json" cat << 'EOF' | sudo -u "$username" tee "$config_file" > /dev/null { "data-root": "/home/$USER/.local/share/docker", "storage-driver": "fuse-overlayfs", "exec-opts": ["native.cgroupdriver=cgroupfs"] } EOF # Expand $USER variable in the config file sudo -u "$username" bash -c "sed -i 's|\$USER|$username|g' '$config_file'" log_with_prefix "Docker Setup" "Docker configured to use local disk: /home/$username/.local/share/docker" } # Ensure Docker daemon is running for the user ensure_docker_running() { local username="$1" log_with_prefix "Docker Setup" "Ensuring Docker daemon is running for $username" local user_uid user_uid=$(id -u "$username") local xdg_runtime_dir local docker_host # Check if systemd is available on the system (not user session, which may not exist yet) if systemctl --version >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then # Systemd available - Docker will use standard runtime directory when it runs xdg_runtime_dir="/run/user/$user_uid" docker_host="unix:///run/user/$user_uid/docker.sock" else # No systemd - use home directory (rootless Docker fallback) xdg_runtime_dir="/home/$username/.docker/run" docker_host="unix:///home/$username/.docker/run/docker.sock" fi # Start Docker daemon if not running if ! sudo -u "$username" -i bash -c "export XDG_RUNTIME_DIR='$xdg_runtime_dir' && export DOCKER_HOST='$docker_host' && docker version &>/dev/null"; then log_with_prefix "Docker Setup" "Starting Docker daemon for $username..." sudo -u "$username" -i bash << EOF # Check if this user's dockerd is already running if ! pgrep -u "$user_uid" -x dockerd >/dev/null 2>&1; then mkdir -p ~/.docker # Unset data-related environment variables to prevent Docker from using NFS # Pass data-root and cgroup settings explicitly via command line nohup env -u DOCKER_ROOT -u DOCKER_DATA_ROOT -u XDG_DATA_HOME \ XDG_RUNTIME_DIR="$xdg_runtime_dir" \ DOCKER_HOST="$docker_host" \ dockerd-rootless.sh \ --data-root="/home/$username/.local/share/docker" \ --exec-opt native.cgroupdriver=cgroupfs \ > ~/.docker/docker.log 2>&1 & sleep 5 else echo "Docker daemon already running for $username" fi EOF # Verify it started if sudo -u "$username" -i bash -c "export XDG_RUNTIME_DIR='$xdg_runtime_dir' && export DOCKER_HOST='$docker_host' && docker version &>/dev/null"; then log_with_prefix "Docker Setup" "✅ Docker daemon started successfully" else warn_with_prefix "Docker Setup" "Docker daemon may need more time to start" fi else log_with_prefix "Docker Setup" "✅ Docker daemon already running" fi }