495 lines
14 KiB
Bash
495 lines
14 KiB
Bash
|
|
#!/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 "$@"
|
|||
|
|
|