personal_infra/scripts/setup_layer_8_secondary_services.sh
2025-12-01 11:17:02 +01:00

384 lines
11 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
###############################################################################
# Layer 8: Secondary Services
#
# This script deploys the ntfy-emergency-app and memos services.
# Must be run after Layers 0-7 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 directories
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ANSIBLE_DIR="$PROJECT_ROOT/ansible"
declare -a LAYER_SUMMARY=()
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]$ ]]
}
record_summary() {
LAYER_SUMMARY+=("$1")
}
get_hosts_from_inventory() {
local target="$1"
cd "$ANSIBLE_DIR"
ansible-inventory -i inventory.ini --list | \
python3 - "$target" <<'PY' 2>/dev/null || echo ""
import json, sys
data = json.load(sys.stdin)
target = sys.argv[1]
if target in data:
print(' '.join(data[target].get('hosts', [])))
else:
hostvars = data.get('_meta', {}).get('hostvars', {})
if target in hostvars:
print(target)
PY
}
get_primary_host_ip() {
local target="$1"
cd "$ANSIBLE_DIR"
ansible-inventory -i inventory.ini --list | \
python3 - "$target" <<'PY' 2>/dev/null || echo ""
import json, sys
data = json.load(sys.stdin)
target = sys.argv[1]
hostvars = data.get('_meta', {}).get('hostvars', {})
if target in hostvars:
print(hostvars[target].get('ansible_host', target))
else:
hosts = data.get(target, {}).get('hosts', [])
if hosts:
first = hosts[0]
hv = hostvars.get(first, {})
print(hv.get('ansible_host', first))
PY
}
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
if [ ! -f "$ANSIBLE_DIR/infra_vars.yml" ]; then
print_error "infra_vars.yml not found"
((errors++))
else
print_success "infra_vars.yml exists"
fi
if [ ! -f "$ANSIBLE_DIR/services_config.yml" ]; then
print_error "services_config.yml not found"
((errors++))
else
print_success "services_config.yml exists"
fi
if [ -z "$(get_hosts_from_inventory "vipy")" ]; then
print_error "vipy not configured in inventory.ini"
((errors++))
else
print_success "vipy configured in inventory"
fi
if [ -z "$(get_hosts_from_inventory "memos-box")" ]; then
print_warning "memos-box not configured in inventory.ini (memos deployment will be skipped)"
else
print_success "memos-box configured in inventory"
fi
if [ $errors -gt 0 ]; then
print_error "Prerequisites not met. Resolve the issues above and re-run the script."
exit 1
fi
print_success "Prerequisites verified"
# Display configured subdomains
local emergency_subdomain=$(grep "^ ntfy_emergency_app:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "emergency")
local memos_subdomain=$(grep "^ memos:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "memos")
print_info "Configured subdomains:"
echo " • ntfy_emergency_app: $emergency_subdomain"
echo " • memos: $memos_subdomain"
echo ""
}
check_dns_configuration() {
print_header "Validating DNS Configuration"
if ! command -v dig &> /dev/null; then
print_warning "dig command not found. Skipping DNS validation."
print_info "Install dnsutils/bind-tools to enable DNS validation."
return 0
fi
cd "$ANSIBLE_DIR"
local root_domain
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
local emergency_subdomain=$(grep "^ ntfy_emergency_app:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "emergency")
local memos_subdomain=$(grep "^ memos:" "$ANSIBLE_DIR/services_config.yml" | awk '{print $2}' 2>/dev/null || echo "memos")
local vipy_ip
vipy_ip=$(get_primary_host_ip "vipy")
if [ -z "$vipy_ip" ]; then
print_error "Unable to determine vipy IP from inventory"
return 1
fi
local memos_ip=""
local memos_host=$(get_hosts_from_inventory "memos-box")
if [ -n "$memos_host" ]; then
memos_ip=$(get_primary_host_ip "$memos_host")
fi
local dns_ok=true
local emergency_fqdn="${emergency_subdomain}.${root_domain}"
local memos_fqdn="${memos_subdomain}.${root_domain}"
print_info "Expected DNS:"
echo "$emergency_fqdn$vipy_ip"
if [ -n "$memos_ip" ]; then
echo "$memos_fqdn$memos_ip"
else
echo "$memos_fqdn → (skipped - memos-box not in inventory)"
fi
echo ""
local resolved
print_info "Checking $emergency_fqdn..."
resolved=$(dig +short "$emergency_fqdn" | head -n1)
if [ "$resolved" = "$vipy_ip" ]; then
print_success "$emergency_fqdn resolves to $resolved"
elif [ -n "$resolved" ]; then
print_error "$emergency_fqdn resolves to $resolved (expected $vipy_ip)"
dns_ok=false
else
print_error "$emergency_fqdn does not resolve"
dns_ok=false
fi
if [ -n "$memos_ip" ]; then
print_info "Checking $memos_fqdn..."
resolved=$(dig +short "$memos_fqdn" | head -n1)
if [ "$resolved" = "$memos_ip" ]; then
print_success "$memos_fqdn resolves to $resolved"
elif [ -n "$resolved" ]; then
print_error "$memos_fqdn resolves to $resolved (expected $memos_ip)"
dns_ok=false
else
print_error "$memos_fqdn does not resolve"
dns_ok=false
fi
fi
echo ""
if [ "$dns_ok" = false ]; then
print_error "DNS validation failed."
print_info "Update DNS records as shown above and wait for propagation."
echo ""
if ! confirm_action "Continue anyway? (SSL certificates will fail without correct DNS)"; then
exit 1
fi
else
print_success "DNS validation passed"
fi
}
deploy_ntfy_emergency_app() {
print_header "Deploying ntfy-emergency-app"
cd "$ANSIBLE_DIR"
print_info "This deploys the emergency notification interface pointing at ntfy."
echo ""
if ! confirm_action "Deploy / update the ntfy-emergency-app?"; then
print_warning "Skipped ntfy-emergency-app deployment"
record_summary "${YELLOW}• ntfy-emergency-app${NC}: skipped"
return 0
fi
print_info "Running: ansible-playbook -i inventory.ini services/ntfy-emergency-app/deploy_ntfy_emergency_app_playbook.yml"
echo ""
if ansible-playbook -i inventory.ini services/ntfy-emergency-app/deploy_ntfy_emergency_app_playbook.yml; then
print_success "ntfy-emergency-app deployed successfully"
record_summary "${GREEN}• ntfy-emergency-app${NC}: deployed"
else
print_error "ntfy-emergency-app deployment failed"
record_summary "${RED}• ntfy-emergency-app${NC}: failed"
fi
}
deploy_memos() {
print_header "Deploying Memos"
if [ -z "$(get_hosts_from_inventory "memos-box")" ]; then
print_warning "memos-box not in inventory. Skipping memos deployment."
record_summary "${YELLOW}• memos${NC}: skipped (memos-box missing)"
return 0
fi
cd "$ANSIBLE_DIR"
if ! confirm_action "Deploy / update memos on memos-box?"; then
print_warning "Skipped memos deployment"
record_summary "${YELLOW}• memos${NC}: skipped"
return 0
fi
print_info "Running: ansible-playbook -i inventory.ini services/memos/deploy_memos_playbook.yml"
echo ""
if ansible-playbook -i inventory.ini services/memos/deploy_memos_playbook.yml; then
print_success "Memos deployed successfully"
record_summary "${GREEN}• memos${NC}: deployed"
else
print_error "Memos deployment failed"
record_summary "${RED}• memos${NC}: failed"
fi
}
verify_services() {
print_header "Verifying 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
vipy_host=$(get_hosts_from_inventory "vipy")
if [ -n "$vipy_host" ]; then
print_info "Checking services on vipy ($vipy_host)..."
if timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$vipy_host "docker ps | grep ntfy-emergency-app" &>/dev/null; then
print_success "ntfy-emergency-app container running"
else
print_warning "ntfy-emergency-app container not running"
fi
echo ""
fi
local memos_host
memos_host=$(get_hosts_from_inventory "memos-box")
if [ -n "$memos_host" ]; then
print_info "Checking memos on memos-box ($memos_host)..."
if timeout 5 ssh -i "$ssh_key" -o StrictHostKeyChecking=no -o BatchMode=yes counterweight@$memos_host "systemctl is-active memos" &>/dev/null; then
print_success "memos systemd service running"
else
print_warning "memos systemd service not running"
fi
echo ""
fi
}
print_summary() {
print_header "Layer 8 Summary"
if [ ${#LAYER_SUMMARY[@]} -eq 0 ]; then
print_info "No actions were performed."
return
fi
for entry in "${LAYER_SUMMARY[@]}"; do
echo -e "$entry"
done
echo ""
print_info "Next steps:"
echo " • Visit each service's subdomain to complete any manual setup."
echo " • Configure backups for new services if applicable."
echo " • Update Uptime Kuma monitors if additional endpoints are desired."
}
main() {
print_header "Layer 8: Secondary Services"
check_prerequisites
check_dns_configuration
deploy_ntfy_emergency_app
deploy_memos
verify_services
print_summary
}
main "$@"