commit f72f2ac4e25db479af0ffd54fedc56bb301bd3ca Author: cclohmar Date: Mon May 4 17:50:26 2026 +0100 chore: sync: ClausL-MacBook-Pro.local 2026-05-04 17:50 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..baa01c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Binaries +src/server + +# Dependency directories +vendor/ +node_modules/ + +# Go build artifacts +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary +*.test + +# Output of go build +bin/ +pkg/ + +# Coverage files +*.out +coverage.html + +# Environment files +.env +.env.local +.env.*.local + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Local databases +*.db +*.sqlite +*.sqlite3 +authelia-api.db + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ + +# Backup files +*.bak +*.backup +*~ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2bb8ea --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# Authelia API + +A Go-based REST API and management layer that sits alongside an Authelia LXC on Proxmox. Provides a "Source of Truth" in SQLite, handles bulk user onboarding via JSON, and automates synchronization of the Authelia `users_database.yml` file. + +## Features + +- **Sovereign Bootstrap**: Automatically imports existing Authelia users on first run +- **Bulk User Management**: Create multiple users via JSON API with automatic password generation +- **Real-time Sync**: SQLite changes automatically sync to Authelia's YAML configuration +- **SMTP Onboarding**: Send welcome emails using Authelia's SMTP configuration +- **Secure API**: Bearer token authentication with bcrypt hashing +- **Drop-in Deployment**: Runs alongside existing Authelia installation + +## Installation + +### Quick Installation (Recommended) + +Download and execute the installation script: + +```bash +curl -fsSL https://github.com/yourusername/authelia-api/raw/main/install-authelia-api.sh | sudo bash +``` + +### Manual Installation + +1. **Download the binary**: + ```bash + # Download the latest authelia-api binary + curl -fsSL -o authelia-api https://github.com/yourusername/authelia-api/releases/latest/download/authelia-api + chmod +x authelia-api + ``` + +2. **Run the installer**: + ```bash + sudo ./install-authelia-api.sh + ``` + +### Development Installation + +For building from source, see the [src/README.md](src/README.md) file. + +**Note for local development**: When installing from a cloned repository, set the environment variable to use the local binary: + +```bash +AUTHELIA_API_DEVELOPMENT_MODE=true sudo ./install-authelia-api.sh +``` + +## Quick Start + +1. **Install using the script above** +2. **Get your bearer token** (from Authelia configuration): + ```bash + grep -A2 "session:" /opt/authelia/configuration.yml | grep "secret:" | awk '{print $2}' + ``` +3. **Test the API**: + ```bash + curl -H "Authorization: Bearer YOUR_TOKEN_HERE" http://127.0.0.1:8080/api/health + ``` + +## API Testing with Postman + +Postman collection and environment files are provided for API testing: + +- `authelia-api.postman_collection.json` - Complete API collection +- `authelia-api.postman_environment.json` - Environment variables +- `POSTMAN_GUIDE.md` - Setup and usage guide + +See [POSTMAN_GUIDE.md](POSTMAN_GUIDE.md) for detailed instructions. + +## Files in This Repository + +- `authelia-api` - Ready-to-use binary (production) +- `install-authelia-api.sh` - Installation script +- `POSTMAN_GUIDE.md` - API testing guide +- `src/` - Source code and build instructions +- `authelia-api.postman_collection.json` - Postman collection +- `authelia-api.postman_environment.json` - Postman environment + +## Production Deployment + +The repository provides a ready-to-deploy binary. The installation script handles: +- Systemd service creation +- Database setup +- Configuration generation +- User creation +- Firewall configuration (if applicable) + +## License + +MIT License \ No newline at end of file diff --git a/authelia-api b/authelia-api new file mode 100644 index 0000000..c23bfd7 Binary files /dev/null and b/authelia-api differ diff --git a/authelia-api.postman_collection.json b/authelia-api.postman_collection.json new file mode 100644 index 0000000..e6ba6c0 --- /dev/null +++ b/authelia-api.postman_collection.json @@ -0,0 +1,161 @@ +{ + "info": { + "name": "Authelia API API", + "description": "Postman collection for Authelia API REST API\n\nThis API provides a \"Source of Truth\" for Authelia user management with SQLite backend, bulk user onboarding, and automatic synchronization with Authelia's `users_database.yml` file.\n\n## Authentication\n- Use Bearer token authentication\n- Initial token: `session.secret` from Authelia configuration.yml\n- Header: `Authorization: Bearer `\n\n## Base URL\n- Default: `http://127.0.0.1:8080`\n- Can be changed via `--listen` flag\n\n## Quick Start\n1. Install Authelia API using `install.sh`\n2. Start the service: `systemctl start authelia-api`\n3. Get your session.secret from `/opt/authelia/configuration.yml`\n4. Use this collection with the token as environment variable\n\n## Notes\n- All user passwords are auto-generated as secure placeholders\n- Changes trigger automatic sync to Authelia YAML file\n- SMTP onboarding emails available if configured", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Health Check", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/health", + "host": ["{{base_url}}"], + "path": ["api", "health"] + }, + "description": "Health check endpoint to verify API is running. No authentication required." + }, + "response": [] + }, + { + "name": "Bulk Create Users", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{bearer_token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"users\": [\n {\n \"username\": \"john.doe\",\n \"display_name\": \"John Doe\",\n \"email\": \"john.doe@example.com\",\n \"groups\": [\"users\", \"admins\"]\n },\n {\n \"username\": \"jane.smith\",\n \"display_name\": \"Jane Smith\",\n \"email\": \"jane.smith@example.com\",\n \"groups\": [\"users\"]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/users/bulk", + "host": ["{{base_url}}"], + "path": ["api", "users", "bulk"] + }, + "description": "Create multiple users in a single request. Passwords are automatically generated and returned in the response.\n\n**Limits:**\n- Max 1000 users per request\n- Username, display_name, and email are required\n- Email must contain '@' symbol\n\n**Response includes:**\n- `success`: boolean indicating if any users were created\n- `created`: count of successfully created users\n- `users`: array with status and placeholder_password for each user" + }, + "response": [] + }, + { + "name": "List Users", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{bearer_token}}" + } + ], + "url": { + "raw": "{{base_url}}/api/users", + "host": ["{{base_url}}"], + "path": ["api", "users"] + }, + "description": "List all users in the system with pagination support.\n\n**Query Parameters:**\n- `page`: page number (default: 1)\n- `page_size`: items per page (default: 50)\n\n**Note:** Pagination is implemented but currently returns all users. Future versions will support proper pagination." + }, + "response": [] + }, + { + "name": "Delete User", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{bearer_token}}" + } + ], + "url": { + "raw": "{{base_url}}/api/users/:username", + "host": ["{{base_url}}"], + "path": ["api", "users", ":username"], + "variable": [ + { + "key": ":username", + "value": "john.doe", + "description": "Username to delete" + } + ] + }, + "description": "Delete a user by username. Triggers automatic sync to update Authelia's YAML file.\n\n**Returns:**\n- `success`: boolean\n- `message`: confirmation message" + }, + "response": [] + } + ], + "variable": [ + { + "key": "base_url", + "value": "http://127.0.0.1:8080", + "type": "string", + "description": "Base URL for API requests" + }, + { + "key": "bearer_token", + "value": "YOUR_SESSION_SECRET_HERE", + "type": "string", + "description": "Bearer token (session.secret from Authelia config)" + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "// Pre-request script can be used to set dynamic variables", + "console.log('Request:', pm.request);" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "// Test script to validate responses", + "pm.test('Status code is 200 or 201', function () {", + " pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]);", + "});", + "", + "// For health check, ensure status is ok", + "if (pm.request.url.toString().includes('/health')) {", + " pm.test('Health check returns ok', function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.status).to.equal('ok');", + " });", + "}", + "", + "// For bulk create, check response structure", + "if (pm.request.url.toString().includes('/bulk')) {", + " pm.test('Bulk create returns success field', function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('success');", + " pm.expect(jsonData).to.have.property('created');", + " pm.expect(jsonData).to.have.property('users');", + " });", + "}" + ] + } + } + ] +} \ No newline at end of file diff --git a/authelia-api.postman_environment.json b/authelia-api.postman_environment.json new file mode 100644 index 0000000..a03ff78 --- /dev/null +++ b/authelia-api.postman_environment.json @@ -0,0 +1,23 @@ +{ + "id": "authelia-api-environment", + "name": "Authelia API", + "values": [ + { + "key": "base_url", + "value": "http://127.0.0.1:8080", + "type": "default", + "enabled": true, + "description": "Base URL for API requests" + }, + { + "key": "bearer_token", + "value": "", + "type": "secret", + "enabled": true, + "description": "Bearer token (session.secret from Authelia config)" + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-04-02T00:00:00.000Z", + "_postman_exported_using": "Postman/11.0" +} \ No newline at end of file diff --git a/example_bulk_request.json b/example_bulk_request.json new file mode 100644 index 0000000..5c52309 --- /dev/null +++ b/example_bulk_request.json @@ -0,0 +1,22 @@ +{ + "users": [ + { + "username": "john.doe", + "display_name": "John Doe", + "email": "john.doe@example.com", + "groups": ["users", "admins"] + }, + { + "username": "jane.smith", + "display_name": "Jane Smith", + "email": "jane.smith@example.com", + "groups": ["users"] + }, + { + "username": "bob.johnson", + "display_name": "Bob Johnson", + "email": "bob.johnson@example.com", + "groups": ["users", "developers"] + } + ] +} \ No newline at end of file diff --git a/install-authelia-api.sh b/install-authelia-api.sh new file mode 100644 index 0000000..be2aa39 --- /dev/null +++ b/install-authelia-api.sh @@ -0,0 +1,858 @@ +#!/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://github.com/yourusername/authelia-api" +DOWNLOAD_URL="${AUTHELIA_API_DOWNLOAD_URL:-${REPO_URL}/releases/latest/download/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}_linux_${ARCH}" + + 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 \ No newline at end of file