#!/bin/bash # Setup a complete service environment with NFS and rootless Docker # Implements DEPLOYMENT_STRATEGY.md conventions set -euo pipefail # Parse parameters and flags TIN_SERVICE_NAME="${1:-}" TIN_SERVICE_ENVIRONMENT="${2:-}" NAS_SERVER="${3:-}" PROVIDED_UID="${4:-}" # Optional pre-calculated UID to avoid duplicate warnings SKIP_NAS=false # Check for --skip-nas flag in fourth or fifth parameter if [[ "${4:-}" == "--skip-nas" || "${5:-}" == "--skip-nas" ]]; then SKIP_NAS=true fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TINSNIP_ROOT="$(dirname "$SCRIPT_DIR")" # Source new library modules source "$TINSNIP_ROOT/lib/core.sh" source "$TINSNIP_ROOT/lib/uid.sh" source "$TINSNIP_ROOT/lib/nfs.sh" source "$TINSNIP_ROOT/lib/docker.sh" source "$TINSNIP_ROOT/lib/metadata.sh" log() { log_with_prefix "Service Setup" "$@" } error() { error_with_prefix "Service Setup" "$@" } usage() { echo "Usage: $0 [uid] [--skip-nas]" echo "" echo "Sets up a complete service environment with:" echo " - Service user with UID convention" echo " - NFS mount to /mnt/- (with automatic NAS setup)" echo " - XDG directory integration" echo " - Rootless Docker with privileged ports" echo "" echo "Parameters:" echo " service_name: tinsnip, gazette, etc." echo " service_env: prod, test, dev, staging" echo " nas_server: NAS hostname or IP" echo " uid: Optional pre-calculated UID (to avoid duplicate warnings)" echo " --skip-nas: Skip automated NAS setup (requires manual configuration)" echo "" echo "Examples:" echo " $0 tinsnip test DS412plus # With automated NAS setup" echo " $0 lldap prod 192.168.1.100 --skip-nas # Manual NAS setup required" echo " $0 gateway prod 192.168.1.100 50500 # With pre-calculated UID" exit 1 } # calculate_service_uid is now provided by lib.sh check_prerequisites() { # Check Ubuntu if [[ ! -f /etc/os-release ]] || ! grep -q "Ubuntu" /etc/os-release; then error "This script requires Ubuntu" fi # Check sudo access if ! groups | grep -q sudo; then error "Current user must have sudo access" fi # Check not running as root or service user if [[ $EUID -eq 0 ]]; then error "Do not run as root. Run as a regular user with sudo access." fi local service_user="${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" if [[ "$USER" == "$service_user" ]]; then error "Do not run as the service user ($service_user). Run as a regular admin user." fi } main() { # Validate parameters if [[ -z "$TIN_SERVICE_NAME" || -z "$TIN_SERVICE_ENVIRONMENT" || -z "$NAS_SERVER" ]]; then usage fi # Guard against explicit station creation if [[ "$TIN_SERVICE_NAME" == "station" ]]; then error "Station is infrastructure and cannot be created as a service. It will be automatically created when needed." fi # Calculate service details (use provided UID if available to avoid duplicate warning) if [[ -n "$PROVIDED_UID" && "$PROVIDED_UID" =~ ^[0-9]+$ ]]; then TIN_SERVICE_UID="$PROVIDED_UID" else TIN_SERVICE_UID=$(calculate_service_uid "$TIN_SERVICE_NAME" "$TIN_SERVICE_ENVIRONMENT") fi SERVICE_USER="${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" log "Service Environment Setup" log "=========================" log "Service: $TIN_SERVICE_NAME" log "Environment: $TIN_SERVICE_ENVIRONMENT" log "User: $SERVICE_USER (UID: $TIN_SERVICE_UID)" log "NAS: $NAS_SERVER" if [[ "$SKIP_NAS" == "true" ]]; then log "NAS Setup: Manual (--skip-nas flag used)" else log "NAS Setup: Automated (if SSH keys available)" fi echo # Check prerequisites check_prerequisites # Check for username collision (idempotent - allow if UID matches) if id "$SERVICE_USER" &>/dev/null 2>&1; then local existing_uid=$(id -u "$SERVICE_USER") if [[ "$existing_uid" == "$TIN_SERVICE_UID" ]]; then log "User '$SERVICE_USER' already exists with correct UID $existing_uid (idempotent)" # User exists with correct UID - continue with setup else # UID mismatch - this is a real conflict error "User '$SERVICE_USER' already exists with conflicting UID $existing_uid (expected: $TIN_SERVICE_UID)" echo "" >&2 echo "This may be from:" >&2 echo " - A different sheet using the same service/environment name" >&2 echo " - A previous deployment with different UID" >&2 echo "" >&2 echo "To avoid collision, choose a unique service name:" >&2 echo " tin machine ${TIN_SERVICE_NAME}-${TIN_SHEET} ${TIN_SERVICE_ENVIRONMENT}" >&2 local sheet_num=$(get_sheet_number "${TIN_SHEET:-topsheet}" 2>/dev/null || echo "X") echo " tin machine ${TIN_SERVICE_NAME}-${sheet_num} ${TIN_SERVICE_ENVIRONMENT}" >&2 echo " tin machine $(echo ${TIN_SHEET:-topsheet} | cut -d. -f1)-${TIN_SERVICE_NAME} ${TIN_SERVICE_ENVIRONMENT}" >&2 echo "" >&2 echo "Or remove the existing user:" >&2 echo " sudo userdel -r $SERVICE_USER" >&2 exit 1 fi fi # Ensure station exists first local sheet="${TIN_SHEET:-topsheet}" log "Checking $sheet station..." if ! "$SCRIPT_DIR/setup_station.sh" "$NAS_SERVER"; then error "Failed to setup sheet station" fi # Proceeding with setup # Step 1: Mount NFS and create service user log "Step 1: Setting up NFS mount and service user..." # Create service user if ! create_service_user "$SERVICE_USER" "$TIN_SERVICE_UID"; then error "Failed to create service user $SERVICE_USER" fi # Setup NFS mount (unless skipping NAS setup) if [[ "$SKIP_NAS" != "true" ]]; then if ! setup_nfs_mount "$TIN_SERVICE_NAME" "$TIN_SERVICE_ENVIRONMENT" "$TIN_SERVICE_UID" "$NAS_SERVER" "$sheet"; then error "Failed to setup NFS mount" fi # Setup XDG integration if ! setup_xdg_integration "$TIN_SERVICE_NAME" "$TIN_SERVICE_ENVIRONMENT" "$sheet"; then error "Failed to setup XDG integration" fi # Create machine metadata in station-prod log "Creating machine metadata in station-prod..." if ! create_machine_metadata "$TIN_SERVICE_NAME" "$TIN_SERVICE_ENVIRONMENT" "$TIN_SERVICE_UID"; then error "Failed to create machine metadata" fi # Create .machine symlink from mount to station-prod log "Creating .machine symlink..." if ! create_machine_symlink "$TIN_SERVICE_NAME" "$TIN_SERVICE_ENVIRONMENT"; then error "Failed to create .machine symlink" fi # Create environment loader script for automatic sourcing on login log "Creating environment loader script..." if ! create_service_env_loader_script "$SERVICE_USER"; then warn "Failed to create environment loader script" fi fi # Step 2: Install rootless Docker log "Step 2: Installing rootless Docker..." if ! install_docker_for_user "$SERVICE_USER"; then error "Failed to install Docker" fi log "" log "Machine setup complete!" log "" log "Machine environment details:" log " Machine: ${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" log " User: $SERVICE_USER (UID: $TIN_SERVICE_UID)" log " NFS mount: /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" log " XDG integration: ~/.local/{state,share,config}/$sheet/@$TIN_SERVICE_NAME" if [[ "$SKIP_NAS" == "false" ]]; then log " NAS exports: Automatically configured (if SSH keys available)" else log " NAS exports: Manual configuration required (--skip-nas used)" fi log "" log "To use this environment:" log " sudo -u $SERVICE_USER -i" log " cd /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" log " docker run hello-world" log "" log "To deploy services to this machine:" log " # Using modern CLI (recommended):" log " tin service deploy ${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT} " log " # Examples:" log " tin service deploy ${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT} lldap" log " tin service deploy ${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT} caddy" log "" log " # Or manually as service user:" log " sudo -u $SERVICE_USER -i" log " cd /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/service/" log " docker compose up -d" } main "$@"