#!/usr/bin/env bash set -e ############################################################################# # Authelia API Installer # Installation script for Authelia API # # Compatible with: # - Debian/Ubuntu # - RHEL/CentOS/Fedora # - Alpine (with adjustments) # # Usage: # curl -fsSL https://github.com/yourusername/authelia-api/install.sh | sudo bash # # For development/testing: # cd /home/authelia/dev # sudo ./install-authelia-api.sh ############################################################################# # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # Configuration # Update REPO_URL to your actual GitHub repository before distribution REPO_URL="https://git.lohmar.co.uk/cclohmar/autehlia-api" DOWNLOAD_URL="${AUTHELIA_API_DOWNLOAD_URL:-${REPO_URL}/raw/branch/main/authelia-api}" INSTALL_DIR="${AUTHELIA_API_INSTALL_DIR:-/opt/authelia/api}" SERVICE_NAME="authelia-api" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" USER="authelia" # Default Authelia user # For development/testing - use local binary (set AUTHELIA_API_DEVELOPMENT_MODE=true for dev) DEVELOPMENT_MODE="${AUTHELIA_API_DEVELOPMENT_MODE:-false}" LOCAL_BINARY_PATH="/home/authelia/dev/authelia-api" LOCAL_SOURCE_PATH="/home/authelia/dev/src" # Global variables NEED_SUDO=false IS_ROOT=false DISTRO="" DISTRO_VERSION="" ARCH="" GO_VERSION="1.22" ############################################################################# # Utility Functions ############################################################################# print_header() { echo -e "${CYAN}${BOLD}" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ ║" echo "║ Authelia API Installer v1.0.0 ║" echo "║ ║" echo "╚════════════════════════════════════════════════════════════════╝" echo -e "${NC}" } print_success() { echo -e "${GREEN}✓${NC} $1" } print_error() { echo -e "${RED}✗${NC} $1" } print_info() { echo -e "${BLUE}ℹ${NC} $1" } print_warning() { echo -e "${YELLOW}⚠${NC} $1" } print_step() { echo -e "\n${MAGENTA}${BOLD}▶${NC} $1\n" } run_command() { local cmd="$1" local desc="$2" if [ "$NEED_SUDO" = true ] && [ "$IS_ROOT" = false ]; then cmd="sudo $cmd" fi print_info "$desc" eval "$cmd" } ask_confirm() { local prompt="$1" local default="${2:-y}" if [ "$default" = "y" ]; then prompt="$prompt [Y/n]: " else prompt="$prompt [y/N]: " fi read -r -p "$prompt" response response=${response:-$default} if [[ $response =~ ^[Yy]$ ]]; then return 0 else return 1 fi } ############################################################################# # System Detection ############################################################################# detect_system() { print_step "Detecting system..." # Check if running as root if [ "$EUID" -eq 0 ]; then IS_ROOT=true print_info "Running as root" else IS_ROOT=false # Check if sudo is available if command -v sudo &> /dev/null; then NEED_SUDO=true print_info "Running as non-root user, will use sudo when needed" else print_error "Not running as root and sudo is not available" print_error "Please run as root or install sudo" exit 1 fi fi # Detect distribution if [ -f /etc/os-release ]; then . /etc/os-release DISTRO="$ID" DISTRO_VERSION="$VERSION_ID" print_success "Detected: $NAME $VERSION" elif [ -f /etc/debian_version ]; then DISTRO="debian" DISTRO_VERSION=$(cat /etc/debian_version) print_success "Detected: Debian $DISTRO_VERSION" elif [ -f /etc/redhat-release ]; then DISTRO="rhel" DISTRO_VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) print_success "Detected: RHEL/CentOS $DISTRO_VERSION" else print_warning "Could not detect distribution, assuming generic Linux" DISTRO="linux" fi # Detect architecture ARCH=$(uname -m) case "$ARCH" in x86_64|amd64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; armv7l|armhf) ARCH="armv7" ;; *) print_error "Unsupported architecture: $ARCH" exit 1 ;; esac print_success "Architecture: $ARCH" } check_dependencies() { print_step "Checking dependencies..." local missing_deps=() # Check for curl (needed for binary download) if ! command -v curl &> /dev/null; then missing_deps+=("curl") fi # Check for authelia binary if ! command -v authelia &> /dev/null && [ ! -f /opt/authelia/authelia ]; then print_warning "Authelia binary not found in PATH or /opt/authelia/authelia" print_warning "The authelia-api requires Authelia to be installed" if ! ask_confirm "Continue anyway?" "n"; then exit 1 fi fi if [ ${#missing_deps[@]} -ne 0 ]; then print_error "Missing dependencies: ${missing_deps[*]}" if [ "$DISTRO" = "debian" ] || [ "$DISTRO" = "ubuntu" ]; then run_command "apt-get update" "Updating package list" run_command "apt-get install -y ${missing_deps[*]}" "Installing missing dependencies" elif [ "$DISTRO" = "rhel" ] || [ "$DISTRO" = "centos" ] || [ "$DISTRO" = "fedora" ]; then run_command "yum install -y ${missing_deps[*]}" "Installing missing dependencies" elif [ "$DISTRO" = "alpine" ]; then run_command "apk add ${missing_deps[*]}" "Installing missing dependencies" else print_error "Please install manually: ${missing_deps[*]}" exit 1 fi fi print_success "All dependencies satisfied" } ############################################################################# # Installation Functions ############################################################################# create_user() { print_step "Checking service user..." # Detect Authelia service user local authelia_service_file="/etc/systemd/system/authelia.service" if [ -f "$authelia_service_file" ]; then # Extract User from service file local service_user=$(grep -E "^User=" "$authelia_service_file" | cut -d'=' -f2) if [ -n "$service_user" ]; then USER="$service_user" print_success "Using Authelia service user: $USER" return 0 fi fi # Default to authelia user print_info "Authelia service user not found, using default: $USER" # Check if user exists if id "$USER" &>/dev/null; then print_success "User '$USER' already exists" else print_info "Creating user '$USER'" if [ "$DISTRO" = "alpine" ]; then run_command "adduser -D -s /bin/false $USER" "Creating system user" else run_command "useradd -r -s /bin/false -M $USER" "Creating system user" fi print_success "User '$USER' created" fi } prepare_installation_directory() { print_step "Preparing installation directory..." # Create installation directory if [ ! -d "$INSTALL_DIR" ]; then run_command "mkdir -p $INSTALL_DIR" "Creating installation directory" else print_info "Installation directory already exists: $INSTALL_DIR" fi # Set permissions run_command "chown -R $USER:$USER $INSTALL_DIR" "Setting ownership" run_command "chmod 755 $INSTALL_DIR" "Setting directory permissions" print_success "Installation directory ready: $INSTALL_DIR" } download_binary() { print_step "Downloading Authelia API binary..." local binary_dest="$INSTALL_DIR/authelia-api" if [ "$DEVELOPMENT_MODE" = true ]; then if [ -f "$LOCAL_BINARY_PATH" ]; then print_info "Development mode: Using local binary" # Only copy if source and destination are different if [ "$LOCAL_BINARY_PATH" != "$binary_dest" ]; then cp "$LOCAL_BINARY_PATH" "$binary_dest" else print_info "Binary already in correct location" fi else print_warning "Development mode enabled but local binary not found: $LOCAL_BINARY_PATH" print_info "Falling back to download" # Continue to download logic DEVELOPMENT_MODE=false fi fi if [ "$DEVELOPMENT_MODE" = false ]; then # Construct download URL with architecture local download_url="${DOWNLOAD_URL}" print_info "Downloading from: $download_url" if command -v curl &> /dev/null; then run_command "curl -fsSL -o '$binary_dest' '$download_url'" "Downloading binary" else print_error "curl not available for download" exit 1 fi fi # Make binary executable run_command "chmod +x '$binary_dest'" "Making binary executable" run_command "chown $USER:$USER '$binary_dest'" "Setting binary ownership" # Verify binary if [ -f "$binary_dest" ] && [ -x "$binary_dest" ]; then print_success "Binary downloaded and ready: $binary_dest" # Test binary version if "$binary_dest" --version &>/dev/null; then local version=$("$binary_dest" --version 2>/dev/null || echo "unknown") print_success "Binary version: $version" fi else print_error "Binary download or verification failed" exit 1 fi } download_source_files() { print_step "Downloading source files and documentation..." local source_dir="$INSTALL_DIR/src" # Create source directory if [ ! -d "$source_dir" ]; then run_command "mkdir -p $source_dir" "Creating source directory" fi # For development, copy existing source files if [ "$DEVELOPMENT_MODE" = true ]; then print_info "Development mode: Using existing source files" # If source directory already exists in install location, skip copying if [ -d "$INSTALL_DIR/src" ] && [ "$INSTALL_DIR/src" != "$source_dir" ]; then print_info "Source directory already exists at installation location" elif [ -d "$LOCAL_SOURCE_PATH" ] && [ "$LOCAL_SOURCE_PATH" != "$source_dir" ]; then print_info "Copying source files from development location" cp -r "$LOCAL_SOURCE_PATH"/* "$source_dir/" 2>/dev/null || true fi # Also copy root README if it exists (from dev directory) if [ -f "/home/authelia/dev/README.md" ] && [ "/home/authelia/dev/README.md" != "$source_dir/ROOT_README.md" ]; then cp "/home/authelia/dev/README.md" "$source_dir/ROOT_README.md" fi else # In production, download source archive local source_url="${REPO_URL}/archive/refs/heads/main.tar.gz" local temp_file="/tmp/authelia-api-src.tar.gz" print_info "Downloading source files from GitHub" if command -v curl &> /dev/null; then run_command "curl -fsSL -o '$temp_file' '$source_url'" "Downloading source archive" run_command "tar -xzf '$temp_file' -C '$source_dir' --strip-components=1" "Extracting source files" run_command "rm -f '$temp_file'" "Cleaning up temp file" else print_warning "curl not available, skipping source download" fi fi # Set permissions on source directory run_command "chown -R $USER:$USER '$source_dir'" "Setting source directory ownership" run_command "chmod -R 644 '$source_dir'" "Setting source file permissions" print_success "Source files downloaded to: $source_dir" } create_configuration() { print_step "Creating configuration..." local config_file="$INSTALL_DIR/config.yml" # Check if configuration already exists if [ -f "$config_file" ]; then print_info "Configuration already exists: $config_file" if ask_confirm "Overwrite existing configuration?" "n"; then print_info "Backing up existing configuration" run_command "cp '$config_file' '${config_file}.backup.$(date +%Y%m%d_%H%M%S)'" "Backing up config" else print_info "Skipping configuration creation" return 0 fi fi # Create basic configuration cat > /tmp/authelia-api-config.yml << EOF # Authelia API Configuration # This file can be used to override default settings # Database settings database_path: "$INSTALL_DIR/authelia-api.db" # API server settings listen_addr: "127.0.0.1:8080" log_level: "info" # Authelia integration (will auto-discover from Authelia config) # authelia_config_path: "/opt/authelia/configuration.yml" # authelia_binary_path: "/opt/authelia/authelia" # Uncomment to override environment variables # AUTHELIA_API_DB_PATH: "$INSTALL_DIR/authelia-api.db" # AUTHELIA_API_LISTEN_ADDR: "127.0.0.1:8080" # AUTHELIA_API_LOG_LEVEL: "info" EOF run_command "mv /tmp/authelia-api-config.yml '$config_file'" "Creating configuration file" run_command "chown $USER:$USER '$config_file'" "Setting configuration ownership" run_command "chmod 600 '$config_file'" "Securing configuration" print_success "Configuration created: $config_file" } create_systemd_service() { print_step "Creating systemd service..." # Skip service creation for non-standard install directories if [ "$INSTALL_DIR" != "/opt/authelia/api" ]; then print_info "Skipping systemd service creation (non-standard install directory)" return 0 fi # Check if service already exists if [ -f "$SERVICE_FILE" ]; then print_info "Service file already exists: $SERVICE_FILE" if ask_confirm "Overwrite existing service file?" "n"; then print_info "Stopping existing service" run_command "systemctl stop $SERVICE_NAME 2>/dev/null || true" "Stopping service" run_command "systemctl disable $SERVICE_NAME 2>/dev/null || true" "Disabling service" else print_info "Skipping service creation" return 0 fi fi # Create service file local service_content="[Unit] Description=Authelia API Documentation=https://github.com/yourusername/authelia-api After=network.target authelia.service Requires=authelia.service Wants=network-online.target [Service] Type=simple User=$USER Group=$USER WorkingDirectory=$INSTALL_DIR Environment=\"AUTHELIA_API_DB_PATH=$INSTALL_DIR/authelia-api.db\" Environment=\"AUTHELIA_API_LISTEN_ADDR=127.0.0.1:8080\" Environment=\"AUTHELIA_API_LOG_LEVEL=info\" ExecStart=$INSTALL_DIR/authelia-api --config /opt/authelia/configuration.yml ExecReload=/bin/kill -HUP \$MAINPID Restart=on-failure RestartSec=5 TimeoutStopSec=30 StandardOutput=journal StandardError=journal SyslogIdentifier=authelia-api" # Add security hardening only if not running as root if [ "$USER" != "root" ]; then service_content="$service_content # Security hardening NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=$INSTALL_DIR /opt/authelia" fi # Add install section service_content="$service_content [Install] WantedBy=multi-user.target" # Write service file echo "$service_content" > /tmp/authelia-api.service run_command "mv /tmp/authelia-api.service '$SERVICE_FILE'" "Creating service file" run_command "chmod 644 '$SERVICE_FILE'" "Setting service file permissions" # Reload systemd run_command "systemctl daemon-reload" "Reloading systemd" print_success "Systemd service created: $SERVICE_FILE" } setup_database() { print_step "Setting up database..." local db_file="$INSTALL_DIR/authelia-api.db" # Check if database already exists if [ -f "$db_file" ]; then print_info "Database already exists: $db_file" if ask_confirm "Initialize new database (old data will be lost)?" "n"; then print_info "Backing up existing database" run_command "cp '$db_file' '${db_file}.backup.$(date +%Y%m%d_%H%M%S)'" "Backing up database" run_command "rm -f '$db_file'" "Removing old database" else print_info "Skipping database setup" return 0 fi fi # Create empty database file run_command "touch '$db_file'" "Creating database file" run_command "chown $USER:$USER '$db_file'" "Setting database ownership" run_command "chmod 600 '$db_file'" "Securing database" print_success "Database file created: $db_file" } run_bootstrap() { print_step "Running bootstrap..." local binary_path="$INSTALL_DIR/authelia-api" if [ ! -f "$binary_path" ]; then print_error "Binary not found: $binary_path" return 1 fi print_info "Running bootstrap process (first-time setup)" # Run bootstrap with proper user if [ "$IS_ROOT" = true ]; then run_command "sudo -u $USER $binary_path --bootstrap" "Running bootstrap" else run_command "$binary_path --bootstrap" "Running bootstrap" fi if [ $? -eq 0 ]; then print_success "Bootstrap completed successfully" else print_warning "Bootstrap encountered issues (this might be expected if already bootstrapped)" fi } enable_and_start_service() { print_step "Enabling and starting service..." # Skip service operations for non-standard install directories if [ "$INSTALL_DIR" != "/opt/authelia/api" ]; then print_info "Skipping service operations (non-standard install directory)" return 0 fi # Enable service run_command "systemctl enable $SERVICE_NAME" "Enabling service" # Start service run_command "systemctl start $SERVICE_NAME" "Starting service" # Check service status sleep 2 if systemctl is-active --quiet "$SERVICE_NAME"; then print_success "Service is running" # Show status run_command "systemctl status $SERVICE_NAME --no-pager" "Service status" else print_error "Service failed to start" run_command "journalctl -u $SERVICE_NAME -n 20 --no-pager" "Checking service logs" return 1 fi } setup_firewall() { print_step "Configuring firewall (if applicable)..." # Skip firewall configuration for non-standard install directories if [ "$INSTALL_DIR" != "/opt/authelia/api" ]; then print_info "Skipping firewall configuration (non-standard install directory)" return 0 fi local port="8080" # Check if firewall-cmd is available (firewalld) if command -v firewall-cmd &> /dev/null; then if firewall-cmd --state &>/dev/null; then print_info "Configuring firewalld" # Add the service/port if ! firewall-cmd --query-port="${port}/tcp" &>/dev/null; then run_command "firewall-cmd --permanent --add-port=${port}/tcp" "Adding firewall rule" run_command "firewall-cmd --reload" "Reloading firewall" print_success "Firewall rule added for port $port" else print_info "Firewall rule already exists for port $port" fi fi # Check for ufw elif command -v ufw &> /dev/null; then if ufw status | grep -q "Status: active"; then print_info "Configuring UFW" if ! ufw status | grep -q "${port}/tcp"; then run_command "ufw allow ${port}/tcp comment 'Authelia API'" "Adding firewall rule" print_success "Firewall rule added for port $port" else print_info "Firewall rule already exists for port $port" fi fi # Check for iptables (direct) elif command -v iptables &> /dev/null; then print_info "Note: Using iptables directly requires manual configuration" print_info "If using iptables, you may need to add:" print_info " iptables -A INPUT -p tcp --dport $port -j ACCEPT" else print_info "No firewall management tool detected" fi } create_backup_script() { print_step "Creating backup script..." local backup_script="$INSTALL_DIR/backup.sh" cat > "$backup_script" << 'EOF' #!/usr/bin/env bash set -e # Authelia API Backup Script # Backups database and configuration BACKUP_DIR="/opt/authelia/backups" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/authelia-api_backup_$TIMESTAMP.tar.gz" # Create backup directory if it doesn't exist mkdir -p "$BACKUP_DIR" # Stop authelia-api service to ensure consistent backup echo "Stopping authelia-api service..." systemctl stop authelia-api # Create backup echo "Creating backup..." tar -czf "$BACKUP_FILE" \ /opt/authelia/api/authelia-api.db \ /opt/authelia/api/config.yml \ /opt/authelia/api/src/ 2>/dev/null || true # Restart authelia-api service echo "Starting authelia-api service..." systemctl start authelia-api echo "Backup created: $BACKUP_FILE" echo "Size: $(du -h "$BACKUP_FILE" | cut -f1)" EOF # Copy backup script from source if it exists if [ -f "$LOCAL_SOURCE_PATH/backup.sh" ]; then cp "$LOCAL_SOURCE_PATH/backup.sh" "$backup_script" run_command "chmod +x '$backup_script'" "Making backup script executable" run_command "chown $USER:$USER '$backup_script'" "Setting backup script ownership" print_success "Backup script created: $backup_script" else run_command "chmod +x '$backup_script'" "Making backup script executable" run_command "chown $USER:$USER '$backup_script'" "Setting backup script ownership" print_success "Backup script created: $backup_script" fi } ############################################################################# # Main Installation Flow ############################################################################# main_installation() { print_header echo -e "${BOLD}Authelia API Installation${NC}" echo -e "This will install the Authelia API to: ${CYAN}$INSTALL_DIR${NC}" echo "" # Show what will be installed echo -e "${BOLD}Components to install:${NC}" echo " • Authelia API binary" echo " • Source code and documentation" echo " • Configuration files" echo " • Systemd service" echo " • Backup script" echo "" if [ "$IS_ROOT" = false ] && [ "$NEED_SUDO" = true ]; then echo -e "${YELLOW}Note:${NC} Some operations will require sudo privileges" echo "" fi if ! ask_confirm "Proceed with installation?" "y"; then print_info "Installation cancelled" exit 0 fi # Run installation steps detect_system check_dependencies create_user prepare_installation_directory download_binary download_source_files create_configuration setup_database create_systemd_service run_bootstrap enable_and_start_service setup_firewall create_backup_script print_step "Installation Complete!" echo -e "${GREEN}${BOLD}✓ Authelia API has been successfully installed${NC}" echo "" echo -e "${BOLD}Service Information:${NC}" echo " Service: $SERVICE_NAME" echo " Status: $(systemctl is-active $SERVICE_NAME 2>/dev/null || echo 'not installed')" echo " Logs: journalctl -u $SERVICE_NAME" echo "" echo -e "${BOLD}Files and Directories:${NC}" echo " Binary: $INSTALL_DIR/authelia-api" echo " Database: $INSTALL_DIR/authelia-api.db" echo " Config: $INSTALL_DIR/config.yml" echo " Source: $INSTALL_DIR/src/" echo " Backup: $INSTALL_DIR/backup.sh" echo "" echo -e "${BOLD}API Access:${NC}" echo " URL: http://127.0.0.1:8080" echo " Health: http://127.0.0.1:8080/api/health" echo "" echo -e "${BOLD}Next Steps:${NC}" echo " 1. Review configuration: $INSTALL_DIR/config.yml" echo " 2. Test API with: curl http://127.0.0.1:8080/api/health" echo " 3. Check logs: journalctl -u $SERVICE_NAME -f" echo " 4. Add API admins using the API endpoints" echo "" echo -e "${BOLD}Documentation:${NC} $REPO_URL" echo "" # Test API health endpoint print_info "Testing API health endpoint..." sleep 3 if command -v curl &> /dev/null; then if curl -fs http://127.0.0.1:8080/api/health &>/dev/null; then print_success "API is responding correctly" else print_warning "API health check failed (service might still be starting)" print_info "Check logs with: journalctl -u $SERVICE_NAME -f" fi fi } ############################################################################# # Uninstallation Functions ############################################################################# uninstall() { print_header echo -e "${RED}${BOLD}⚠ Uninstall Authelia API${NC}" echo "" echo -e "This will:" echo " • Stop and disable the service" echo " • Remove systemd service file" echo " • Remove installation directory: ${CYAN}$INSTALL_DIR${NC}" echo " • Remove backup scripts" echo "" echo -e "${YELLOW}Warning:${NC} This will delete all authelia-api data including the database!" echo "" if ! ask_confirm "Are you sure you want to uninstall?" "n"; then print_info "Uninstall cancelled" exit 0 fi print_step "Starting uninstallation..." # Stop and disable service if [ -f "$SERVICE_FILE" ]; then run_command "systemctl stop $SERVICE_NAME 2>/dev/null || true" "Stopping service" run_command "systemctl disable $SERVICE_NAME 2>/dev/null || true" "Disabling service" run_command "rm -f '$SERVICE_FILE'" "Removing service file" run_command "systemctl daemon-reload" "Reloading systemd" fi # Remove installation directory if [ -d "$INSTALL_DIR" ]; then run_command "rm -rf '$INSTALL_DIR'" "Removing installation directory" fi # Remove backup script if [ -f "/usr/local/bin/authelia-api-backup" ]; then run_command "rm -f /usr/local/bin/authelia-api-backup" "Removing backup script" fi print_success "Uninstallation complete!" echo "" echo -e "${BOLD}Note:${NC} Authelia configuration and user database were not modified" echo " Backup files in /opt/authelia/backups/ were not removed" } ############################################################################# # Main Script Entry Point ############################################################################# # Parse command line arguments case "${1:-}" in --help|-h|help) print_header echo "Usage: $0 [OPTION]" echo "" echo "Options:" echo " install Install Authelia API (default)" echo " uninstall Remove Authelia API" echo " --help, -h Show this help message" echo " --version, -v Show version" echo "" echo "Examples:" echo " $0 # Interactive installation" echo " $0 install # Explicit installation" echo " $0 uninstall # Remove installation" echo "" echo "Install via curl:" echo " curl -fsSL $REPO_URL/install.sh | sudo bash" echo "" exit 0 ;; --version|-v) echo "Authelia API Installer v1.0.0" exit 0 ;; uninstall|--uninstall) uninstall exit 0 ;; install|--install|"") main_installation exit 0 ;; *) print_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac