Updated pnpm version from 10.19.0 to 10.27.0 to fix HIGH severity vulnerabilities (CVE-2025-69262, CVE-2025-69263, CVE-2025-6926). Changes: - apps/api/Dockerfile: line 8 - apps/web/Dockerfile: lines 8 and 81 Fixes #180
1337 lines
44 KiB
Bash
Executable File
1337 lines
44 KiB
Bash
Executable File
#!/bin/bash
|
|
# Calibr Setup Wizard
|
|
# Interactive installer for Calibr sports betting prediction system
|
|
# Supports both native Python and Docker deployments
|
|
|
|
set -e
|
|
|
|
# ============================================================================
|
|
# Configuration
|
|
# ============================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
# Set up logging
|
|
LOG_DIR="$PROJECT_ROOT/logs"
|
|
mkdir -p "$LOG_DIR"
|
|
LOG_FILE="$LOG_DIR/setup-$(date +%Y%m%d_%H%M%S).log"
|
|
|
|
# Redirect stdout/stderr to both console and log file
|
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
|
|
# Send trace output (set -x) ONLY to log file on fd 3
|
|
exec 3>>"$LOG_FILE"
|
|
export BASH_XTRACEFD=3
|
|
|
|
# Enable verbose command tracing (only goes to log file now)
|
|
set -x
|
|
|
|
echo "==================================================================="
|
|
echo "Calibr Setup Wizard"
|
|
echo "Started: $(date)"
|
|
echo "Full log: $LOG_FILE"
|
|
echo "==================================================================="
|
|
echo ""
|
|
|
|
# Source common functions
|
|
# shellcheck source=lib/common.sh
|
|
source "$SCRIPT_DIR/lib/common.sh"
|
|
|
|
# ============================================================================
|
|
# Global Variables
|
|
# ============================================================================
|
|
|
|
NON_INTERACTIVE=false
|
|
DRY_RUN=false
|
|
MODE=""
|
|
ODDS_API_KEY=""
|
|
ENABLE_SSO=false
|
|
ENABLE_TELEGRAM=false
|
|
ENABLE_ML=false
|
|
TELEGRAM_BOT_TOKEN=""
|
|
TELEGRAM_CHAT_ID=""
|
|
USE_BUNDLED_AUTHENTIK=false
|
|
EXTERNAL_AUTHENTIK_URL=""
|
|
CALIBR_BASE_URL=""
|
|
CALIBR_ALLOWED_HOSTS=""
|
|
AUTHENTIK_BASE_URL=""
|
|
|
|
DETECTED_OS=""
|
|
DETECTED_PKG_MANAGER=""
|
|
PORT_OVERRIDES=()
|
|
|
|
# ============================================================================
|
|
# Help and Usage
|
|
# ============================================================================
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
Calibr Setup Wizard
|
|
|
|
USAGE:
|
|
$0 [OPTIONS]
|
|
|
|
OPTIONS:
|
|
-h, --help Show this help message
|
|
--non-interactive Run in non-interactive mode (requires all options)
|
|
--dry-run Show what would happen without executing
|
|
--mode MODE Deployment mode: native or docker
|
|
--odds-api-key KEY The Odds API key (required)
|
|
--enable-sso Enable Authentik SSO (Docker mode only)
|
|
--bundled-authentik Use bundled Authentik server (with --enable-sso)
|
|
--external-authentik URL Use external Authentik server URL (with --enable-sso)
|
|
--enable-telegram Enable Telegram bot notifications
|
|
--enable-ml Enable ML prediction models
|
|
--telegram-token TOKEN Telegram bot token (if --enable-telegram)
|
|
--telegram-chat-id ID Telegram chat ID (if --enable-telegram)
|
|
|
|
EXAMPLES:
|
|
# Interactive mode (recommended for first-time setup)
|
|
$0
|
|
|
|
# Non-interactive Docker deployment with bundled SSO
|
|
$0 --non-interactive --mode docker --odds-api-key "abc123" --enable-sso --bundled-authentik
|
|
|
|
# Non-interactive Docker with external Authentik
|
|
$0 --non-interactive --mode docker --odds-api-key "abc123" --enable-sso --external-authentik "https://auth.example.com"
|
|
|
|
# Non-interactive native deployment with ML models
|
|
$0 --non-interactive --mode native --odds-api-key "abc123" --enable-ml
|
|
|
|
# Dry run to see what would happen
|
|
$0 --dry-run --mode docker --odds-api-key "abc123" --enable-sso --bundled-authentik
|
|
|
|
EOF
|
|
}
|
|
|
|
# ============================================================================
|
|
# Argument Parsing
|
|
# ============================================================================
|
|
|
|
parse_arguments() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
--non-interactive)
|
|
NON_INTERACTIVE=true
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
;;
|
|
--mode)
|
|
MODE="$2"
|
|
shift
|
|
;;
|
|
--odds-api-key)
|
|
ODDS_API_KEY="$2"
|
|
shift
|
|
;;
|
|
--enable-sso)
|
|
ENABLE_SSO=true
|
|
;;
|
|
--bundled-authentik)
|
|
USE_BUNDLED_AUTHENTIK=true
|
|
;;
|
|
--external-authentik)
|
|
EXTERNAL_AUTHENTIK_URL="$2"
|
|
shift
|
|
;;
|
|
--enable-telegram)
|
|
ENABLE_TELEGRAM=true
|
|
;;
|
|
--enable-ml)
|
|
ENABLE_ML=true
|
|
;;
|
|
--telegram-token)
|
|
TELEGRAM_BOT_TOKEN="$2"
|
|
shift
|
|
;;
|
|
--telegram-chat-id)
|
|
TELEGRAM_CHAT_ID="$2"
|
|
shift
|
|
;;
|
|
*)
|
|
print_error "Unknown option: $1"
|
|
echo "Use --help for usage information"
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Validate non-interactive mode
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
if [[ -z "$MODE" || -z "$ODDS_API_KEY" ]]; then
|
|
print_error "Non-interactive mode requires --mode and --odds-api-key"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$MODE" != "native" && "$MODE" != "docker" ]]; then
|
|
print_error "Invalid mode: $MODE (must be 'native' or 'docker')"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$ENABLE_TELEGRAM" == true ]]; then
|
|
if [[ -z "$TELEGRAM_BOT_TOKEN" || -z "$TELEGRAM_CHAT_ID" ]]; then
|
|
print_error "Telegram enabled but --telegram-token or --telegram-chat-id missing"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Welcome Banner
|
|
# ============================================================================
|
|
|
|
show_banner() {
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat << "EOF"
|
|
|
|
____ _ _ _
|
|
/ ___|__ _| (_) |__ _ __
|
|
| | / _` | | | '_ \| '__|
|
|
| |__| (_| | | | |_) | |
|
|
\____\__,_|_|_|_.__/|_|
|
|
|
|
Sports Betting Prediction System
|
|
|
|
EOF
|
|
|
|
echo "Welcome to the Calibr Setup Wizard!"
|
|
echo ""
|
|
echo "This wizard will guide you through setting up Calibr on your system."
|
|
echo "You can choose between native Python or Docker deployment, and configure"
|
|
echo "optional features like Authentik SSO and Telegram notifications."
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# Platform Detection
|
|
# ============================================================================
|
|
|
|
detect_platform() {
|
|
print_header "Detecting Platform"
|
|
|
|
DETECTED_OS=$(detect_os)
|
|
DETECTED_PKG_MANAGER=$(detect_package_manager "$DETECTED_OS")
|
|
|
|
local os_name
|
|
os_name=$(get_os_name "$DETECTED_OS")
|
|
|
|
if [[ "$DETECTED_OS" == "unknown" ]]; then
|
|
print_warning "Could not detect operating system"
|
|
print_info "Detected OS type: $OSTYPE"
|
|
echo ""
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
print_error "Cannot proceed in non-interactive mode on unknown OS"
|
|
exit 1
|
|
fi
|
|
if ! confirm "Continue anyway?"; then
|
|
exit 1
|
|
fi
|
|
else
|
|
print_success "Detected: $os_name ($DETECTED_PKG_MANAGER)"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Mode Selection
|
|
# ============================================================================
|
|
|
|
select_deployment_mode() {
|
|
if [[ -n "$MODE" ]]; then
|
|
print_header "Deployment Mode"
|
|
print_info "Using mode: $MODE"
|
|
return
|
|
fi
|
|
|
|
print_header "Deployment Mode"
|
|
echo ""
|
|
echo "How would you like to run Calibr?"
|
|
echo ""
|
|
echo " 1) Native Python"
|
|
echo " - Best for development and testing"
|
|
echo " - Runs directly on your system Python"
|
|
echo " - Uses SQLite database by default"
|
|
echo " - Easier to debug and modify"
|
|
echo ""
|
|
echo " 2) Docker"
|
|
echo " - Best for production deployment"
|
|
echo " - Isolated environment with all dependencies"
|
|
echo " - Includes PostgreSQL, Redis, Celery workers"
|
|
echo " - Optional Authentik SSO integration"
|
|
echo ""
|
|
|
|
local selection
|
|
selection=$(select_option "Select deployment mode:" \
|
|
"Native Python (development)" \
|
|
"Docker (production)")
|
|
|
|
if [[ "$selection" == *"Docker"* ]]; then
|
|
MODE="docker"
|
|
else
|
|
MODE="native"
|
|
fi
|
|
|
|
print_success "Selected: $MODE mode"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Dependency Checking
|
|
# ============================================================================
|
|
|
|
check_and_install_dependencies() {
|
|
if ! check_dependencies "$MODE" "$DETECTED_PKG_MANAGER"; then
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
print_error "Dependency check failed in non-interactive mode"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
if confirm "Would you like to install missing dependencies?"; then
|
|
install_missing_dependencies
|
|
else
|
|
print_error "Cannot proceed without required dependencies"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check port conflicts for Docker mode
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
check_port_conflicts || {
|
|
print_warning "Port conflict check failed, continuing anyway"
|
|
}
|
|
fi
|
|
}
|
|
|
|
check_port_conflicts() {
|
|
local result
|
|
result=$(check_docker_ports "$MODE" "$ENABLE_SSO" 2>&1)
|
|
local port_check_result=$?
|
|
|
|
# If no conflicts, return early
|
|
if [[ $port_check_result -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Check if we got valid output
|
|
if [[ ! "$result" =~ CONFLICTS:.*SUGGESTIONS: ]]; then
|
|
print_warning "Port check returned unexpected output, skipping conflict resolution"
|
|
return 0
|
|
fi
|
|
|
|
# Parse conflicts and suggestions
|
|
local conflicts_part="${result#*CONFLICTS:}"
|
|
conflicts_part="${conflicts_part%%|*}"
|
|
local suggestions_part="${result#*SUGGESTIONS:}"
|
|
suggestions_part="${suggestions_part%%$'\n'*}"
|
|
|
|
# Convert to arrays, handling empty cases
|
|
local conflicts=()
|
|
local suggestions=()
|
|
|
|
if [[ -n "$conflicts_part" ]]; then
|
|
IFS='~' read -ra conflicts <<< "$conflicts_part"
|
|
fi
|
|
|
|
if [[ -n "$suggestions_part" ]]; then
|
|
IFS='~' read -ra suggestions <<< "$suggestions_part"
|
|
fi
|
|
|
|
# Only show if we have actual conflicts
|
|
if [[ ${#conflicts[@]} -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
print_warning "Port conflicts detected!"
|
|
echo ""
|
|
echo "The following ports are already in use:"
|
|
for conflict in "${conflicts[@]}"; do
|
|
echo " - $conflict"
|
|
done
|
|
echo ""
|
|
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
print_error "Port conflicts in non-interactive mode. Please free these ports or use --dry-run to see alternatives."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Suggested alternative port configuration:"
|
|
for suggestion in "${suggestions[@]}"; do
|
|
echo " $suggestion"
|
|
done
|
|
echo ""
|
|
|
|
if confirm "Use alternative ports automatically?"; then
|
|
# Store suggestions for use in generate_env_file
|
|
PORT_OVERRIDES=("${suggestions[@]}")
|
|
print_success "Will use alternative ports"
|
|
else
|
|
print_error "Cannot proceed with port conflicts"
|
|
echo ""
|
|
echo "Please either:"
|
|
echo " 1. Stop services using these ports"
|
|
echo " 2. Run with --dry-run to see alternatives and configure manually"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
install_missing_dependencies() {
|
|
print_header "Installing Dependencies"
|
|
|
|
check_sudo
|
|
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
# Install Docker dependencies
|
|
if ! check_command docker; then
|
|
local docker_pkg
|
|
docker_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "docker")
|
|
if [[ -n "$docker_pkg" ]]; then
|
|
install_package "$DETECTED_PKG_MANAGER" "$docker_pkg"
|
|
|
|
# Start Docker service
|
|
case "$DETECTED_PKG_MANAGER" in
|
|
pacman|dnf)
|
|
print_step "Starting Docker service..."
|
|
sudo systemctl enable --now docker
|
|
;;
|
|
esac
|
|
|
|
# Add user to docker group
|
|
print_step "Adding user to docker group..."
|
|
sudo usermod -aG docker "$USER"
|
|
print_warning "You may need to log out and back in for docker group membership to take effect"
|
|
print_info "Or run: newgrp docker"
|
|
fi
|
|
fi
|
|
|
|
if ! check_docker_compose; then
|
|
local compose_pkg
|
|
compose_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "docker-compose")
|
|
if [[ -n "$compose_pkg" ]]; then
|
|
install_package "$DETECTED_PKG_MANAGER" "$compose_pkg"
|
|
fi
|
|
fi
|
|
|
|
if ! check_docker_buildx; then
|
|
print_warning "Docker Buildx not found. Attempting to install..."
|
|
docker buildx install 2>/dev/null || true
|
|
fi
|
|
else
|
|
# Install Python dependencies
|
|
if ! check_python "3.10"; then
|
|
local python_pkg
|
|
python_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "python3")
|
|
if [[ -n "$python_pkg" ]]; then
|
|
install_package "$DETECTED_PKG_MANAGER" "$python_pkg"
|
|
fi
|
|
fi
|
|
|
|
if ! check_python_venv; then
|
|
local venv_pkg
|
|
venv_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "python3-venv")
|
|
if [[ -n "$venv_pkg" ]]; then
|
|
install_package "$DETECTED_PKG_MANAGER" "$venv_pkg"
|
|
fi
|
|
fi
|
|
|
|
if ! check_pip; then
|
|
local pip_pkg
|
|
pip_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "python3-pip")
|
|
if [[ -n "$pip_pkg" ]]; then
|
|
install_package "$DETECTED_PKG_MANAGER" "$pip_pkg"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Re-check dependencies
|
|
echo ""
|
|
if ! check_dependencies "$MODE" "$DETECTED_PKG_MANAGER"; then
|
|
print_error "Dependency installation failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# SSO Configuration
|
|
# ============================================================================
|
|
|
|
configure_sso_from_scratch() {
|
|
echo ""
|
|
echo "Authentik SSO Setup Options:"
|
|
echo ""
|
|
echo " 1) Bundled Authentik (included with Docker)"
|
|
echo " - Runs on http://localhost:9000"
|
|
echo " - Automatically configured"
|
|
echo " - Requires ~500MB additional disk space"
|
|
echo " - Recommended for new deployments"
|
|
echo ""
|
|
echo " 2) External Authentik (use existing instance)"
|
|
echo " - Connect to your own Authentik server"
|
|
echo " - Requires manual OAuth2 provider setup"
|
|
echo ""
|
|
|
|
local sso_choice
|
|
sso_choice=$(select_option "Select SSO configuration:" \
|
|
"Bundled Authentik (recommended)" \
|
|
"External Authentik")
|
|
|
|
if [[ "$sso_choice" == *"Bundled"* ]]; then
|
|
ENABLE_SSO=true
|
|
USE_BUNDLED_AUTHENTIK=true
|
|
print_success "Will use bundled Authentik server"
|
|
|
|
# Ask about Authentik URL configuration
|
|
echo ""
|
|
local authentik_url_choice
|
|
authentik_url_choice=$(select_option "How will users access Authentik?" \
|
|
"localhost (http://localhost:PORT)" \
|
|
"Custom domain (https://auth.example.com)")
|
|
|
|
if [[ "$authentik_url_choice" == *"localhost"* ]]; then
|
|
AUTHENTIK_BASE_URL="http://localhost:\${AUTHENTIK_PORT:-9000}"
|
|
print_info "Authentik will be available at http://localhost:9000"
|
|
print_info "Initial admin setup: http://localhost:9000/if/flow/initial-setup/"
|
|
else
|
|
echo ""
|
|
local authentik_domain
|
|
while [[ -z "$authentik_domain" ]]; do
|
|
read -r -p "Enter Authentik domain (e.g., auth.example.com): " authentik_domain
|
|
if [[ -z "$authentik_domain" ]]; then
|
|
print_error "Domain cannot be empty"
|
|
fi
|
|
done
|
|
|
|
local authentik_protocol
|
|
if confirm "Use HTTPS? (recommended for production)" "y"; then
|
|
authentik_protocol="https"
|
|
else
|
|
authentik_protocol="http"
|
|
fi
|
|
|
|
AUTHENTIK_BASE_URL="${authentik_protocol}://${authentik_domain}"
|
|
print_success "Authentik will be available at: $AUTHENTIK_BASE_URL"
|
|
print_info "Make sure your reverse proxy forwards to localhost:\${AUTHENTIK_PORT:-9000}"
|
|
fi
|
|
else
|
|
ENABLE_SSO=true
|
|
USE_BUNDLED_AUTHENTIK=false
|
|
echo ""
|
|
echo "External Authentik Configuration"
|
|
echo ""
|
|
while [[ -z "$EXTERNAL_AUTHENTIK_URL" ]]; do
|
|
read -r -p "Enter your Authentik server URL (e.g., https://auth.example.com): " EXTERNAL_AUTHENTIK_URL
|
|
if ! validate_url "$EXTERNAL_AUTHENTIK_URL"; then
|
|
print_error "Invalid URL format. Please try again."
|
|
EXTERNAL_AUTHENTIK_URL=""
|
|
fi
|
|
done
|
|
# Remove trailing slash
|
|
EXTERNAL_AUTHENTIK_URL="${EXTERNAL_AUTHENTIK_URL%/}"
|
|
print_success "Will connect to external Authentik at $EXTERNAL_AUTHENTIK_URL"
|
|
print_warning "You'll need to configure OAuth2 provider in Authentik admin"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Configuration Collection
|
|
# ============================================================================
|
|
|
|
load_existing_env() {
|
|
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
|
print_header "Detecting Existing Configuration"
|
|
parse_env_file "$PROJECT_ROOT/.env"
|
|
print_success "Found existing .env file"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
collect_configuration() {
|
|
print_header "Configuration"
|
|
|
|
# Try to load existing .env
|
|
local has_existing_env=false
|
|
if load_existing_env; then
|
|
has_existing_env=true
|
|
echo ""
|
|
print_info "Found existing configuration. I'll ask about each setting."
|
|
echo ""
|
|
fi
|
|
|
|
# Odds API Key (required)
|
|
if [[ -z "$ODDS_API_KEY" ]]; then
|
|
local existing_key
|
|
existing_key=$(get_env_value "ODDS_API_KEY")
|
|
|
|
if [[ -n "$existing_key" ]] && ! is_placeholder "$existing_key"; then
|
|
# Show masked existing value
|
|
local masked
|
|
masked=$(mask_value "$existing_key")
|
|
echo "The Odds API Key: $masked (existing)"
|
|
if confirm "Keep this API key?" "y"; then
|
|
ODDS_API_KEY="$existing_key"
|
|
print_success "Using existing Odds API key"
|
|
else
|
|
while true; do
|
|
read -r -p "Enter new The Odds API key: " ODDS_API_KEY
|
|
if validate_api_key "$ODDS_API_KEY"; then
|
|
break
|
|
fi
|
|
print_error "Invalid API key format. Please try again."
|
|
done
|
|
fi
|
|
else
|
|
echo ""
|
|
echo "The Odds API is required for fetching sports odds data."
|
|
echo "Get your free API key at: https://the-odds-api.com/"
|
|
echo ""
|
|
|
|
while true; do
|
|
read -r -p "Enter your The Odds API key: " ODDS_API_KEY
|
|
if validate_api_key "$ODDS_API_KEY"; then
|
|
break
|
|
fi
|
|
print_error "Invalid API key format. Please try again."
|
|
done
|
|
fi
|
|
fi
|
|
if [[ -z "$ODDS_API_KEY" ]] || is_placeholder "$ODDS_API_KEY"; then
|
|
print_error "Valid Odds API key is required"
|
|
exit 1
|
|
fi
|
|
print_success "Odds API key configured"
|
|
|
|
# URL Configuration (Docker only)
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
echo ""
|
|
echo "URL Configuration"
|
|
echo ""
|
|
echo "You can configure Calibr to be accessed via:"
|
|
echo " - localhost (development/testing)"
|
|
echo " - Custom domain with reverse proxy (production)"
|
|
echo ""
|
|
|
|
local url_choice
|
|
url_choice=$(select_option "How will users access Calibr?" \
|
|
"localhost (http://localhost:PORT)" \
|
|
"Custom domain (https://calibr.example.com)")
|
|
|
|
if [[ "$url_choice" == *"localhost"* ]]; then
|
|
# Use localhost with port
|
|
CALIBR_BASE_URL="http://localhost:\${WEB_PORT_PROD:-8001}"
|
|
CALIBR_ALLOWED_HOSTS="localhost,127.0.0.1"
|
|
print_success "Using localhost configuration"
|
|
else
|
|
# Custom domain
|
|
echo ""
|
|
local calibr_domain
|
|
while [[ -z "$calibr_domain" ]]; do
|
|
read -r -p "Enter Calibr domain (e.g., calibr.example.com): " calibr_domain
|
|
if [[ -z "$calibr_domain" ]]; then
|
|
print_error "Domain cannot be empty"
|
|
fi
|
|
done
|
|
|
|
local calibr_protocol
|
|
if confirm "Use HTTPS? (recommended for production)" "y"; then
|
|
calibr_protocol="https"
|
|
else
|
|
calibr_protocol="http"
|
|
fi
|
|
|
|
CALIBR_BASE_URL="${calibr_protocol}://${calibr_domain}"
|
|
CALIBR_ALLOWED_HOSTS="${calibr_domain},localhost,127.0.0.1"
|
|
print_success "Using custom domain: $CALIBR_BASE_URL"
|
|
print_info "Make sure your reverse proxy forwards to localhost:\${WEB_PORT_PROD:-8001}"
|
|
fi
|
|
fi
|
|
|
|
# SSO (Docker only)
|
|
if [[ "$MODE" == "docker" ]] && [[ "$ENABLE_SSO" == false ]]; then
|
|
# Check for existing Authentik configuration
|
|
local existing_oidc_endpoint
|
|
existing_oidc_endpoint=$(get_env_value "OIDC_OP_AUTHORIZATION_ENDPOINT")
|
|
|
|
if [[ -n "$existing_oidc_endpoint" ]] && ! is_placeholder "$existing_oidc_endpoint"; then
|
|
echo ""
|
|
echo "Existing Authentik SSO configuration detected:"
|
|
echo " Endpoint: $existing_oidc_endpoint"
|
|
if confirm "Keep existing SSO configuration?" "y"; then
|
|
ENABLE_SSO=true
|
|
print_success "Using existing SSO configuration"
|
|
else
|
|
configure_sso_from_scratch
|
|
fi
|
|
else
|
|
echo ""
|
|
echo "Authentik SSO provides centralized authentication and user management."
|
|
echo "This is optional and can be configured later if needed."
|
|
echo ""
|
|
if confirm "Enable Authentik SSO integration?"; then
|
|
ENABLE_SSO=true
|
|
configure_sso_from_scratch
|
|
else
|
|
print_info "SSO will be disabled (can be enabled later)"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Telegram Bot
|
|
if [[ "$ENABLE_TELEGRAM" == false ]] && [[ "$NON_INTERACTIVE" == false ]]; then
|
|
# Check for existing Telegram config
|
|
local existing_token existing_chat_id
|
|
existing_token=$(get_env_value "TELEGRAM_BOT_TOKEN")
|
|
existing_chat_id=$(get_env_value "TELEGRAM_CHAT_ID")
|
|
|
|
# If we have valid existing values, ask if they want to keep
|
|
if [[ -n "$existing_token" ]] && ! is_placeholder "$existing_token" && \
|
|
[[ -n "$existing_chat_id" ]] && ! is_placeholder "$existing_chat_id"; then
|
|
echo ""
|
|
local masked_token masked_chat
|
|
masked_token=$(mask_value "$existing_token")
|
|
masked_chat=$(mask_value "$existing_chat_id")
|
|
echo "Telegram Bot Token: $masked_token (existing)"
|
|
echo "Telegram Chat ID: $masked_chat (existing)"
|
|
if confirm "Keep existing Telegram configuration?" "y"; then
|
|
ENABLE_TELEGRAM=true
|
|
TELEGRAM_BOT_TOKEN="$existing_token"
|
|
TELEGRAM_CHAT_ID="$existing_chat_id"
|
|
print_success "Using existing Telegram configuration"
|
|
else
|
|
if confirm "Configure new Telegram bot?"; then
|
|
ENABLE_TELEGRAM=true
|
|
while [[ -z "$TELEGRAM_BOT_TOKEN" ]]; do
|
|
echo ""
|
|
echo "Get your bot token from @BotFather on Telegram"
|
|
read -r -p "Enter Telegram bot token: " TELEGRAM_BOT_TOKEN
|
|
done
|
|
while [[ -z "$TELEGRAM_CHAT_ID" ]]; do
|
|
echo ""
|
|
echo "Get your chat ID from @userinfobot on Telegram"
|
|
read -r -p "Enter Telegram chat ID: " TELEGRAM_CHAT_ID
|
|
done
|
|
print_success "Telegram bot configured"
|
|
else
|
|
print_info "Telegram bot will be disabled"
|
|
fi
|
|
fi
|
|
else
|
|
echo ""
|
|
echo "Telegram bot enables real-time notifications for picks and updates."
|
|
echo ""
|
|
if confirm "Enable Telegram bot notifications?"; then
|
|
ENABLE_TELEGRAM=true
|
|
|
|
while [[ -z "$TELEGRAM_BOT_TOKEN" ]]; do
|
|
echo ""
|
|
echo "Get your bot token from @BotFather on Telegram"
|
|
read -r -p "Enter Telegram bot token: " TELEGRAM_BOT_TOKEN
|
|
done
|
|
|
|
while [[ -z "$TELEGRAM_CHAT_ID" ]]; do
|
|
echo ""
|
|
echo "Get your chat ID from @userinfobot on Telegram"
|
|
read -r -p "Enter Telegram chat ID: " TELEGRAM_CHAT_ID
|
|
done
|
|
|
|
print_success "Telegram bot configured"
|
|
else
|
|
print_info "Telegram bot will be disabled (can be enabled later)"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ML Models
|
|
if [[ "$ENABLE_ML" == false ]] && [[ "$NON_INTERACTIVE" == false ]]; then
|
|
echo ""
|
|
echo "ML prediction models (XGBoost, LightGBM) enhance prediction accuracy."
|
|
echo "This increases installation time and requires ~500MB additional disk space."
|
|
echo ""
|
|
if confirm "Enable ML prediction models?"; then
|
|
ENABLE_ML=true
|
|
print_success "ML models will be installed"
|
|
else
|
|
print_info "ML models will be disabled (can be enabled later)"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Environment File Generation
|
|
# ============================================================================
|
|
|
|
generate_env_file() {
|
|
print_header "Generating Configuration"
|
|
|
|
cd "$PROJECT_ROOT" || exit 1
|
|
|
|
# Backup existing .env
|
|
if [[ -f .env ]]; then
|
|
backup_file .env
|
|
fi
|
|
|
|
# Preserve or generate secrets
|
|
local django_secret existing_django_secret
|
|
existing_django_secret=$(get_env_value "DJANGO_SECRET_KEY")
|
|
if [[ -n "$existing_django_secret" ]] && ! is_placeholder "$existing_django_secret"; then
|
|
django_secret="$existing_django_secret"
|
|
print_info "Preserving existing Django secret key"
|
|
else
|
|
django_secret=$(generate_secret 50)
|
|
print_info "Generated new Django secret key"
|
|
fi
|
|
|
|
# Preserve or generate DB password
|
|
local db_password existing_db_password
|
|
existing_db_password=$(get_env_value "DB_PASSWORD")
|
|
if [[ -n "$existing_db_password" ]] && ! is_placeholder "$existing_db_password" && [[ "$MODE" == "docker" ]]; then
|
|
db_password="$existing_db_password"
|
|
print_info "Preserving existing database password"
|
|
else
|
|
db_password=$(generate_secret 32)
|
|
fi
|
|
|
|
# Generate admin password (always new for security)
|
|
local admin_password
|
|
admin_password=$(generate_secret 16)
|
|
|
|
# Preserve Ollama configuration if exists
|
|
local ollama_url ollama_model ollama_timeout
|
|
ollama_url=$(get_env_value "OLLAMA_BASE_URL")
|
|
ollama_model=$(get_env_value "OLLAMA_MODEL")
|
|
ollama_timeout=$(get_env_value "OLLAMA_TIMEOUT")
|
|
|
|
# Preserve Authentik secrets if exist
|
|
local authentik_secret authentik_db_password
|
|
authentik_secret=$(get_env_value "AUTHENTIK_SECRET_KEY")
|
|
authentik_db_password=$(get_env_value "AUTHENTIK_POSTGRES_PASSWORD")
|
|
|
|
# Create .env file
|
|
cat > .env << EOF
|
|
# Calibr Configuration
|
|
# Generated by setup.sh on $(date)
|
|
|
|
# ============================================================================
|
|
# API Keys
|
|
# ============================================================================
|
|
|
|
# The Odds API (required)
|
|
ODDS_API_KEY=$ODDS_API_KEY
|
|
|
|
EOF
|
|
|
|
# Add Telegram config if enabled
|
|
if [[ "$ENABLE_TELEGRAM" == true ]]; then
|
|
cat >> .env << EOF
|
|
# Telegram Bot (optional)
|
|
TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN
|
|
TELEGRAM_CHAT_ID=$TELEGRAM_CHAT_ID
|
|
|
|
EOF
|
|
fi
|
|
|
|
# Add Django config
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# Django Configuration
|
|
# ============================================================================
|
|
|
|
DJANGO_SECRET_KEY=$django_secret
|
|
DJANGO_DEBUG=false
|
|
DJANGO_ALLOWED_HOSTS=${CALIBR_ALLOWED_HOSTS:-localhost,127.0.0.1}
|
|
|
|
EOF
|
|
|
|
# Add Ollama config if exists
|
|
if [[ -n "$ollama_url" ]] && ! is_placeholder "$ollama_url"; then
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# LLM Configuration (Ollama)
|
|
# ============================================================================
|
|
|
|
OLLAMA_BASE_URL=$ollama_url
|
|
EOF
|
|
if [[ -n "$ollama_model" ]] && ! is_placeholder "$ollama_model"; then
|
|
echo "OLLAMA_MODEL=$ollama_model" >> .env
|
|
fi
|
|
if [[ -n "$ollama_timeout" ]] && ! is_placeholder "$ollama_timeout"; then
|
|
echo "OLLAMA_TIMEOUT=$ollama_timeout" >> .env
|
|
fi
|
|
echo "" >> .env
|
|
print_info "Preserved Ollama LLM configuration"
|
|
fi
|
|
|
|
# Add Docker-specific config
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
# Extract port overrides if they exist
|
|
local web_port=8001
|
|
local web_port_dev=8000
|
|
local postgres_port=5433
|
|
local valkey_port=6380
|
|
local authentik_port=9000
|
|
local authentik_https_port=9443
|
|
|
|
for override in "${PORT_OVERRIDES[@]}"; do
|
|
local key="${override%%=*}"
|
|
local value="${override#*=}"
|
|
case "$key" in
|
|
WEB_PORT_PROD) web_port="$value" ;;
|
|
WEB_PORT) web_port_dev="$value" ;;
|
|
POSTGRES_PORT) postgres_port="$value" ;;
|
|
VALKEY_PORT) valkey_port="$value" ;;
|
|
AUTHENTIK_PORT) authentik_port="$value" ;;
|
|
AUTHENTIK_PORT_HTTPS) authentik_https_port="$value" ;;
|
|
esac
|
|
done
|
|
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# Port Configuration
|
|
# ============================================================================
|
|
|
|
# Web application ports (external)
|
|
WEB_PORT=$web_port_dev
|
|
WEB_PORT_PROD=$web_port
|
|
|
|
# Database port (external)
|
|
POSTGRES_PORT=$postgres_port
|
|
|
|
# Cache port (external)
|
|
VALKEY_PORT=$valkey_port
|
|
|
|
# ============================================================================
|
|
# Application URLs
|
|
# ============================================================================
|
|
|
|
# Base URL for Calibr web application
|
|
CALIBR_BASE_URL=${CALIBR_BASE_URL:-http://localhost:$web_port}
|
|
|
|
# Base URL for Authentik server (if using SSO)
|
|
AUTHENTIK_BASE_URL=${AUTHENTIK_BASE_URL:-http://localhost:\${AUTHENTIK_PORT:-9000}}
|
|
|
|
EOF
|
|
|
|
if [[ "$ENABLE_SSO" == true ]]; then
|
|
cat >> .env << EOF
|
|
# Authentik ports (external)
|
|
AUTHENTIK_PORT=$authentik_port
|
|
AUTHENTIK_PORT_HTTPS=$authentik_https_port
|
|
|
|
EOF
|
|
fi
|
|
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# Database (PostgreSQL for Docker)
|
|
# ============================================================================
|
|
|
|
DB_ENGINE=postgres
|
|
DB_NAME=calibr
|
|
DB_USER=calibr
|
|
DB_PASSWORD=$db_password
|
|
DB_HOST=db
|
|
DB_PORT=5432
|
|
|
|
# ============================================================================
|
|
# Cache (Redis/Valkey)
|
|
# ============================================================================
|
|
|
|
REDIS_URL=redis://valkey:6379/0
|
|
CELERY_BROKER_URL=redis://valkey:6379/1
|
|
|
|
EOF
|
|
|
|
# Add SSO config if enabled
|
|
if [[ "$ENABLE_SSO" == true ]]; then
|
|
# Always generate new secrets or preserve existing valid ones
|
|
if [[ -z "$authentik_secret" ]] || is_placeholder "$authentik_secret" || [[ ${#authentik_secret} -lt 20 ]]; then
|
|
authentik_secret=$(generate_secret 60)
|
|
print_info "Generated new Authentik secret key (60 chars)"
|
|
else
|
|
print_info "Preserved existing Authentik secret key"
|
|
fi
|
|
|
|
if [[ -z "$authentik_db_password" ]] || is_placeholder "$authentik_db_password" || [[ ${#authentik_db_password} -lt 16 ]]; then
|
|
authentik_db_password=$(generate_secret 32)
|
|
print_info "Generated new Authentik database password (32 chars)"
|
|
else
|
|
print_info "Preserved existing Authentik database password"
|
|
fi
|
|
|
|
# Ensure secrets are not empty (failsafe)
|
|
if [[ -z "$authentik_secret" ]]; then
|
|
authentik_secret=$(generate_secret 60)
|
|
print_warning "Failsafe: Generated Authentik secret key"
|
|
fi
|
|
if [[ -z "$authentik_db_password" ]]; then
|
|
authentik_db_password=$(generate_secret 32)
|
|
print_warning "Failsafe: Generated Authentik database password"
|
|
fi
|
|
|
|
# Set OIDC endpoints based on configuration
|
|
local auth_base_url
|
|
local auth_type="bundled"
|
|
if [[ -n "$AUTHENTIK_BASE_URL" ]]; then
|
|
# Use the URL configured during setup
|
|
auth_base_url="$AUTHENTIK_BASE_URL"
|
|
if [[ "$USE_BUNDLED_AUTHENTIK" == true ]]; then
|
|
print_info "Configured bundled Authentik endpoint: $auth_base_url"
|
|
else
|
|
auth_type="external"
|
|
print_info "Configured external Authentik endpoint: $auth_base_url"
|
|
fi
|
|
elif [[ -n "$EXTERNAL_AUTHENTIK_URL" ]]; then
|
|
# External Authentik
|
|
auth_type="external"
|
|
auth_base_url="$EXTERNAL_AUTHENTIK_URL"
|
|
print_info "Configured for external Authentik: $auth_base_url"
|
|
else
|
|
# Preserve existing or use default
|
|
local existing_endpoint
|
|
existing_endpoint=$(get_env_value "OIDC_OP_AUTHORIZATION_ENDPOINT")
|
|
if [[ -n "$existing_endpoint" ]] && ! is_placeholder "$existing_endpoint"; then
|
|
# Extract base URL from existing endpoint
|
|
auth_base_url=$(echo "$existing_endpoint" | sed -E 's|(https?://[^/]+).*|\1|')
|
|
print_info "Preserved existing Authentik endpoint ($auth_base_url)"
|
|
else
|
|
auth_base_url="http://localhost:9000"
|
|
print_info "Using default bundled Authentik (localhost:9000)"
|
|
fi
|
|
fi
|
|
|
|
# Write Authentik configuration
|
|
print_info "Writing Authentik secrets to .env (secret: ${#authentik_secret} chars, password: ${#authentik_db_password} chars)"
|
|
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# Authentik SSO
|
|
# ============================================================================
|
|
|
|
# Authentik server secrets (required for SSO)
|
|
AUTHENTIK_SECRET_KEY=$authentik_secret
|
|
AUTHENTIK_POSTGRES_PASSWORD=$authentik_db_password
|
|
|
|
# OAuth2 / OIDC Configuration
|
|
# OIDC will be disabled until you complete Authentik setup
|
|
OIDC_ENABLED=false
|
|
OIDC_RP_CLIENT_ID=calibr
|
|
OIDC_RP_CLIENT_SECRET=change-after-authentik-setup
|
|
|
|
# Authentik endpoints (automatically configured)
|
|
OIDC_OP_AUTHORIZATION_ENDPOINT=$auth_base_url/application/o/authorize/
|
|
OIDC_OP_TOKEN_ENDPOINT=$auth_base_url/application/o/token/
|
|
OIDC_OP_USER_ENDPOINT=$auth_base_url/application/o/userinfo/
|
|
OIDC_OP_JWKS_ENDPOINT=$auth_base_url/application/o/calibr/jwks/
|
|
OIDC_OP_LOGOUT_ENDPOINT=$auth_base_url/application/o/calibr/end-session/
|
|
|
|
EOF
|
|
fi
|
|
else
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# Database (SQLite for Native)
|
|
# ============================================================================
|
|
|
|
DB_ENGINE=sqlite
|
|
DATABASE_PATH=data/db.sqlite3
|
|
|
|
EOF
|
|
fi
|
|
|
|
# Add ML config if enabled
|
|
if [[ "$ENABLE_ML" == true ]]; then
|
|
cat >> .env << EOF
|
|
# ============================================================================
|
|
# Machine Learning Models
|
|
# ============================================================================
|
|
|
|
ENABLE_ML_MODELS=true
|
|
|
|
EOF
|
|
fi
|
|
|
|
chmod 600 .env
|
|
print_success "Created .env file"
|
|
|
|
# Save credentials
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
cat > .admin-credentials << EOF
|
|
Calibr Admin Credentials
|
|
Generated: $(date)
|
|
|
|
Web App: http://localhost:8000
|
|
Admin Username: admin
|
|
Admin Password: $admin_password
|
|
|
|
Database Password: $db_password
|
|
|
|
EOF
|
|
chmod 600 .admin-credentials
|
|
print_success "Saved credentials to .admin-credentials"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Deployment
|
|
# ============================================================================
|
|
|
|
setup_authentik_blueprint() {
|
|
if [[ "$ENABLE_SSO" != true ]] || [[ "$USE_BUNDLED_AUTHENTIK" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
print_header "Configuring Authentik Blueprint"
|
|
|
|
# Create blueprints directory
|
|
mkdir -p "$PROJECT_ROOT/docker/authentik-blueprints"
|
|
|
|
# Copy blueprint file
|
|
if [[ -f "$PROJECT_ROOT/docker/authentik-blueprint-calibr.yaml" ]]; then
|
|
cp "$PROJECT_ROOT/docker/authentik-blueprint-calibr.yaml" \
|
|
"$PROJECT_ROOT/docker/authentik-blueprints/calibr.yaml"
|
|
print_success "Blueprint configured for auto-import"
|
|
print_info "OAuth2 provider will be created automatically on Authentik first start"
|
|
else
|
|
print_warning "Blueprint template not found, skipping auto-configuration"
|
|
fi
|
|
|
|
# Generate docker-compose override for blueprint mounting
|
|
if [[ ! -f "$PROJECT_ROOT/docker-compose.authentik.yml" ]]; then
|
|
cat > "$PROJECT_ROOT/docker-compose.authentik.yml" << 'EOF'
|
|
# Auto-generated by setup.sh - Authentik blueprint auto-import
|
|
# Include with: docker compose -f docker-compose.yml -f docker-compose.authentik.yml up -d
|
|
version: '3.8'
|
|
|
|
services:
|
|
authentik_server:
|
|
volumes:
|
|
- ./docker/authentik-blueprints:/blueprints/custom:ro
|
|
|
|
authentik_worker:
|
|
volumes:
|
|
- ./docker/authentik-blueprints:/blueprints/custom:ro
|
|
EOF
|
|
print_success "Created docker-compose.authentik.yml for blueprint mounting"
|
|
print_info "Blueprints will be auto-imported on Authentik startup"
|
|
fi
|
|
}
|
|
|
|
run_deployment() {
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
print_header "Dry Run - Deployment"
|
|
print_info "Would execute: $MODE deployment"
|
|
return
|
|
fi
|
|
|
|
print_header "Deploying Calibr"
|
|
|
|
cd "$PROJECT_ROOT" || exit 1
|
|
|
|
# Setup Authentik blueprint if SSO enabled
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
setup_authentik_blueprint
|
|
fi
|
|
|
|
if [[ "$MODE" == "native" ]]; then
|
|
print_step "Running native Python installer..."
|
|
"$SCRIPT_DIR/install.sh"
|
|
else
|
|
print_step "Running Docker quick-start..."
|
|
|
|
# Pass --with-sso flag if SSO enabled
|
|
if [[ "$ENABLE_SSO" == true ]]; then
|
|
"$PROJECT_ROOT/docker/quick-start.sh" --with-sso
|
|
else
|
|
"$PROJECT_ROOT/docker/quick-start.sh"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Post-Installation
|
|
# ============================================================================
|
|
|
|
show_post_install_info() {
|
|
print_header "Installation Complete!"
|
|
|
|
echo ""
|
|
echo "Calibr has been successfully installed!"
|
|
echo ""
|
|
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
# Get ports from .env or use defaults
|
|
local web_port
|
|
local authentik_port
|
|
web_port=$(get_env_value "WEB_PORT")
|
|
web_port="${web_port:-8000}"
|
|
authentik_port=$(get_env_value "AUTHENTIK_PORT")
|
|
authentik_port="${authentik_port:-9000}"
|
|
|
|
cat << EOF
|
|
🌐 Web Application: http://localhost:$web_port
|
|
👤 Admin Login: Check .admin-credentials file
|
|
|
|
📋 Next Steps:
|
|
1. Access the web app at http://localhost:8000
|
|
2. Log in with credentials from .admin-credentials
|
|
3. Configure league thresholds in Django admin
|
|
4. View predictions and place bets!
|
|
|
|
🔧 Managing Services:
|
|
Start: docker compose up -d
|
|
Stop: docker compose down
|
|
Logs: docker compose logs -f
|
|
Restart: docker compose restart
|
|
|
|
EOF
|
|
|
|
if [[ "$ENABLE_SSO" == true ]]; then
|
|
if [[ "$USE_BUNDLED_AUTHENTIK" == true ]]; then
|
|
cat << EOF
|
|
🔐 Authentik SSO Setup (Bundled Server - Auto-Configured!)
|
|
|
|
Authentik OAuth2 provider has been pre-configured via blueprint!
|
|
You only need to complete initial setup and get the client secret:
|
|
|
|
Step 1: Create Authentik Admin Account
|
|
URL: http://localhost:$authentik_port/if/flow/initial-setup/
|
|
|
|
Create your Authentik admin account (recommended username: akadmin)
|
|
Note: This is separate from Calibr admin - it manages SSO
|
|
|
|
Step 2: Verify Auto-Configuration
|
|
1. Log into Authentik admin: http://localhost:$authentik_port
|
|
2. Go to Applications → Applications
|
|
3. You should see "Calibr Sports Betting" application (auto-created!)
|
|
4. Go to Applications → Providers
|
|
5. Click on "calibr" provider
|
|
6. Copy the Client Secret (shown once)
|
|
|
|
Step 3: Enable OIDC in Calibr
|
|
1. Edit .env file:
|
|
- Set OIDC_ENABLED=true
|
|
- Set OIDC_RP_CLIENT_SECRET=<paste-secret-from-step-2>
|
|
2. Restart: docker compose restart web
|
|
|
|
Step 4: Test SSO Login
|
|
1. Visit http://localhost:$web_port
|
|
2. Click "Login with SSO"
|
|
3. Authenticate with your Authentik credentials
|
|
4. Grant access to Calibr
|
|
|
|
✨ The OAuth2 provider and application were created automatically!
|
|
You only needed to create the admin account and copy the secret.
|
|
|
|
📚 Full Setup Guide: docs/06-operations/07-authentik-setup.md
|
|
🔧 Blueprint Location: docker/authentik-blueprints/calibr.yaml
|
|
|
|
EOF
|
|
else
|
|
cat << EOF
|
|
🔐 Authentik SSO Setup (External Server)
|
|
|
|
You configured an external Authentik server.
|
|
|
|
Complete the setup:
|
|
1. Create OAuth2 provider in your Authentik admin
|
|
2. Use Client ID: calibr
|
|
3. Add redirect URI: http://localhost:8000/oidc/callback/
|
|
4. Copy the Client Secret
|
|
5. Update .env:
|
|
- Set OIDC_ENABLED=true
|
|
- Set OIDC_RP_CLIENT_SECRET=<your-client-secret>
|
|
6. Restart: docker compose restart web
|
|
|
|
📚 Detailed Guide: docs/06-operations/07-authentik-setup.md
|
|
|
|
EOF
|
|
fi
|
|
fi
|
|
else
|
|
cat << EOF
|
|
🌐 Web Application:
|
|
Start: cd packages/webapp && python manage.py runserver
|
|
Access: http://localhost:8000
|
|
|
|
📋 Next Steps:
|
|
1. Activate the virtual environment: source venv/bin/activate
|
|
2. Start the web app (see command above)
|
|
3. Configure league thresholds in Django admin
|
|
4. View predictions and place bets!
|
|
|
|
🔧 Database:
|
|
Location: data/db.sqlite3
|
|
Backup: cp data/db.sqlite3 data/db.sqlite3.backup
|
|
|
|
EOF
|
|
fi
|
|
|
|
cat << EOF
|
|
📚 Documentation:
|
|
Quick Start: README.md
|
|
Full Docs: docs/README.md
|
|
Web App: docs/03-user-guide/02-web-app.md
|
|
|
|
🐛 Troubleshooting:
|
|
Issues: docs/03-user-guide/04-troubleshooting.md
|
|
Support: https://github.com/noahwoltje/sports_betting_bot/issues
|
|
|
|
EOF
|
|
|
|
if [[ "$NON_INTERACTIVE" == false ]]; then
|
|
echo ""
|
|
if [[ "$MODE" == "docker" ]]; then
|
|
if command -v xdg-open >/dev/null 2>&1; then
|
|
if confirm "Open web app in browser?"; then
|
|
xdg-open http://localhost:8000 2>/dev/null || true
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main Execution
|
|
# ============================================================================
|
|
|
|
main() {
|
|
parse_arguments "$@"
|
|
|
|
show_banner
|
|
|
|
detect_platform
|
|
|
|
select_deployment_mode
|
|
|
|
check_and_install_dependencies
|
|
|
|
collect_configuration
|
|
|
|
generate_env_file
|
|
|
|
run_deployment
|
|
|
|
show_post_install_info
|
|
|
|
echo ""
|
|
print_success "Setup complete!"
|
|
echo ""
|
|
echo "==================================================================="
|
|
echo "Setup completed: $(date)"
|
|
echo "Full log saved to: $LOG_FILE"
|
|
echo "==================================================================="
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|