374 lines
12 KiB
Bash
Executable file
374 lines
12 KiB
Bash
Executable file
#!/bin/bash
|
|
set -e
|
|
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
BLUE='\033[0;34m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
print_header() {
|
|
echo ""
|
|
echo -e "${BLUE}${BOLD}════════════════════════════════════════════════════════════${NC}"
|
|
echo -e "${BLUE}${BOLD} Kaboot Production Setup${NC}"
|
|
echo -e "${BLUE}${BOLD}════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
}
|
|
|
|
print_step() {
|
|
echo -e "${GREEN}${BOLD}▶ $1${NC}"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}⚠ $1${NC}"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}✖ $1${NC}"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}✔ $1${NC}"
|
|
}
|
|
|
|
print_header
|
|
|
|
KABOOT_DOMAIN=""
|
|
AUTH_DOMAIN=""
|
|
GEMINI_API_KEY=""
|
|
TURN_IP=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--domain)
|
|
KABOOT_DOMAIN="$2"
|
|
shift 2
|
|
;;
|
|
--auth-domain)
|
|
AUTH_DOMAIN="$2"
|
|
shift 2
|
|
;;
|
|
--gemini-key)
|
|
GEMINI_API_KEY="$2"
|
|
shift 2
|
|
;;
|
|
--turn-ip)
|
|
TURN_IP="$2"
|
|
shift 2
|
|
;;
|
|
--help|-h)
|
|
echo "Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --domain DOMAIN Main application domain (e.g., kaboot.example.com)"
|
|
echo " --auth-domain DOMAIN Authentication domain (e.g., auth.example.com)"
|
|
echo " --gemini-key KEY Gemini API key for system AI (optional)"
|
|
echo " --turn-ip IP Public IP for TURN server (required for cross-network play)"
|
|
echo " --help, -h Show this help message"
|
|
echo ""
|
|
echo "If options are not provided, you will be prompted for them."
|
|
exit 0
|
|
;;
|
|
*)
|
|
print_error "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ ! -f ".env.example" ]; then
|
|
print_error "Error: .env.example not found. Run this script from the project root."
|
|
exit 1
|
|
fi
|
|
|
|
if [ -f ".env" ]; then
|
|
print_warning ".env file already exists."
|
|
read -p "Overwrite existing configuration? (y/N): " -n 1 -r
|
|
echo ""
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Aborting. Existing configuration preserved."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
echo -e "${BOLD}Domain Configuration${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
|
|
if [ -z "$KABOOT_DOMAIN" ]; then
|
|
read -p "Enter your main domain (e.g., kaboot.example.com): " KABOOT_DOMAIN
|
|
fi
|
|
|
|
if [ -z "$KABOOT_DOMAIN" ]; then
|
|
print_error "Domain is required."
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$AUTH_DOMAIN" ]; then
|
|
DEFAULT_AUTH="auth.${KABOOT_DOMAIN}"
|
|
read -p "Enter your auth domain [$DEFAULT_AUTH]: " AUTH_DOMAIN
|
|
AUTH_DOMAIN=${AUTH_DOMAIN:-$DEFAULT_AUTH}
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}System AI Configuration (Optional)${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo "You can provide a Gemini API key to enable AI quiz generation"
|
|
echo "for all users without requiring them to set up their own key."
|
|
echo -e "${YELLOW}Note: This key will be embedded in the frontend and visible to users.${NC}"
|
|
echo ""
|
|
|
|
if [ -z "$GEMINI_API_KEY" ]; then
|
|
read -p "Enter Gemini API key (or press Enter to skip): " GEMINI_API_KEY
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}TURN Server Configuration (Cross-Network Play)${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo "A TURN server is required for players on different networks"
|
|
echo "(behind NAT/firewalls) to connect to each other."
|
|
echo ""
|
|
|
|
if [ -z "$TURN_IP" ]; then
|
|
read -p "Enter your server's public IP for TURN (or press Enter to skip): " TURN_IP
|
|
fi
|
|
|
|
echo ""
|
|
print_step "Generating secrets..."
|
|
|
|
PG_PASS=$(openssl rand -base64 36 | tr -d '\n' | tr -d '/')
|
|
AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n' | tr -d '/')
|
|
AUTHENTIK_BOOTSTRAP_PASSWORD=$(openssl rand -base64 24 | tr -d '\n' | tr -d '/')
|
|
AUTHENTIK_BOOTSTRAP_TOKEN=$(openssl rand -hex 32)
|
|
ENCRYPTION_KEY=$(openssl rand -base64 36 | tr -d '\n' | tr -d '/')
|
|
TURN_PASSWORD=$(openssl rand -base64 24 | tr -d '\n' | tr -d '/' | tr -d '+')
|
|
|
|
print_success "Secrets generated"
|
|
|
|
print_step "Creating .env file..."
|
|
|
|
cat > .env << EOF
|
|
# Kaboot Production Configuration
|
|
# Generated by setup-prod.sh on $(date)
|
|
|
|
# Domain Configuration (used by docker-compose for frontend build)
|
|
KABOOT_DOMAIN=${KABOOT_DOMAIN}
|
|
AUTH_DOMAIN=${AUTH_DOMAIN}
|
|
|
|
# Database
|
|
PG_PASS=${PG_PASS}
|
|
PG_USER=authentik
|
|
PG_DB=authentik
|
|
|
|
# Authentik Secrets
|
|
AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
|
|
AUTHENTIK_BOOTSTRAP_PASSWORD=${AUTHENTIK_BOOTSTRAP_PASSWORD}
|
|
AUTHENTIK_BOOTSTRAP_TOKEN=${AUTHENTIK_BOOTSTRAP_TOKEN}
|
|
AUTHENTIK_ERROR_REPORTING=false
|
|
|
|
# Backend Encryption
|
|
ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
|
|
# OIDC Configuration (Production)
|
|
OIDC_ISSUER=https://${AUTH_DOMAIN}/application/o/kaboot/
|
|
OIDC_JWKS_URI=https://${AUTH_DOMAIN}/application/o/kaboot/jwks/
|
|
|
|
# Security
|
|
CORS_ORIGIN=https://${KABOOT_DOMAIN}
|
|
NODE_ENV=production
|
|
LOG_REQUESTS=true
|
|
|
|
# System AI (optional - server-side quiz generation)
|
|
GEMINI_API_KEY=${GEMINI_API_KEY}
|
|
|
|
# TURN Server (for cross-network multiplayer)
|
|
VITE_TURN_URL=${TURN_IP:+turn:${TURN_IP}:3478}
|
|
VITE_TURN_USERNAME=${TURN_IP:+kaboot}
|
|
VITE_TURN_CREDENTIAL=${TURN_IP:+${TURN_PASSWORD}}
|
|
EOF
|
|
|
|
print_success "Created .env"
|
|
|
|
print_step "Creating Caddyfile..."
|
|
|
|
cat > Caddyfile << EOF
|
|
# Kaboot Production Caddyfile
|
|
# Generated by setup-prod.sh on $(date)
|
|
|
|
${KABOOT_DOMAIN} {
|
|
handle /api/* {
|
|
reverse_proxy kaboot-backend:3001
|
|
}
|
|
|
|
handle /health {
|
|
reverse_proxy kaboot-backend:3001
|
|
}
|
|
|
|
handle {
|
|
root * /srv/frontend
|
|
try_files {path} /index.html
|
|
file_server
|
|
}
|
|
}
|
|
|
|
${AUTH_DOMAIN} {
|
|
@oidc path /application/o/*
|
|
|
|
handle @oidc {
|
|
reverse_proxy authentik-server:9000 {
|
|
header_up X-Forwarded-Proto {scheme}
|
|
header_up X-Forwarded-Host {host}
|
|
header_down -Access-Control-Allow-Origin
|
|
}
|
|
header Access-Control-Allow-Origin "https://${KABOOT_DOMAIN}"
|
|
header Access-Control-Allow-Methods "GET, POST, OPTIONS"
|
|
header Access-Control-Allow-Headers "Content-Type, Authorization"
|
|
}
|
|
|
|
handle {
|
|
reverse_proxy authentik-server:9000 {
|
|
header_up X-Forwarded-Proto {scheme}
|
|
header_up X-Forwarded-Host {host}
|
|
transport http {
|
|
keepalive 30s
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
|
|
print_success "Created Caddyfile"
|
|
|
|
if [ -n "$TURN_IP" ]; then
|
|
print_step "Creating turnserver.conf..."
|
|
|
|
cat > turnserver.conf << EOF
|
|
# Coturn TURN Server Configuration
|
|
# Generated by setup-prod.sh on $(date)
|
|
|
|
listening-port=3478
|
|
tls-listening-port=5349
|
|
external-ip=${TURN_IP}
|
|
realm=${KABOOT_DOMAIN}
|
|
lt-cred-mech
|
|
user=kaboot:${TURN_PASSWORD}
|
|
fingerprint
|
|
no-multicast-peers
|
|
no-cli
|
|
log-file=/var/log/turnserver.log
|
|
total-quota=100
|
|
stale-nonce=600
|
|
min-port=49152
|
|
max-port=65535
|
|
EOF
|
|
|
|
print_success "Created turnserver.conf"
|
|
fi
|
|
|
|
print_step "Creating production Authentik blueprint..."
|
|
|
|
BLUEPRINT_DIR="authentik/blueprints"
|
|
PROD_BLUEPRINT="${BLUEPRINT_DIR}/kaboot-setup-production.yaml"
|
|
DEV_BLUEPRINT="${BLUEPRINT_DIR}/kaboot-setup.yaml"
|
|
|
|
if [ -f "${BLUEPRINT_DIR}/kaboot-setup-production.yaml.example" ]; then
|
|
sed "s/kaboot.example.com/${KABOOT_DOMAIN}/g; s/auth.example.com/${AUTH_DOMAIN}/g" \
|
|
"${BLUEPRINT_DIR}/kaboot-setup-production.yaml.example" > "$PROD_BLUEPRINT"
|
|
print_success "Created production blueprint"
|
|
else
|
|
print_warning "Production blueprint example not found, skipping..."
|
|
fi
|
|
|
|
if [ -f "$DEV_BLUEPRINT" ]; then
|
|
rm "$DEV_BLUEPRINT"
|
|
print_success "Removed development blueprint"
|
|
fi
|
|
|
|
if [ -n "$GEMINI_API_KEY" ]; then
|
|
print_success "System AI will be available (key configured for backend)"
|
|
else
|
|
print_warning "No Gemini API key provided - users must configure their own"
|
|
fi
|
|
|
|
if [ -n "$TURN_IP" ]; then
|
|
print_success "TURN server configured for cross-network play"
|
|
else
|
|
print_warning "No TURN IP provided - cross-network play may not work"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}════════════════════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN}${BOLD} Setup Complete!${NC}"
|
|
echo -e "${GREEN}${BOLD}════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Configuration Summary${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo -e " Main Domain: ${BLUE}https://${KABOOT_DOMAIN}${NC}"
|
|
echo -e " Auth Domain: ${BLUE}https://${AUTH_DOMAIN}${NC}"
|
|
if [ -n "$GEMINI_API_KEY" ]; then
|
|
echo -e " System AI: ${GREEN}Enabled${NC}"
|
|
else
|
|
echo -e " System AI: ${YELLOW}Disabled (users need own API key)${NC}"
|
|
fi
|
|
if [ -n "$TURN_IP" ]; then
|
|
echo -e " TURN Server: ${GREEN}${TURN_IP}:3478${NC}"
|
|
else
|
|
echo -e " TURN Server: ${YELLOW}Not configured${NC}"
|
|
fi
|
|
echo ""
|
|
echo -e "${BOLD}Authentik Admin${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo -e " Admin URL: ${BLUE}https://${AUTH_DOMAIN}/if/admin/${NC}"
|
|
echo -e " Username: ${YELLOW}akadmin${NC}"
|
|
echo -e " Password: ${YELLOW}${AUTHENTIK_BOOTSTRAP_PASSWORD}${NC}"
|
|
echo ""
|
|
echo -e "${RED}${BOLD}⚠ SAVE THESE CREDENTIALS - They won't be shown again!${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Files Created${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo " .env - Environment variables"
|
|
echo " Caddyfile - Reverse proxy config"
|
|
if [ -n "$TURN_IP" ]; then
|
|
echo " turnserver.conf - TURN server config"
|
|
fi
|
|
echo " authentik/blueprints/kaboot-setup-production.yaml"
|
|
echo ""
|
|
echo -e "${BOLD}Next Steps${NC}"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo ""
|
|
echo " 1. Ensure DNS records point to this server:"
|
|
echo -e " ${KABOOT_DOMAIN} → ${BLUE}<your-server-ip>${NC}"
|
|
echo -e " ${AUTH_DOMAIN} → ${BLUE}<your-server-ip>${NC}"
|
|
echo ""
|
|
if [ -n "$TURN_IP" ]; then
|
|
echo " 2. Open firewall ports for TURN server:"
|
|
echo -e " ${YELLOW}sudo ufw allow 3478/tcp && sudo ufw allow 3478/udp${NC}"
|
|
echo -e " ${YELLOW}sudo ufw allow 5349/tcp && sudo ufw allow 49152:65535/udp${NC}"
|
|
echo ""
|
|
echo " 3. Start the TURN server:"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.turn.yml up -d${NC}"
|
|
echo ""
|
|
echo " 4. Start the production stack:"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d${NC}"
|
|
echo ""
|
|
echo " 5. Wait for services to start (~60 seconds for Authentik)"
|
|
echo ""
|
|
echo " 6. Verify all services are running:"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps${NC}"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.turn.yml ps${NC}"
|
|
else
|
|
echo " 2. Start the production stack:"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d${NC}"
|
|
echo ""
|
|
echo " 3. Wait for services to start (~60 seconds for Authentik)"
|
|
echo ""
|
|
echo " 4. Verify all services are running:"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps${NC}"
|
|
fi
|
|
echo ""
|
|
echo " Check Authentik blueprint was applied:"
|
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml logs authentik-server | grep -i blueprint${NC}"
|
|
echo ""
|
|
echo " Access your app at:"
|
|
echo -e " ${BLUE}https://${KABOOT_DOMAIN}${NC}"
|
|
echo ""
|