#!/bin/bash # # Provision Ubuntu Host as DevOps Infrastructure # # This script transforms a fresh Ubuntu 24.04 host into a complete DevOps # infrastructure running: # - Forgejo (Git forge at forge.nasty.sh) # - Verdaccio (NPM cache at npm.nasty.sh) # - Forgejo Runner (CI/CD) # - Nginx (reverse proxy) # - PostgreSQL (database) # - Restic REST server (workstation backups) # # Usage: # ./setup-devops-host.sh # Full setup # ./setup-devops-host.sh --check # Pre-flight check only # ./setup-devops-host.sh --verify # Post-install verification # # Prerequisites: # - Fresh Ubuntu 24.04 host (or Debian-based) # - SSH access with sudo privileges # - At least 50GB free disk space # - Host accessible via SSH key # # Environment Variables: # DEVOPS_HOST_USER SSH user (default: current user) # DEVOPS_HOST_SSH_KEY SSH key path (default: ~/.ssh/id_ed25519) # BIGDISK_PATH Storage path on target (default: /bigdisk) # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INFRA_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Configuration TARGET_HOST="${1:-}" MODE="${2:---full}" DEVOPS_USER="${DEVOPS_HOST_USER:-$(whoami)}" SSH_KEY="${DEVOPS_HOST_SSH_KEY:-${HOME}/.ssh/id_ed25519}" BIGDISK="${BIGDISK_PATH:-/bigdisk}" # Paths FORGEJO_DIR="$INFRA_ROOT/docker/forgejo" VERDACCIO_DIR="$INFRA_ROOT/docker/verdaccio" RESTIC_DIR="$INFRA_ROOT/docker/restic" SYSTEMD_DIR="$INFRA_ROOT/systemd" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' log_banner() { echo -e "\n${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${CYAN}║$(printf '%*s' 64 '' | tr ' ' ' ')║${NC}" echo -e "${CYAN}║ $(printf '%-60s' "$1")║${NC}" echo -e "${CYAN}║$(printf '%*s' 64 '' | tr ' ' ' ')║${NC}" echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}\n" } log_section() { echo -e "\n${BLUE}━━━ $1 ━━━${NC}"; } log_info() { echo -e "${GREEN}[✓]${NC} $1"; } log_warn() { echo -e "${YELLOW}[!]${NC} $1"; } log_error() { echo -e "${RED}[✗]${NC} $1"; } log_step() { echo -e "${CYAN}→${NC} $1"; } ssh_cmd() { ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "$DEVOPS_USER@$TARGET_HOST" "$@" } scp_file() { scp -i "$SSH_KEY" -o StrictHostKeyChecking=no "$1" "$DEVOPS_USER@$TARGET_HOST:$2" } # ============================================================================ # Pre-flight Checks # ============================================================================ check_prerequisites() { log_section "Pre-flight Checks" # Check target host provided if [[ -z "$TARGET_HOST" ]]; then log_error "No target host specified" echo "Usage: $0 [--check|--verify]" exit 1 fi log_info "Target host: $TARGET_HOST" # Check SSH key if [[ ! -f "$SSH_KEY" ]]; then log_error "SSH key not found: $SSH_KEY" echo "Set DEVOPS_HOST_SSH_KEY environment variable or ensure key exists" exit 1 fi log_info "SSH key: $SSH_KEY" # Check SSH connectivity log_step "Testing SSH connection..." if ! ssh_cmd "echo 'SSH OK'" &>/dev/null; then log_error "Cannot connect to $TARGET_HOST" echo "Verify:" echo " - Host is reachable" echo " - SSH key has access" echo " - User has sudo privileges" exit 1 fi log_info "SSH connection OK" # Check OS log_step "Checking operating system..." local os_info os_info=$(ssh_cmd "cat /etc/os-release" 2>/dev/null || echo "") if echo "$os_info" | grep -qi "ubuntu\|debian"; then local version version=$(echo "$os_info" | grep VERSION_ID | cut -d'"' -f2) log_info "OS: $(echo "$os_info" | grep PRETTY_NAME | cut -d'"' -f2)" else log_warn "Not Ubuntu/Debian - script may require adjustments" fi # Check sudo access log_step "Checking sudo access..." if ssh_cmd "sudo -n true" &>/dev/null 2>&1; then log_info "Sudo access: passwordless" elif ssh_cmd "sudo true" &>/dev/null; then log_warn "Sudo requires password - may prompt during setup" else log_error "No sudo access" exit 1 fi # Check disk space log_step "Checking disk space..." local disk_space disk_space=$(ssh_cmd "df -BG / | tail -1 | awk '{print \$4}'" | tr -d 'G') if (( disk_space < 50 )); then log_warn "Low disk space: ${disk_space}GB available (recommend 50GB+)" else log_info "Disk space: ${disk_space}GB available" fi # Check existing services log_step "Checking for port conflicts..." local ports_in_use ports_in_use=$(ssh_cmd "ss -tlnp | grep -E ':(80|443|2222|3000|4873|5432)' || echo 'none'") if [[ "$ports_in_use" != "none" ]]; then log_warn "Some required ports already in use:" echo "$ports_in_use" else log_info "Required ports available: 80, 443, 2222, 3000, 4873, 5432" fi } # ============================================================================ # System Setup # ============================================================================ install_docker() { log_section "Installing Docker" if ssh_cmd "docker --version" &>/dev/null; then log_info "Docker already installed: $(ssh_cmd 'docker --version')" return fi log_step "Installing Docker..." ssh_cmd "sudo apt-get update" ssh_cmd "sudo apt-get install -y docker.io docker-compose" ssh_cmd "sudo systemctl enable docker" ssh_cmd "sudo systemctl start docker" # Add user to docker group log_step "Adding $DEVOPS_USER to docker group..." ssh_cmd "sudo usermod -aG docker $DEVOPS_USER" log_info "Docker installed: $(ssh_cmd 'docker --version')" log_warn "Note: User may need to re-login for docker group to take effect" } create_directories() { log_section "Creating Directory Structure" log_step "Creating $BIGDISK directories..." ssh_cmd "sudo mkdir -p $BIGDISK/{forgejo,verdaccio/{storage,config},restic,restic-backups}" ssh_cmd "sudo chown -R $DEVOPS_USER:$DEVOPS_USER $BIGDISK" ssh_cmd "chmod 755 $BIGDISK" ssh_cmd "chmod 700 $BIGDISK/verdaccio/config" ssh_cmd "chmod 700 $BIGDISK/restic-backups" log_info "Directory structure created:" ssh_cmd "tree -L 2 $BIGDISK 2>/dev/null || ls -la $BIGDISK" } generate_secrets() { log_section "Generating Secrets" local env_file="$BIGDISK/forgejo/.env" # Check if secrets already exist if ssh_cmd "test -f $env_file" &>/dev/null; then log_info "Secrets file exists, preserving existing values" return fi log_step "Generating secure secrets..." # Generate random secrets local db_password local forgejo_secret_key local forgejo_internal_token local restic_password db_password=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32) forgejo_secret_key=$(openssl rand -base64 32) forgejo_internal_token=$(openssl rand -base64 32) restic_password=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32) # Create .env file ssh_cmd "cat > $env_file" <= 4 )); then log_info "Containers running: $containers_running" else log_error "Expected 5 containers, found $containers_running running" fi # Check Forgejo log_step "Testing Forgejo..." if ssh_cmd "curl -f http://localhost/ &>/dev/null"; then log_info "Forgejo responding on http://$target_ip/" else log_warn "Forgejo not responding yet (may still be initializing)" fi # Check Verdaccio log_step "Testing Verdaccio..." if ssh_cmd "curl -f http://localhost/-/ping &>/dev/null"; then log_info "Verdaccio responding" else log_warn "Verdaccio not responding yet" fi # Check Restic log_step "Testing Restic REST server..." if ssh_cmd "curl -f http://localhost:8000/ &>/dev/null"; then log_info "Restic REST server responding on port 8000" else log_warn "Restic REST server not responding yet" fi # Service URLs echo "" log_info "Service URLs (after adding /etc/hosts):" echo " Forgejo: http://forge.nasty.sh/" echo " Verdaccio: http://npm.nasty.sh/" echo " Git SSH: ssh://git@forge.nasty.sh:2222//.git" echo " Restic: http://10.0.0.11:8000/ (workstation backups)" echo "" } print_next_steps() { log_section "Next Steps" echo "" echo "1. Add /etc/hosts entries (see above)" echo "" echo "2. Create Forgejo admin user:" echo " - Navigate to http://forge.nasty.sh/" echo " - Click 'Register'" echo " - First user becomes admin" echo "" echo "3. Generate NPM token for Verdaccio:" echo " - User Settings → Applications → Generate Token" echo " - Add to $BIGDISK/forgejo/.env: FORGEJO_NPM_TOKEN=" echo " - Restart: sudo systemctl restart devops" echo "" echo "4. Configure Forgejo Runner:" echo " - Admin → Actions → Runners → Create Registration Token" echo " - Runner will auto-register on next start" echo "" echo "5. Configure workstation NPM:" echo " - Run: ./tooling/scripts/dev-setup/configure-verdaccio-client.sh" echo "" echo "Logs:" echo " sudo journalctl -u devops -f" echo "" echo "Restart:" echo " sudo systemctl restart devops" echo "" } # ============================================================================ # Main # ============================================================================ main() { case "$MODE" in --check|-c) log_banner "DevOps Host Setup - Pre-flight Check" check_prerequisites log_info "Pre-flight check passed ✓" ;; --verify|-v) log_banner "DevOps Host Setup - Verification" verify_deployment ;; --full|-f|"") log_banner "DevOps Host Setup - Full Installation" check_prerequisites install_docker create_directories generate_secrets deploy_configs deploy_systemd_service start_services verify_deployment configure_hosts_entries print_next_steps log_banner "Setup Complete!" ;; --help|-h) echo "Provision Ubuntu Host as DevOps Infrastructure" echo "" echo "Usage: $0 [mode]" echo "" echo "Modes:" echo " (default) Full installation" echo " --check, -c Pre-flight check only" echo " --verify, -v Post-install verification" echo " --help, -h Show this help" echo "" echo "Environment Variables:" echo " DEVOPS_HOST_USER SSH user (default: current user)" echo " DEVOPS_HOST_SSH_KEY SSH key path (default: ~/.ssh/id_ed25519)" echo " BIGDISK_PATH Storage path (default: /bigdisk)" echo "" echo "Example:" echo " $0 10.0.0.11 # Full setup" echo " $0 devops.local --check # Check only" echo "" ;; *) log_error "Unknown mode: $MODE" echo "Run '$0 --help' for usage" exit 1 ;; esac } main "$@"