494 lines
16 KiB
Bash
Executable file
494 lines
16 KiB
Bash
Executable file
#!/bin/bash
|
||
|
||
###############################################################################
|
||
# Layer 7: Core Services
|
||
#
|
||
# This script deploys Vaultwarden, Forgejo, and LNBits on vipy.
|
||
# Must be run after Layers 0, 1A, 2, and 3 are complete.
|
||
###############################################################################
|
||
|
||
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)"
|
||
ANSIBLE_DIR="$PROJECT_ROOT/ansible"
|
||
|
||
###############################################################################
|
||
# 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"
|
||
}
|
||
|
||
confirm_action() {
|
||
local prompt="$1"
|
||
local response
|
||
|
||
read -p "$(echo -e ${YELLOW}${prompt}${NC} [y/N]: )" response
|
||
[[ "$response" =~ ^[Yy]$ ]]
|
||
}
|
||
|
||
###############################################################################
|
||
# Verification Functions
|
||
###############################################################################
|
||
|
||
check_prerequisites() {
|
||
print_header "Verifying Prerequisites"
|
||
|
||
local errors=0
|
||
|
||
if [ -z "$VIRTUAL_ENV" ]; then
|
||
print_error "Virtual environment not activated"
|
||
echo "Run: source venv/bin/activate"
|
||
((errors++))
|
||
else
|
||
print_success "Virtual environment activated"
|
||
fi
|
||
|
||
if ! command -v ansible &> /dev/null; then
|
||
print_error "Ansible not found"
|
||
((errors++))
|
||
else
|
||
print_success "Ansible found"
|
||
fi
|
||
|
||
if [ ! -f "$ANSIBLE_DIR/inventory.ini" ]; then
|
||
print_error "inventory.ini not found"
|
||
((errors++))
|
||
else
|
||
print_success "inventory.ini exists"
|
||
fi
|
||
|
||
# Check if vipy is configured
|
||
if ! grep -q "^\[vipy\]" "$ANSIBLE_DIR/inventory.ini"; then
|
||
print_error "vipy not configured in inventory.ini"
|
||
print_info "Layer 7 requires vipy VPS"
|
||
((errors++))
|
||
else
|
||
print_success "vipy configured in inventory"
|
||
fi
|
||
|
||
if [ $errors -gt 0 ]; then
|
||
print_error "Prerequisites not met"
|
||
exit 1
|
||
fi
|
||
|
||
print_success "Prerequisites verified"
|
||
}
|
||
|
||
get_hosts_from_inventory() {
|
||
local group="$1"
|
||
cd "$ANSIBLE_DIR"
|
||
ansible-inventory -i inventory.ini --list | \
|
||
python3 -c "import sys, json; data=json.load(sys.stdin); print(' '.join(data.get('$group', {}).get('hosts', [])))" 2>/dev/null || echo ""
|
||
}
|
||
|
||
check_dns_configuration() {
|
||
print_header "Validating DNS Configuration"
|
||
|
||
cd "$ANSIBLE_DIR"
|
||
|
||
# Get vipy IP
|
||
local vipy_ip=$(ansible-inventory -i inventory.ini --list | python3 -c "import sys, json; data=json.load(sys.stdin); hosts=data.get('vipy', {}).get('hosts', []); print(hosts[0] if hosts else '')" 2>/dev/null)
|
||
|
||
if [ -z "$vipy_ip" ]; then
|
||
print_error "Could not determine vipy IP from inventory"
|
||
return 1
|
||
fi
|
||
|
||
print_info "Vipy IP: $vipy_ip"
|
||
echo ""
|
||
|
||
# Get domain from infra_vars.yml
|
||
local root_domain=$(grep "^root_domain:" "$ANSIBLE_DIR/infra_vars.yml" | awk '{print $2}' 2>/dev/null)
|
||
|
||
if [ -z "$root_domain" ]; then
|
||
print_error "Could not determine root_domain from infra_vars.yml"
|
||
return 1
|
||
fi
|
||
|
||
# Get subdomains from centralized config
|
||
local vw_subdomain="vault"
|
||
local fg_subdomain="git"
|
||
local ln_subdomain="lnbits"
|
||
|
||
if [ -f "$ANSIBLE_DIR/services_config.yml" ]; then
|
||
vw_subdomain=$(grep "^ vaultwarden:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "vault")
|
||
fg_subdomain=$(grep "^ forgejo:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "git")
|
||
ln_subdomain=$(grep "^ lnbits:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "lnbits")
|
||
fi
|
||
|
||
print_info "Checking DNS records..."
|
||
echo ""
|
||
|
||
local dns_ok=true
|
||
|
||
if command -v dig &> /dev/null; then
|
||
# Check each subdomain
|
||
for service in "vaultwarden:$vw_subdomain" "forgejo:$fg_subdomain" "lnbits:$ln_subdomain"; do
|
||
local name=$(echo "$service" | cut -d: -f1)
|
||
local subdomain=$(echo "$service" | cut -d: -f2)
|
||
local fqdn="${subdomain}.${root_domain}"
|
||
|
||
print_info "Checking $fqdn..."
|
||
local resolved=$(dig +short "$fqdn" | head -n1)
|
||
|
||
if [ "$resolved" = "$vipy_ip" ]; then
|
||
print_success "$fqdn → $resolved ✓"
|
||
elif [ -n "$resolved" ]; then
|
||
print_error "$fqdn → $resolved (expected $vipy_ip)"
|
||
dns_ok=false
|
||
else
|
||
print_error "$fqdn does not resolve"
|
||
dns_ok=false
|
||
fi
|
||
done
|
||
else
|
||
print_warning "dig command not found, skipping DNS validation"
|
||
print_info "Install dnsutils/bind-tools to enable DNS validation"
|
||
return 1
|
||
fi
|
||
|
||
echo ""
|
||
|
||
if [ "$dns_ok" = false ]; then
|
||
print_error "DNS validation failed"
|
||
print_info "Please configure DNS records for all services"
|
||
echo ""
|
||
print_warning "DNS changes can take time to propagate"
|
||
echo ""
|
||
if ! confirm_action "Continue anyway? (SSL certificates will fail without proper DNS)"; then
|
||
exit 1
|
||
fi
|
||
else
|
||
print_success "DNS validation passed"
|
||
fi
|
||
}
|
||
|
||
###############################################################################
|
||
# Service Deployment
|
||
###############################################################################
|
||
|
||
deploy_vaultwarden() {
|
||
print_header "Deploying Vaultwarden (Password Manager)"
|
||
|
||
cd "$ANSIBLE_DIR"
|
||
|
||
print_info "This will:"
|
||
echo " • Deploy Vaultwarden via Docker"
|
||
echo " • Configure Caddy reverse proxy"
|
||
echo " • Set up fail2ban protection"
|
||
echo " • Enable sign-ups (disable after first user)"
|
||
echo ""
|
||
|
||
if ! confirm_action "Proceed with Vaultwarden deployment?"; then
|
||
print_warning "Skipped Vaultwarden deployment"
|
||
return 0
|
||
fi
|
||
|
||
print_info "Running: ansible-playbook -i inventory.ini services/vaultwarden/deploy_vaultwarden_playbook.yml"
|
||
echo ""
|
||
|
||
if ansible-playbook -i inventory.ini services/vaultwarden/deploy_vaultwarden_playbook.yml; then
|
||
print_success "Vaultwarden deployed"
|
||
echo ""
|
||
print_warning "POST-DEPLOYMENT:"
|
||
echo " 1. Visit your Vaultwarden subdomain"
|
||
echo " 2. Create your first user account"
|
||
echo " 3. Run: ansible-playbook -i inventory.ini services/vaultwarden/disable_vaultwarden_sign_ups_playbook.yml"
|
||
return 0
|
||
else
|
||
print_error "Vaultwarden deployment failed"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
deploy_forgejo() {
|
||
print_header "Deploying Forgejo (Git Server)"
|
||
|
||
cd "$ANSIBLE_DIR"
|
||
|
||
print_info "This will:"
|
||
echo " • Install Forgejo binary"
|
||
echo " • Create git user and directories"
|
||
echo " • Configure Caddy reverse proxy"
|
||
echo " • Enable SSH cloning on port 22"
|
||
echo ""
|
||
|
||
if ! confirm_action "Proceed with Forgejo deployment?"; then
|
||
print_warning "Skipped Forgejo deployment"
|
||
return 0
|
||
fi
|
||
|
||
print_info "Running: ansible-playbook -i inventory.ini services/forgejo/deploy_forgejo_playbook.yml"
|
||
echo ""
|
||
|
||
if ansible-playbook -i inventory.ini services/forgejo/deploy_forgejo_playbook.yml; then
|
||
print_success "Forgejo deployed"
|
||
echo ""
|
||
print_warning "POST-DEPLOYMENT:"
|
||
echo " 1. Visit your Forgejo subdomain"
|
||
echo " 2. Create admin account on first visit"
|
||
echo " 3. Add your SSH key for git cloning"
|
||
return 0
|
||
else
|
||
print_error "Forgejo deployment failed"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
deploy_lnbits() {
|
||
print_header "Deploying LNBits (Lightning Wallet)"
|
||
|
||
cd "$ANSIBLE_DIR"
|
||
|
||
print_info "This will:"
|
||
echo " • Install system dependencies and uv (Python 3.12 tooling)"
|
||
echo " • Clone LNBits repository (version v1.3.1)"
|
||
echo " • Sync dependencies with uv targeting Python 3.12"
|
||
echo " • Configure with FakeWallet (testing)"
|
||
echo " • Create systemd service"
|
||
echo " • Configure Caddy reverse proxy"
|
||
echo ""
|
||
|
||
if ! confirm_action "Proceed with LNBits deployment?"; then
|
||
print_warning "Skipped LNBits deployment"
|
||
return 0
|
||
fi
|
||
|
||
print_info "Running: ansible-playbook -i inventory.ini services/lnbits/deploy_lnbits_playbook.yml"
|
||
echo ""
|
||
|
||
if ansible-playbook -i inventory.ini services/lnbits/deploy_lnbits_playbook.yml; then
|
||
print_success "LNBits deployed"
|
||
echo ""
|
||
print_warning "POST-DEPLOYMENT:"
|
||
echo " 1. Visit your LNBits subdomain"
|
||
echo " 2. Create superuser on first visit"
|
||
echo " 3. Configure real Lightning backend (FakeWallet is for testing only)"
|
||
echo " 4. Disable new user registration"
|
||
return 0
|
||
else
|
||
print_error "LNBits deployment failed"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
###############################################################################
|
||
# Backup Configuration
|
||
###############################################################################
|
||
|
||
setup_backups() {
|
||
print_header "Setting Up Backups (Optional)"
|
||
|
||
cd "$ANSIBLE_DIR"
|
||
|
||
print_info "Configure automated backups to lapy"
|
||
echo ""
|
||
|
||
# Vaultwarden backup
|
||
if confirm_action "Set up Vaultwarden backup to lapy?"; then
|
||
print_info "Running: ansible-playbook -i inventory.ini services/vaultwarden/setup_backup_vaultwarden_to_lapy.yml"
|
||
if ansible-playbook -i inventory.ini services/vaultwarden/setup_backup_vaultwarden_to_lapy.yml; then
|
||
print_success "Vaultwarden backup configured"
|
||
else
|
||
print_error "Vaultwarden backup setup failed"
|
||
fi
|
||
echo ""
|
||
fi
|
||
|
||
# LNBits backup
|
||
if confirm_action "Set up LNBits backup to lapy (GPG encrypted)?"; then
|
||
print_info "Running: ansible-playbook -i inventory.ini services/lnbits/setup_backup_lnbits_to_lapy.yml"
|
||
if ansible-playbook -i inventory.ini services/lnbits/setup_backup_lnbits_to_lapy.yml; then
|
||
print_success "LNBits backup configured"
|
||
else
|
||
print_error "LNBits backup setup failed"
|
||
fi
|
||
echo ""
|
||
fi
|
||
|
||
print_warning "Forgejo backups are not automated - set up manually if needed"
|
||
}
|
||
|
||
###############################################################################
|
||
# Verification
|
||
###############################################################################
|
||
|
||
verify_services() {
|
||
print_header "Verifying Service Deployments"
|
||
|
||
cd "$ANSIBLE_DIR"
|
||
|
||
local ssh_key=$(grep "ansible_ssh_private_key_file" "$ANSIBLE_DIR/inventory.ini" | head -n1 | sed 's/.*ansible_ssh_private_key_file=\([^ ]*\).*/\1/')
|
||
ssh_key="${ssh_key/#\~/$HOME}"
|
||
|
||
local vipy_host=$(get_hosts_from_inventory "vipy")
|
||
|
||
if [ -z "$vipy_host" ]; then
|
||
print_error "Could not determine vipy host"
|
||
return
|
||
fi
|
||
|
||
print_info "Checking services on vipy ($vipy_host)..."
|
||
echo ""
|
||
|
||
# Check Vaultwarden
|
||
if timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$vipy_host "docker ps | grep vaultwarden" &>/dev/null; then
|
||
print_success "Vaultwarden container running"
|
||
else
|
||
print_warning "Vaultwarden container not running (may not be deployed)"
|
||
fi
|
||
|
||
# Check Forgejo
|
||
if timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$vipy_host "systemctl is-active forgejo" &>/dev/null; then
|
||
print_success "Forgejo service running"
|
||
else
|
||
print_warning "Forgejo service not running (may not be deployed)"
|
||
fi
|
||
|
||
# Check LNBits
|
||
if timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$vipy_host "systemctl is-active lnbits" &>/dev/null; then
|
||
print_success "LNBits service running"
|
||
else
|
||
print_warning "LNBits service not running (may not be deployed)"
|
||
fi
|
||
|
||
# Check Caddy configs
|
||
if timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$vipy_host "ls /etc/caddy/sites-enabled/*.conf 2>/dev/null" &>/dev/null; then
|
||
print_success "Caddy configs exist"
|
||
local configs=$(timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$vipy_host "ls /etc/caddy/sites-enabled/*.conf 2>/dev/null" | xargs -n1 basename)
|
||
print_info "Configured services:"
|
||
echo "$configs" | sed 's/^/ /'
|
||
else
|
||
print_warning "No Caddy configs found"
|
||
fi
|
||
|
||
echo ""
|
||
}
|
||
|
||
###############################################################################
|
||
# Summary
|
||
###############################################################################
|
||
|
||
print_summary() {
|
||
print_header "Layer 7 Setup Complete! 🎉"
|
||
|
||
echo "Summary of what was deployed:"
|
||
echo ""
|
||
print_success "Core services deployed on vipy"
|
||
echo ""
|
||
|
||
print_warning "CRITICAL POST-DEPLOYMENT STEPS:"
|
||
echo ""
|
||
echo "For each service you deployed, you MUST:"
|
||
echo ""
|
||
|
||
echo "1. Vaultwarden (if deployed):"
|
||
echo " • Visit web UI and create first user"
|
||
echo " • Disable sign-ups: ansible-playbook -i inventory.ini services/vaultwarden/disable_vaultwarden_sign_ups_playbook.yml"
|
||
echo " • Optional: Set up backup"
|
||
echo ""
|
||
|
||
echo "2. Forgejo (if deployed):"
|
||
echo " • Visit web UI and create admin account"
|
||
echo " • Add your SSH public key for git operations"
|
||
echo " • Test cloning: git clone git@<forgejo_subdomain>.<yourdomain>:username/repo.git"
|
||
echo ""
|
||
|
||
echo "3. LNBits (if deployed):"
|
||
echo " • Visit web UI and create superuser"
|
||
echo " • Configure real Lightning backend (currently FakeWallet)"
|
||
echo " • Disable new user registration"
|
||
echo " • Optional: Set up encrypted backup"
|
||
echo ""
|
||
|
||
print_info "Services are now accessible:"
|
||
echo " • Vaultwarden: https://<vaultwarden_subdomain>.<yourdomain>"
|
||
echo " • Forgejo: https://<forgejo_subdomain>.<yourdomain>"
|
||
echo " • LNBits: https://<lnbits_subdomain>.<yourdomain>"
|
||
echo ""
|
||
|
||
print_success "Uptime Kuma monitors automatically created:"
|
||
echo " • Check Uptime Kuma web UI"
|
||
echo " • Look in 'services' monitor group"
|
||
echo " • Monitors for Vaultwarden, Forgejo, LNBits should appear"
|
||
echo ""
|
||
|
||
print_info "Next steps:"
|
||
echo " 1. Complete post-deployment steps above"
|
||
echo " 2. Test each service"
|
||
echo " 3. Check Uptime Kuma monitors are working"
|
||
echo " 4. Proceed to Layer 8: ./scripts/setup_layer_8_secondary_services.sh"
|
||
echo ""
|
||
}
|
||
|
||
###############################################################################
|
||
# Main Execution
|
||
###############################################################################
|
||
|
||
main() {
|
||
clear
|
||
|
||
print_header "🚀 Layer 7: Core Services"
|
||
|
||
echo "This script will deploy core services on vipy:"
|
||
echo " • Vaultwarden (password manager)"
|
||
echo " • Forgejo (git server)"
|
||
echo " • LNBits (Lightning wallet)"
|
||
echo ""
|
||
|
||
if ! confirm_action "Continue with Layer 7 setup?"; then
|
||
echo "Setup cancelled."
|
||
exit 0
|
||
fi
|
||
|
||
check_prerequisites
|
||
check_dns_configuration
|
||
|
||
# Deploy services
|
||
deploy_vaultwarden
|
||
echo ""
|
||
deploy_forgejo
|
||
echo ""
|
||
deploy_lnbits
|
||
|
||
echo ""
|
||
verify_services
|
||
|
||
echo ""
|
||
setup_backups
|
||
|
||
print_summary
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|
||
|