494 lines
14 KiB
Bash
Executable file
494 lines
14 KiB
Bash
Executable file
#!/bin/bash
|
||
|
||
###############################################################################
|
||
# Layer 0: Foundation Setup
|
||
#
|
||
# This script sets up your laptop (lapy) as the Ansible control node.
|
||
# It prepares all the prerequisites needed for the infrastructure deployment.
|
||
###############################################################################
|
||
|
||
set -e # Exit on error
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Project root directory
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
|
||
###############################################################################
|
||
# Helper Functions
|
||
###############################################################################
|
||
|
||
print_header() {
|
||
echo -e "\n${BLUE}========================================${NC}"
|
||
echo -e "${BLUE}$1${NC}"
|
||
echo -e "${BLUE}========================================${NC}\n"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✓${NC} $1"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}✗${NC} $1"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠${NC} $1"
|
||
}
|
||
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ${NC} $1"
|
||
}
|
||
|
||
prompt_user() {
|
||
local prompt="$1"
|
||
local default="$2"
|
||
local result
|
||
|
||
if [ -n "$default" ]; then
|
||
read -p "$(echo -e ${BLUE}${prompt}${NC} [${default}]: )" result
|
||
result="${result:-$default}"
|
||
else
|
||
read -p "$(echo -e ${BLUE}${prompt}${NC}: )" result
|
||
fi
|
||
|
||
echo "$result"
|
||
}
|
||
|
||
confirm_action() {
|
||
local prompt="$1"
|
||
local response
|
||
|
||
read -p "$(echo -e ${YELLOW}${prompt}${NC} [y/N]: )" response
|
||
[[ "$response" =~ ^[Yy]$ ]]
|
||
}
|
||
|
||
###############################################################################
|
||
# Main Setup Functions
|
||
###############################################################################
|
||
|
||
check_prerequisites() {
|
||
print_header "Checking Prerequisites"
|
||
|
||
# Check if we're in the right directory
|
||
if [ ! -f "$PROJECT_ROOT/README.md" ] || [ ! -d "$PROJECT_ROOT/ansible" ]; then
|
||
print_error "Not in the correct project directory"
|
||
echo "Expected: $PROJECT_ROOT"
|
||
exit 1
|
||
fi
|
||
print_success "Running from correct directory: $PROJECT_ROOT"
|
||
|
||
# Check if Python 3 is installed
|
||
if ! command -v python3 &> /dev/null; then
|
||
print_error "Python 3 is not installed. Please install Python 3 first."
|
||
exit 1
|
||
fi
|
||
print_success "Python 3 found: $(python3 --version)"
|
||
|
||
# Check if git is installed
|
||
if ! command -v git &> /dev/null; then
|
||
print_warning "Git is not installed. Some features may not work."
|
||
else
|
||
print_success "Git found: $(git --version | head -n1)"
|
||
fi
|
||
}
|
||
|
||
setup_python_venv() {
|
||
print_header "Setting Up Python Virtual Environment"
|
||
|
||
cd "$PROJECT_ROOT"
|
||
|
||
if [ -d "venv" ]; then
|
||
print_info "Virtual environment already exists"
|
||
if confirm_action "Recreate virtual environment?"; then
|
||
rm -rf venv
|
||
python3 -m venv venv
|
||
print_success "Virtual environment recreated"
|
||
else
|
||
print_success "Using existing virtual environment"
|
||
fi
|
||
else
|
||
python3 -m venv venv
|
||
print_success "Virtual environment created"
|
||
fi
|
||
|
||
# Activate venv
|
||
source venv/bin/activate
|
||
print_success "Virtual environment activated"
|
||
|
||
# Upgrade pip
|
||
print_info "Upgrading pip..."
|
||
pip install --upgrade pip > /dev/null 2>&1
|
||
print_success "pip upgraded"
|
||
}
|
||
|
||
install_python_requirements() {
|
||
print_header "Installing Python Requirements"
|
||
|
||
cd "$PROJECT_ROOT"
|
||
|
||
if [ ! -f "requirements.txt" ]; then
|
||
print_error "requirements.txt not found"
|
||
exit 1
|
||
fi
|
||
|
||
print_info "Installing packages from requirements.txt..."
|
||
pip install -r requirements.txt
|
||
print_success "Python requirements installed"
|
||
|
||
# Verify Ansible installation
|
||
if ! command -v ansible &> /dev/null; then
|
||
print_error "Ansible installation failed"
|
||
exit 1
|
||
fi
|
||
print_success "Ansible installed: $(ansible --version | head -n1)"
|
||
}
|
||
|
||
install_ansible_collections() {
|
||
print_header "Installing Ansible Galaxy Collections"
|
||
|
||
cd "$PROJECT_ROOT/ansible"
|
||
|
||
if [ ! -f "requirements.yml" ]; then
|
||
print_warning "requirements.yml not found, skipping Ansible collections"
|
||
return
|
||
fi
|
||
|
||
print_info "Installing Ansible Galaxy collections..."
|
||
ansible-galaxy collection install -r requirements.yml
|
||
print_success "Ansible Galaxy collections installed"
|
||
}
|
||
|
||
setup_inventory_file() {
|
||
print_header "Setting Up Inventory File"
|
||
|
||
cd "$PROJECT_ROOT/ansible"
|
||
|
||
if [ -f "inventory.ini" ]; then
|
||
print_info "inventory.ini already exists"
|
||
cat inventory.ini
|
||
echo ""
|
||
if ! confirm_action "Do you want to update it?"; then
|
||
print_success "Using existing inventory.ini"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
print_info "Let's configure your infrastructure hosts"
|
||
echo ""
|
||
|
||
# Collect information
|
||
echo -e -n "${BLUE}SSH key path${NC} [~/.ssh/counterganzua]: "
|
||
read ssh_key
|
||
ssh_key="${ssh_key:-~/.ssh/counterganzua}"
|
||
|
||
echo ""
|
||
echo "Enter the IP addresses for your infrastructure (VMs will be added later):"
|
||
echo ""
|
||
|
||
echo -e -n "${BLUE}vipy${NC} (main VPS) IP: "
|
||
read vipy_ip
|
||
echo -e -n "${BLUE}watchtower${NC} (monitoring VPS) IP: "
|
||
read watchtower_ip
|
||
echo -e -n "${BLUE}spacey${NC} (headscale VPS) IP: "
|
||
read spacey_ip
|
||
echo -e -n "${BLUE}nodito${NC} (Proxmox server) IP [optional]: "
|
||
read nodito_ip
|
||
|
||
echo ""
|
||
echo -e -n "${BLUE}Your username on lapy${NC} [$(whoami)]: "
|
||
read lapy_user
|
||
lapy_user="${lapy_user:-$(whoami)}"
|
||
|
||
echo -e -n "${BLUE}GPG recipient email${NC} [optional, for encrypted backups]: "
|
||
read gpg_email
|
||
echo -e -n "${BLUE}GPG key ID${NC} [optional, for encrypted backups]: "
|
||
read gpg_key
|
||
|
||
# Generate inventory.ini
|
||
cat > inventory.ini << EOF
|
||
# Ansible Inventory File
|
||
# Generated by setup_layer_0.sh
|
||
|
||
EOF
|
||
|
||
if [ -n "$vipy_ip" ]; then
|
||
cat >> inventory.ini << EOF
|
||
[vipy]
|
||
$vipy_ip ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=$ssh_key
|
||
|
||
EOF
|
||
fi
|
||
|
||
if [ -n "$watchtower_ip" ]; then
|
||
cat >> inventory.ini << EOF
|
||
[watchtower]
|
||
$watchtower_ip ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=$ssh_key
|
||
|
||
EOF
|
||
fi
|
||
|
||
if [ -n "$spacey_ip" ]; then
|
||
cat >> inventory.ini << EOF
|
||
[spacey]
|
||
$spacey_ip ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=$ssh_key
|
||
|
||
EOF
|
||
fi
|
||
|
||
if [ -n "$nodito_ip" ]; then
|
||
cat >> inventory.ini << EOF
|
||
[nodito]
|
||
$nodito_ip ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=$ssh_key
|
||
|
||
EOF
|
||
fi
|
||
|
||
# Add nodito-vms placeholder for VMs that will be created later
|
||
cat >> inventory.ini << EOF
|
||
# Nodito VMs - These don't exist yet and will be created on the Proxmox server
|
||
# Add them here once you create VMs on nodito (e.g., memos-box, etc.)
|
||
[nodito-vms]
|
||
# Example:
|
||
# 192.168.1.150 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=$ssh_key hostname=memos-box
|
||
|
||
EOF
|
||
|
||
# Add lapy
|
||
cat >> inventory.ini << EOF
|
||
# Local connection to laptop: this assumes you're running ansible commands from your personal laptop
|
||
[lapy]
|
||
localhost ansible_connection=local ansible_user=$lapy_user
|
||
EOF
|
||
|
||
if [ -n "$gpg_email" ] && [ -n "$gpg_key" ]; then
|
||
echo " gpg_recipient=$gpg_email gpg_key_id=$gpg_key" >> inventory.ini
|
||
fi
|
||
|
||
print_success "inventory.ini created"
|
||
echo ""
|
||
print_info "Review your inventory file:"
|
||
cat inventory.ini
|
||
echo ""
|
||
}
|
||
|
||
setup_infra_vars() {
|
||
print_header "Setting Up Infrastructure Variables"
|
||
|
||
cd "$PROJECT_ROOT/ansible"
|
||
|
||
if [ -f "infra_vars.yml" ]; then
|
||
print_info "infra_vars.yml already exists"
|
||
cat infra_vars.yml
|
||
echo ""
|
||
if ! confirm_action "Do you want to update it?"; then
|
||
print_success "Using existing infra_vars.yml"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
echo -e -n "${BLUE}Your root domain${NC} (e.g., contrapeso.xyz): "
|
||
read domain
|
||
|
||
while [ -z "$domain" ]; do
|
||
print_warning "Domain cannot be empty"
|
||
echo -e -n "${BLUE}Your root domain${NC}: "
|
||
read domain
|
||
done
|
||
|
||
cat > infra_vars.yml << EOF
|
||
# Infrastructure Variables
|
||
# Generated by setup_layer_0.sh
|
||
|
||
new_user: counterweight
|
||
ssh_port: 22
|
||
allow_ssh_from: "any"
|
||
root_domain: $domain
|
||
EOF
|
||
|
||
print_success "infra_vars.yml created"
|
||
echo ""
|
||
print_info "Contents:"
|
||
cat infra_vars.yml
|
||
echo ""
|
||
}
|
||
|
||
setup_services_config() {
|
||
print_header "Setting Up Services Configuration"
|
||
|
||
cd "$PROJECT_ROOT/ansible"
|
||
|
||
if [ -f "services_config.yml" ]; then
|
||
print_info "services_config.yml already exists"
|
||
if ! confirm_action "Do you want to recreate it from template?"; then
|
||
print_success "Using existing services_config.yml"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
if [ ! -f "services_config.yml.example" ]; then
|
||
print_error "services_config.yml.example not found"
|
||
return
|
||
fi
|
||
|
||
cp services_config.yml.example services_config.yml
|
||
|
||
print_success "services_config.yml created"
|
||
echo ""
|
||
print_info "This file centralizes all service subdomains and Caddy settings"
|
||
print_info "Customize subdomains in: ansible/services_config.yml"
|
||
echo ""
|
||
}
|
||
|
||
setup_infra_secrets() {
|
||
print_header "Setting Up Infrastructure Secrets"
|
||
|
||
cd "$PROJECT_ROOT/ansible"
|
||
|
||
if [ -f "infra_secrets.yml" ]; then
|
||
print_warning "infra_secrets.yml already exists"
|
||
if ! confirm_action "Do you want to recreate the template?"; then
|
||
print_success "Using existing infra_secrets.yml"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
cat > infra_secrets.yml << EOF
|
||
# Infrastructure Secrets
|
||
# Generated by setup_layer_0.sh
|
||
#
|
||
# IMPORTANT: This file contains sensitive credentials
|
||
# It is already in .gitignore - DO NOT commit it to git
|
||
#
|
||
# You'll need to fill in the Uptime Kuma credentials after Layer 4
|
||
# when you deploy Uptime Kuma
|
||
|
||
# Uptime Kuma Credentials (fill these in after deploying Uptime Kuma in Layer 4)
|
||
uptime_kuma_username: ""
|
||
uptime_kuma_password: ""
|
||
EOF
|
||
|
||
print_success "infra_secrets.yml template created"
|
||
print_warning "You'll need to fill in Uptime Kuma credentials after Layer 4"
|
||
echo ""
|
||
}
|
||
|
||
validate_ssh_key() {
|
||
print_header "Validating SSH Key"
|
||
|
||
cd "$PROJECT_ROOT/ansible"
|
||
|
||
# Extract SSH key path from inventory
|
||
if [ -f "inventory.ini" ]; then
|
||
ssh_key=$(grep "ansible_ssh_private_key_file" inventory.ini | head -n1 | sed 's/.*ansible_ssh_private_key_file=\([^ ]*\).*/\1/')
|
||
|
||
# Expand tilde
|
||
ssh_key="${ssh_key/#\~/$HOME}"
|
||
|
||
if [ -f "$ssh_key" ]; then
|
||
print_success "SSH key found: $ssh_key"
|
||
|
||
# Check permissions
|
||
perms=$(stat -c "%a" "$ssh_key" 2>/dev/null || stat -f "%OLp" "$ssh_key" 2>/dev/null)
|
||
if [ "$perms" != "600" ]; then
|
||
print_warning "SSH key permissions are $perms (should be 600)"
|
||
if confirm_action "Fix permissions?"; then
|
||
chmod 600 "$ssh_key"
|
||
print_success "Permissions fixed"
|
||
fi
|
||
else
|
||
print_success "SSH key permissions are correct (600)"
|
||
fi
|
||
else
|
||
print_error "SSH key not found: $ssh_key"
|
||
print_warning "Make sure to create your SSH key before proceeding to Layer 1"
|
||
echo ""
|
||
echo "To generate a new SSH key:"
|
||
echo " ssh-keygen -t ed25519 -f $ssh_key -C \"your-email@example.com\""
|
||
fi
|
||
else
|
||
print_warning "inventory.ini not found, skipping SSH key validation"
|
||
fi
|
||
}
|
||
|
||
print_summary() {
|
||
print_header "Layer 0 Setup Complete! 🎉"
|
||
|
||
echo "Summary of what was configured:"
|
||
echo ""
|
||
print_success "Python virtual environment created and activated"
|
||
print_success "Ansible and dependencies installed"
|
||
print_success "Ansible Galaxy collections installed"
|
||
print_success "inventory.ini configured with your hosts"
|
||
print_success "infra_vars.yml configured with your domain"
|
||
print_success "services_config.yml created with subdomain settings"
|
||
print_success "infra_secrets.yml template created"
|
||
echo ""
|
||
|
||
print_info "Before proceeding to Layer 1:"
|
||
echo " 1. Ensure your SSH key is added to all VPS root users"
|
||
echo " 2. Verify you can SSH into each machine manually"
|
||
echo " 3. Configure DNS nameservers for your domain (if not done)"
|
||
echo ""
|
||
|
||
print_info "Note about inventory groups:"
|
||
echo " • [nodito-vms] group created as placeholder"
|
||
echo " • These VMs will be created later on Proxmox"
|
||
echo " • Add their IPs to inventory.ini once created"
|
||
echo ""
|
||
|
||
print_info "To test SSH access to a host:"
|
||
echo " ssh -i ~/.ssh/counterganzua root@<host-ip>"
|
||
echo ""
|
||
|
||
print_info "Next steps:"
|
||
echo " 1. Review the files in ansible/"
|
||
echo " 2. Test SSH connections to your hosts"
|
||
echo " 3. Proceed to Layer 1: ./scripts/setup_layer_1.sh"
|
||
echo ""
|
||
|
||
print_warning "Remember to activate the venv before running other commands:"
|
||
echo " source venv/bin/activate"
|
||
echo ""
|
||
}
|
||
|
||
###############################################################################
|
||
# Main Execution
|
||
###############################################################################
|
||
|
||
main() {
|
||
clear
|
||
|
||
print_header "🚀 Layer 0: Foundation Setup"
|
||
|
||
echo "This script will set up your laptop (lapy) as the Ansible control node."
|
||
echo "It will install all prerequisites and configure basic settings."
|
||
echo ""
|
||
|
||
if ! confirm_action "Continue with Layer 0 setup?"; then
|
||
echo "Setup cancelled."
|
||
exit 0
|
||
fi
|
||
|
||
check_prerequisites
|
||
setup_python_venv
|
||
install_python_requirements
|
||
install_ansible_collections
|
||
setup_inventory_file
|
||
setup_infra_vars
|
||
setup_services_config
|
||
setup_infra_secrets
|
||
validate_ssh_key
|
||
print_summary
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|
||
|