From 58cc1b1e921421ad32807c09478930b16a66b721 Mon Sep 17 00:00:00 2001 From: elfateh4 Date: Tue, 2 Dec 2025 03:16:38 +0100 Subject: [PATCH] Remove deprecated deploy workflow, update README with new features and service URLs, delete unused alertmanager and prometheus configurations, refactor Authelia configuration for improved security and functionality, add Gitea and Beszel services, and enhance health checks across services. --- .env.example | 43 ++-- .github/workflows/deploy.yml | 60 ----- README.md | 61 +++-- alertmanager.yml | 17 -- authelia/configuration.template.yml | 60 +++++ authelia/configuration.yml | 36 ++- authelia/users_database.yml | 15 +- beszel_agent_data/fingerprint | 1 + docker-compose.dev.yml | 337 ---------------------------- docker-compose.yml | 188 ++++++++++------ prometheus.yml | 43 ---- rules.yml | 36 --- 12 files changed, 279 insertions(+), 618 deletions(-) delete mode 100644 .github/workflows/deploy.yml delete mode 100644 alertmanager.yml create mode 100644 authelia/configuration.template.yml create mode 100644 beszel_agent_data/fingerprint delete mode 100644 docker-compose.dev.yml delete mode 100644 prometheus.yml delete mode 100644 rules.yml diff --git a/.env.example b/.env.example index 67db7d5..e4358ef 100644 --- a/.env.example +++ b/.env.example @@ -1,20 +1,13 @@ -# .env.example -# Copy this file to .env and update the values below - ## Domain / Timezone DOMAIN=your-domain.com -TZ=Your/Timezone +DOMAIN_PREFIX=gate + +TZ=Africa/Cairo ## ACME (Let's Encrypt) # Your email address for Let's Encrypt certificate notifications ACME_EMAIL=admin@your-domain.com -## Basic Authentication -# Generate password hash with: echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g -# Or use online generator: https://hostingcanada.org/htpasswd-generator/ -# Format: username:$$2y$$10$$hashed_password -BASIC_AUTH_USERS=admin:$$2y$$05$$HIIpotyLuLRMBX3sfUs6E.YM3lP9cpF7hK7sHaSXGs6mw/RS6MXCa - ## Umami (PostgreSQL) # Database user for Umami analytics UMAMI_DB_USER=umami @@ -24,8 +17,6 @@ UMAMI_DB_PASS=your_strong_database_password UMAMI_DB_NAME=umami # Random 64-character secret for Umami app security UMAMI_APP_SECRET=your_64_character_random_secret_here -# Website ID from Umami dashboard (use placeholder initially, update after setup) -UMAMI_WEBSITE_ID=placeholder-website-id ## pgAdmin # Default email for pgAdmin login @@ -33,8 +24,26 @@ PGADMIN_DEFAULT_EMAIL=admin@your-domain.com # Strong password for pgAdmin login PGADMIN_DEFAULT_PASSWORD=your_strong_pgadmin_password -## Grafana -# Admin username for Grafana dashboard -GRAFANA_ADMIN_USER=admin -# Strong password for Grafana admin user -GRAFANA_ADMIN_PASS=your_strong_grafana_password +## Authelia +# Database password for Authelia PostgreSQL +AUTHELIA_DB_PASSWORD=your_authelia_db_password +# JWT secret for Authelia (64+ character random string) +AUTHELIA_JWT_SECRET=your_64_character_jwt_secret_here +# Session secret for Authelia (64+ character random string) +AUTHELIA_SESSION_SECRET=your_64_character_session_secret_here +# Storage encryption key for Authelia (20+ character string) +AUTHELIA_STORAGE_ENCRYPTION_KEY=your_storage_encryption_key_here +# SMTP password for Authelia notifier +AUTHELIA_NOTIFIER_SMTP_PASSWORD=your_smtp_password_here + +## Gitea +# Database user for Gitea +GITEA_DB_USER=gitea +# Database password for Gitea PostgreSQL +GITEA_DB_PASSWORD=your_gitea_db_password_here +# Database name for Gitea +GITEA_DB_NAME=gitea +# Secret key for Gitea (64+ character random string - generate with: openssl rand -hex 32) +GITEA_SECRET_KEY=your_64_character_gitea_secret_key_here +# Internal token for Gitea (generate with: docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret INTERNAL_TOKEN) +GITEA_INTERNAL_TOKEN=your_gitea_internal_token_here diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 661625a..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,60 +0,0 @@ -# .github/workflows/deploy.yml -name: Deploy Gateway to VPS - -on: - push: - branches: [ "disbaled" ] - paths: - - "docker-compose.yml" - - ".github/workflows/deploy.yml" - workflow_dispatch: - -env: - REMOTE_DIR: ${{ secrets.REMOTE_DIR }} - -concurrency: - group: deploy-prod - cancel-in-progress: true - -jobs: - deploy: - name: Ship to VPS - runs-on: ubuntu-latest - environment: production - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts - - - name: Login to GitHub Container Registry on VPS - run: | - ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \ - "echo '${{ secrets.GITHUB_TOKEN }}' | docker login ghcr.io -u '${{ github.actor }}' --password-stdin" - - - name: Deploy (pull, up, prune) - run: | - ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \ - "export REMOTE_DIR='${{ secrets.REMOTE_DIR }}'; bash -se" <<'EOF' - set -euo pipefail - cd "$REMOTE_DIR" - - git pull origin main - docker compose pull - docker compose up -d --remove-orphans - docker image prune -af || true - EOF - - - name: Post-deploy smoke checks - run: | - echo "Deployed to ${{ secrets.SSH_HOST }}:${{ secrets.SSH_PORT }} → ${REMOTE_DIR}" - echo "Traefik: https://traefik.gate.${{ secrets.DOMAIN }}" - echo "Portainer: https://portainer.gate.${{ secrets.DOMAIN }}" - echo "Uptime Kuma: https://uptime.gate.${{ secrets.DOMAIN }}" - echo "Umami: https://umami.gate.${{ secrets.DOMAIN }}" - echo "pgAdmin: https://pgadmin.gate.${{ secrets.DOMAIN }}" diff --git a/README.md b/README.md index a6ff217..1f5ad43 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,35 @@ # Launchpad Gateway -A production-ready Traefik-based reverse proxy gateway with automatic SSL/TLS, analytics, monitoring, and container management. +A production-ready Traefik-based reverse proxy gateway with automatic SSL/TLS, analytics, monitoring, authentication, and container management. ## 🚀 Features - **Automatic SSL/TLS** certificates via Let's Encrypt - **Reverse Proxy** with Traefik v3.1 +- **Authentication** with Authelia (2FA, password reset) - **Web Analytics** with Umami and PostgreSQL - **Container Management** via Portainer - **Uptime Monitoring** with Uptime Kuma -- **Security Headers** and Basic Authentication +- **Server Monitoring** with Beszel +- **Git Repository Hosting** with Gitea +- **Security Headers** and Flexible Routing - **Flexible Domain Routing** (subdomains, paths, custom rules) ## 🏗️ Architecture ``` -Internet → Traefik (Port 80/443) → Internal Services (traefik_proxy network) +Internet → Traefik (Port 80/443) → Authelia (Auth) → Internal Services (traefik_proxy network) ``` ### Current Services -- **Traefik Dashboard**: `traefik.gate.${DOMAIN}` - Reverse proxy management -- **Portainer**: `portainer.gate.${DOMAIN}` - Docker container management -- **Uptime Kuma**: `uptime.gate.${DOMAIN}` - Service monitoring -- **Umami Analytics**: `umami.gate.${DOMAIN}` - Web analytics dashboard +- **Traefik Dashboard**: `traefik.${DOMAIN_PREFIX}.${DOMAIN}` - Reverse proxy management +- **Authelia**: `auth.${DOMAIN_PREFIX}.${DOMAIN}` - Authentication portal +- **Portainer**: `portainer.${DOMAIN_PREFIX}.${DOMAIN}` - Docker container management +- **Uptime Kuma**: `uptime.${DOMAIN_PREFIX}.${DOMAIN}` - Service monitoring +- **Umami Analytics**: `umami.${DOMAIN_PREFIX}.${DOMAIN}` - Web analytics dashboard +- **pgAdmin**: `pgadmin.${DOMAIN_PREFIX}.${DOMAIN}` - PostgreSQL administration +- **Beszel**: `beszel.${DOMAIN_PREFIX}.${DOMAIN}` - Server monitoring +- **Gitea**: `git.${DOMAIN_PREFIX}.${DOMAIN}` - Self-hosted Git service (SSH on port 222) ## 🛠️ Quick Start @@ -49,19 +56,31 @@ Update `.env` with your settings: ```bash # Domain and timezone DOMAIN=your-domain.com -TZ=Your/Timezone +DOMAIN_PREFIX=test +TZ=Africa/Cairo # Let's Encrypt email ACME_EMAIL=admin@your-domain.com -# Basic auth (generate with: htpasswd -nB admin) -BASIC_AUTH_USERS=admin:$$2y$$05$$your_hashed_password - # Database credentials UMAMI_DB_USER=umami UMAMI_DB_PASS=your_secure_password UMAMI_DB_NAME=umami UMAMI_APP_SECRET=your_64_character_secret + +# Authelia secrets (generate with openssl rand -hex 32) +AUTHELIA_DB_PASSWORD=your_secure_password +AUTHELIA_JWT_SECRET=your_64_char_secret +AUTHELIA_SESSION_SECRET=your_64_char_secret +AUTHELIA_STORAGE_ENCRYPTION_KEY=your_64_char_secret +AUTHELIA_NOTIFIER_SMTP_PASSWORD=your_smtp_password + +# Gitea secrets +GITEA_DB_USER=gitea +GITEA_DB_PASSWORD=your_secure_password +GITEA_DB_NAME=gitea +GITEA_SECRET_KEY=your_64_char_secret +GITEA_INTERNAL_TOKEN=your_internal_token ``` @@ -71,10 +90,14 @@ docker compose up -d ``` ### 4. Access Services -- **Traefik Dashboard**: `https://traefik.gate.your-domain.com` -- **Portainer**: `https://portainer.gate.your-domain.com` -- **Uptime Kuma**: `https://uptime.gate.your-domain.com` -- **Umami Analytics**: `https://umami.gate.your-domain.com` +- **Traefik Dashboard**: `https://traefik.${DOMAIN_PREFIX}.${DOMAIN}` +- **Authelia**: `https://auth.${DOMAIN_PREFIX}.${DOMAIN}` +- **Portainer**: `https://portainer.${DOMAIN_PREFIX}.${DOMAIN}` +- **Uptime Kuma**: `https://uptime.${DOMAIN_PREFIX}.${DOMAIN}` +- **Umami Analytics**: `https://umami.${DOMAIN_PREFIX}.${DOMAIN}` +- **pgAdmin**: `https://pgadmin.${DOMAIN_PREFIX}.${DOMAIN}` +- **Beszel**: `https://beszel.${DOMAIN_PREFIX}.${DOMAIN}` +- **Gitea**: `https://git.${DOMAIN_PREFIX}.${DOMAIN}` (SSH: port 222) ## 📋 Adding New Services @@ -231,16 +254,15 @@ services: ### Available Middlewares - **`security-headers`**: HSTS, XSS protection, content type sniffing prevention -- **`basic-auth`**: HTTP Basic Authentication for admin interfaces -- **`umami-analytics`**: Automatic web analytics tracking +- **`authelia`**: Authentication and authorization with 2FA support ### Usage Examples ```yaml # Public application with analytics -- traefik.http.routers.app.middlewares=umami-analytics,security-headers +- traefik.http.routers.app.middlewares=security-headers # Admin interface with authentication -- traefik.http.routers.admin.middlewares=basic-auth,security-headers +- traefik.http.routers.admin.middlewares=authelia@docker,security-headers # API endpoint (security headers only) - traefik.http.routers.api.middlewares=security-headers @@ -274,6 +296,7 @@ services: ### Built-in Monitoring - **Uptime Kuma**: Service availability monitoring +- **Beszel**: Server resource monitoring (CPU, RAM, disk, network) - **Traefik Dashboard**: Traffic and routing metrics - **Umami Analytics**: Web traffic analytics (privacy-focused) diff --git a/alertmanager.yml b/alertmanager.yml deleted file mode 100644 index 5684b39..0000000 --- a/alertmanager.yml +++ /dev/null @@ -1,17 +0,0 @@ -global: - smtp_smarthost: 'smtp.example.com:587' - smtp_from: 'alertmanager@example.com' - smtp_auth_username: 'alertmanager' - smtp_auth_password: 'dummy_password' - -route: - group_by: ['alertname'] - group_wait: 10s - group_interval: 10s - repeat_interval: 1h - receiver: 'email' - -receivers: - - name: 'email' - email_configs: - - to: 'admin@example.com' \ No newline at end of file diff --git a/authelia/configuration.template.yml b/authelia/configuration.template.yml new file mode 100644 index 0000000..7431e16 --- /dev/null +++ b/authelia/configuration.template.yml @@ -0,0 +1,60 @@ +--- +# Authelia configuration +# This is a minimal configuration for getting started with Authelia + +server: + address: 'tcp://:9091' + endpoints: + authz: + forward-auth: + implementation: 'ForwardAuth' + +authentication_backend: + file: + path: '/config/users_database.yml' + +access_control: + default_policy: 'one_factor' + rules: + - domain: 'pgadmin.${DOMAIN_PREFIX}.${DOMAIN}' + policy: 'two_factor' + - domain: 'beszel.${DOMAIN_PREFIX}.${DOMAIN}' + policy: 'two_factor' + - domain: 'traefik.${DOMAIN_PREFIX}.${DOMAIN}' + policy: 'two_factor' + - domain: '*.${DOMAIN_PREFIX}.${DOMAIN}' + policy: 'one_factor' + +session: + name: 'authelia_session' + cookies: + - domain: '${DOMAIN}' + authelia_url: 'https://auth.${DOMAIN_PREFIX}.${DOMAIN}' + default_redirection_url: 'https://portainer.${DOMAIN_PREFIX}.${DOMAIN}' + +storage: + postgres: + address: 'tcp://authelia-db:5432' + database: 'authelia' + username: 'authelia' + +notifier: + disable_startup_check: true + # Configure SMTP for production email notifications + # For testing, you can use filesystem notifier instead: + # filesystem: + # filename: /config/notification.txt + smtp: + address: 'submissions://smtp.gmail.com:465' + username: 'your-email@gmail.com' + sender: 'Authelia ' + # For Gmail, use an App Password (not your regular password) + # Generate at: https://myaccount.google.com/apppasswords + +identity_validation: + reset_password: {} + +regulation: + max_retries: 3 + find_time: 120 + ban_time: 300 diff --git a/authelia/configuration.yml b/authelia/configuration.yml index 3c0a237..24e0f6b 100644 --- a/authelia/configuration.yml +++ b/authelia/configuration.yml @@ -3,7 +3,7 @@ # This is a minimal configuration for getting started with Authelia server: - port: 9091 + address: 'tcp://:9091' endpoints: authz: forward-auth: @@ -14,45 +14,37 @@ authentication_backend: path: '/config/users_database.yml' access_control: - default_policy: 'deny' + default_policy: 'one_factor' rules: - - domain: 'pgadmin.gate.${DOMAIN}' + - domain: 'pgadmin.test.3launchpad.com' policy: 'two_factor' - - domain: '*.gate.${DOMAIN}' + - domain: '*.test.3launchpad.com' policy: 'one_factor' session: name: 'authelia_session' - secret: '${AUTHELIA_SESSION_SECRET}' cookies: - - domain: 'gate.${DOMAIN}' - authelia_url: 'https://auth.gate.${DOMAIN}' - default_redirection_url: 'https://login.gate.${DOMAIN}' + - domain: '3launchpad.com' + authelia_url: 'https://auth.test.3launchpad.com' + default_redirection_url: 'https://portainer.test.3launchpad.com' storage: postgres: - host: 'authelia-db' - port: 5432 + address: 'tcp://authelia-db:5432' database: 'authelia' username: 'authelia' - password: '${AUTHELIA_DB_PASSWORD}' notifier: + disable_startup_check: true smtp: + address: 'smtp://localhost:25' username: 'authelia' - password: 'dummy_password' - host: 'smtp.example.com' - port: 587 sender: 'authelia@example.com' -jwt_secret: '${AUTHELIA_JWT_SECRET}' +identity_validation: + reset_password: {} -api: - endpoints: - reset_password: - disable: false - -regulations: +regulation: max_retries: 3 find_time: 120 - ban_time: 300 \ No newline at end of file + ban_time: 300 diff --git a/authelia/users_database.yml b/authelia/users_database.yml index d0bf72f..b66e7d5 100644 --- a/authelia/users_database.yml +++ b/authelia/users_database.yml @@ -1,10 +1,17 @@ --- # Authelia users database -# This is a simple file-based user database for testing +# This is a simple file-based user database +# +# To generate a new password hash, run: +# docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'YOUR_PASSWORD' +# +# IMPORTANT: Change the default password before production use! users: admin: displayname: "Administrator" - password: "$argon2id$v=19$m=65536,t=3,p=4$abcdefghijklmnopqrstuvwx$abcdefghijklmnopqrstuvwxabcdefghijklmnopqrstuvwx" # Dummy hash for 'password' - email: admin@example.com - groups: [] \ No newline at end of file + # Default password: "Admin@123456" - CHANGE THIS! + password: "$argon2id$v=19$m=65536,t=3,p=4$O1Qjq7AB4/xJ7Qk1dUqp/g$PhVqFWEqyQTJeSnCeiCC3lrcWcpw37kYttw4Xh/qUsk" + email: admin@3launchpad.com + groups: + - admins \ No newline at end of file diff --git a/beszel_agent_data/fingerprint b/beszel_agent_data/fingerprint new file mode 100644 index 0000000..7d9c86f --- /dev/null +++ b/beszel_agent_data/fingerprint @@ -0,0 +1 @@ +c17e1b1086d55cc7d4bfacbfa6b9472ca975863413fb629f \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index bdf20bb..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,337 +0,0 @@ -######################## -# Networks & Volumes -######################## -networks: - traefik_proxy: - name: traefik_proxy - -volumes: - traefik_letsencrypt: - traefik_logs: - portainer_data: - uptime_kuma_data: - umami_data: - pgadmin_data: - authelia_config: - authelia_db_data: - grafana_data: - prometheus_data: - alertmanager_data: - -######################## -# Services -######################## -services: - - ## ───────────────────────────────────────────── - ## Traefik — edge router + ACME (HTTP-01) - ## ───────────────────────────────────────────── - traefik: - image: traefik:latest - container_name: traefik - restart: unless-stopped - ports: - - "80:80" - - "443:443" - # Mail protocol ports for MailCow integration - - "25:25" # SMTP - - "465:465" # SMTPS - - "587:587" # Submission - - "143:143" # IMAP - - "993:993" # IMAPS - - "110:110" # POP3 - - "995:995" # POP3S - - "4190:4190" # ManageSieve - networks: [traefik_proxy] - environment: - TZ: "${TZ}" - command: - # Experimental plugins - - --experimental.plugins.traefik-umami-plugin.modulename=github.com/1cedsoda/traefik-umami-plugin - - --experimental.plugins.traefik-umami-plugin.version=v1.0.3 - - # Providers - - --providers.docker=true - - --providers.docker.exposedbydefault=false - - # Entrypoints - - --entrypoints.web.address=:80 - - --entrypoints.web.http.redirections.entrypoint.to=websecure - - --entrypoints.web.http.redirections.entrypoint.scheme=https - - --entrypoints.websecure.address=:443 - - --entrypoints.web.forwardedheaders.insecure=true - - --entrypoints.websecure.forwardedheaders.insecure=true - - # Mail protocol entrypoints for MailCow integration - - --entrypoints.smtp.address=:25 - - --entrypoints.smtps.address=:465 - - --entrypoints.submission.address=:587 - - --entrypoints.imap.address=:143 - - --entrypoints.imaps.address=:993 - - --entrypoints.pop3.address=:110 - - --entrypoints.pop3s.address=:995 - - --entrypoints.sieve.address=:4190 - - # Dashboard/API (internal) - - --api=true - - --api.dashboard=true - - # ACME via HTTP-01 (no registrar API needed) - - --certificatesresolvers.le.acme.email=${ACME_EMAIL} - - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json - - --certificatesresolvers.le.acme.httpchallenge=true - - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web - # (Alt) Use TLS-ALPN-01 if port 80 is blocked: - # - --certificatesresolvers.le.acme.tlschallenge=true - - # Global timeouts for slow backends - - --serversTransport.forwardingTimeouts.dialTimeout=30s - - --serversTransport.forwardingTimeouts.responseHeaderTimeout=60s - - --serversTransport.forwardingTimeouts.idleConnTimeout=180s - - # Logs - - --accesslog.filepath=/var/log/traefik/access.log - - --accesslog.bufferingsize=100 - - --log.level=INFO - - --metrics.prometheus=true - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - traefik_letsencrypt:/letsencrypt - - traefik_logs:/var/log/traefik - labels: - - traefik.enable=true - - # Reusable security headers - - traefik.http.middlewares.security-headers.headers.stsSeconds=31536000 - - traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true - - traefik.http.middlewares.security-headers.headers.stsPreload=true - - traefik.http.middlewares.security-headers.headers.browserXssFilter=true - - traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true - - traefik.http.middlewares.security-headers.headers.referrerPolicy=no-referrer-when-downgrade - - # # Basic Auth middleware - # - traefik.http.middlewares.basic-auth.basicauth.users=${BASIC_AUTH_USERS} - - # Authelia middleware - - traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/authz/forward-auth - - traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true - - traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Name - - # Traefik dashboard (protected) - - traefik.http.routers.traefik.rule=Host(`traefik.test.${DOMAIN}`) - - traefik.http.routers.traefik.entrypoints=websecure - - traefik.http.routers.traefik.tls.certresolver=le - - traefik.http.routers.traefik.service=api@internal - - traefik.http.routers.traefik.middlewares=authelia@docker,security-headers - - ## ───────────────────────────────────────────── - ## Authelia — authentication and authorization - ## ───────────────────────────────────────────── - authelia: - image: authelia/authelia:latest - container_name: authelia - restart: unless-stopped - networks: [traefik_proxy] - volumes: - - authelia_config:/config - environment: - TZ: "${TZ}" - AUTHELIA_DB_PASSWORD: "${AUTHELIA_DB_PASSWORD}" - AUTHELIA_JWT_SECRET: "${AUTHELIA_JWT_SECRET}" - AUTHELIA_SESSION_SECRET: "${AUTHELIA_SESSION_SECRET}" - depends_on: - - authelia-db - labels: - - traefik.enable=true - - traefik.http.routers.authelia.rule=Host(`auth.test.${DOMAIN}`) - - traefik.http.routers.authelia.entrypoints=websecure - - traefik.http.routers.authelia.tls.certresolver=le - - traefik.http.services.authelia.loadbalancer.server.port=9091 - - ## ───────────────────────────────────────────── - ## Authelia Database — PostgreSQL - ## ───────────────────────────────────────────── - authelia-db: - image: postgres:15-alpine - container_name: authelia-db - restart: unless-stopped - networks: [traefik_proxy] - environment: - POSTGRES_DB: authelia - POSTGRES_USER: authelia - POSTGRES_PASSWORD: ${AUTHELIA_DB_PASSWORD} - volumes: - - authelia_db_data:/var/lib/postgresql/data - - ## ───────────────────────────────────────────── - ## Portainer — Docker control plane - ## ───────────────────────────────────────────── - portainer: - image: portainer/portainer-ce:latest - container_name: portainer - restart: unless-stopped - networks: [traefik_proxy] - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - portainer_data:/data - labels: - - traefik.enable=true - - traefik.http.routers.portainer.rule=Host(`portainer.test.${DOMAIN}`) - - traefik.http.routers.portainer.entrypoints=websecure - - traefik.http.routers.portainer.tls.certresolver=le - - traefik.http.routers.portainer.middlewares=security-headers - - traefik.http.services.portainer.loadbalancer.server.port=9000 - - ## ───────────────────────────────────────────── - ## Uptime Kuma — status page / checks - ## ───────────────────────────────────────────── - uptime-kuma: - image: louislam/uptime-kuma:latest - container_name: uptime-kuma - restart: unless-stopped - volumes: - - uptime_kuma_data:/app/data - networks: [traefik_proxy] - labels: - - traefik.enable=true - - traefik.http.routers.kuma.rule=Host(`uptime.test.${DOMAIN}`) - - traefik.http.routers.kuma.entrypoints=websecure - - traefik.http.routers.kuma.tls.certresolver=le - - traefik.http.routers.kuma.middlewares=security-headers - - traefik.http.services.kuma.loadbalancer.server.port=3001 - - ## ───────────────────────────────────────────── - ## Umami — web analytics - ## ───────────────────────────────────────────── - umami: - image: ghcr.io/umami-software/umami:postgresql-latest - container_name: umami - restart: unless-stopped - networks: [traefik_proxy] - environment: - DATABASE_URL: postgresql://${UMAMI_DB_USER}:${UMAMI_DB_PASS}@umami-db:5432/${UMAMI_DB_NAME} - DATABASE_TYPE: postgresql - APP_SECRET: ${UMAMI_APP_SECRET} - depends_on: - - umami-db - labels: - - traefik.enable=true - - traefik.http.routers.umami.rule=Host(`umami.test.${DOMAIN}`) - - traefik.http.routers.umami.entrypoints=websecure - - traefik.http.routers.umami.tls.certresolver=le - - traefik.http.routers.umami.middlewares=security-headers - - traefik.http.services.umami.loadbalancer.server.port=3000 - - ## ───────────────────────────────────────────── - ## Umami Database — PostgreSQL - ## ───────────────────────────────────────────── - umami-db: - image: postgres:15-alpine - container_name: umami-db - restart: unless-stopped - networks: [traefik_proxy] - environment: - POSTGRES_DB: ${UMAMI_DB_NAME} - POSTGRES_USER: ${UMAMI_DB_USER} - POSTGRES_PASSWORD: ${UMAMI_DB_PASS} - volumes: - - umami_data:/var/lib/postgresql/data - - # ───────────────────────────────────────────── - # pgAdmin — PostgreSQL administration - # ───────────────────────────────────────────── - pgadmin: - image: dpage/pgadmin4:latest - container_name: pgadmin - restart: unless-stopped - networks: [traefik_proxy] - environment: - PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} - PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} - PGADMIN_CONFIG_SERVER_MODE: 'True' - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' - # Fix CSRF issues behind reverse proxy - PGADMIN_CONFIG_WTF_CSRF_CHECK_DEFAULT: 'False' - PGADMIN_CONFIG_WTF_CSRF_TIME_LIMIT: 'None' - PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'False' - # Trust proxy headers - PGADMIN_CONFIG_PROXY_X_HOST_COUNT: '1' - PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT: '1' - volumes: - - pgadmin_data:/var/lib/pgadmin - labels: - - traefik.enable=true - - traefik.http.routers.pgadmin.rule=Host(`pgadmin.test.${DOMAIN}`) - - traefik.http.routers.pgadmin.entrypoints=websecure - - traefik.http.routers.pgadmin.tls.certresolver=le - - traefik.http.services.pgadmin.loadbalancer.server.port=80 - - ## ───────────────────────────────────────────── - ## Prometheus — monitoring - ## ───────────────────────────────────────────── - prometheus: - image: prom/prometheus:latest - container_name: prometheus - restart: unless-stopped - networks: [traefik_proxy] - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml - - ./rules.yml:/etc/prometheus/rules.yml - - prometheus_data:/prometheus - - /var/run/docker.sock:/var/run/docker.sock:ro - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.console.libraries=/etc/prometheus/console_libraries' - - '--web.console.templates=/etc/prometheus/consoles' - - '--storage.tsdb.retention.time=200h' - - '--web.enable-lifecycle' - labels: - - traefik.enable=true - - traefik.http.routers.prometheus.rule=Host(`prometheus.test.${DOMAIN}`) - - traefik.http.routers.prometheus.entrypoints=websecure - - traefik.http.routers.prometheus.tls.certresolver=le - - traefik.http.routers.prometheus.middlewares=authelia@docker,security-headers - - traefik.http.services.prometheus.loadbalancer.server.port=9090 - - ## ───────────────────────────────────────────── - ## Grafana — visualization - ## ───────────────────────────────────────────── - grafana: - image: grafana/grafana:latest - container_name: grafana - restart: unless-stopped - networks: [traefik_proxy] - environment: - GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} - volumes: - - grafana_data:/var/lib/grafana - labels: - - traefik.enable=true - - traefik.http.routers.grafana.rule=Host(`grafana.test.${DOMAIN}`) - - traefik.http.routers.grafana.entrypoints=websecure - - traefik.http.routers.grafana.tls.certresolver=le - - traefik.http.routers.grafana.middlewares=authelia@docker,security-headers - - traefik.http.services.grafana.loadbalancer.server.port=3000 - - ## ───────────────────────────────────────────── - ## Alertmanager — alert handling - ## ───────────────────────────────────────────── - alertmanager: - image: prom/alertmanager:latest - container_name: alertmanager - restart: unless-stopped - networks: [traefik_proxy] - volumes: - - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml - - alertmanager_data:/alertmanager - command: - - '--config.file=/etc/alertmanager/alertmanager.yml' - - '--storage.path=/alertmanager' - labels: - - traefik.enable=true - - traefik.http.routers.alertmanager.rule=Host(`alertmanager.test.${DOMAIN}`) - - traefik.http.routers.alertmanager.entrypoints=websecure - - traefik.http.routers.alertmanager.tls.certresolver=le - - traefik.http.routers.alertmanager.middlewares=authelia@docker,security-headers - - traefik.http.services.alertmanager.loadbalancer.server.port=9093 diff --git a/docker-compose.yml b/docker-compose.yml index 3f03f0b..d64cdb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,11 +12,10 @@ volumes: uptime_kuma_data: umami_data: pgadmin_data: - authelia_config: authelia_db_data: - grafana_data: - prometheus_data: - alertmanager_data: + beszel_data: + gitea_data: + gitea_db_data: ######################## # Services @@ -46,6 +45,9 @@ services: environment: TZ: "${TZ}" command: + # Enable ping endpoint for health checks + - --ping=true + # Experimental plugins - --experimental.plugins.traefik-umami-plugin.modulename=github.com/1cedsoda/traefik-umami-plugin - --experimental.plugins.traefik-umami-plugin.version=v1.0.3 @@ -118,11 +120,17 @@ services: - traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Name # Traefik dashboard (protected) - - traefik.http.routers.traefik.rule=Host(`traefik.gate.${DOMAIN}`) + - traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN_PREFIX}.${DOMAIN}`) - traefik.http.routers.traefik.entrypoints=websecure - traefik.http.routers.traefik.tls.certresolver=le - traefik.http.routers.traefik.service=api@internal - traefik.http.routers.traefik.middlewares=authelia@docker,security-headers + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s ## ───────────────────────────────────────────── ## Authelia — authentication and authorization @@ -133,17 +141,27 @@ services: restart: unless-stopped networks: [traefik_proxy] volumes: - - authelia_config:/config + - ./authelia:/config + entrypoint: /bin/sh + command: + - -c + - | + eval "echo \"$(cat /config/configuration.template.yml)\"" > /config/configuration.yml + exec /app/entrypoint.sh environment: TZ: "${TZ}" - AUTHELIA_DB_PASSWORD: "${AUTHELIA_DB_PASSWORD}" - AUTHELIA_JWT_SECRET: "${AUTHELIA_JWT_SECRET}" - AUTHELIA_SESSION_SECRET: "${AUTHELIA_SESSION_SECRET}" + DOMAIN: "${DOMAIN}" + DOMAIN_PREFIX: "${DOMAIN_PREFIX}" + AUTHELIA_SESSION_SECRET: '${AUTHELIA_SESSION_SECRET}' + AUTHELIA_STORAGE_ENCRYPTION_KEY: '${AUTHELIA_STORAGE_ENCRYPTION_KEY}' + AUTHELIA_STORAGE_POSTGRES_PASSWORD: '${AUTHELIA_DB_PASSWORD}' + AUTHELIA_NOTIFIER_SMTP_PASSWORD: '${AUTHELIA_NOTIFIER_SMTP_PASSWORD}' + AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: '${AUTHELIA_JWT_SECRET}' depends_on: - authelia-db labels: - traefik.enable=true - - traefik.http.routers.authelia.rule=Host(`auth.gate.${DOMAIN}`) + - traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN_PREFIX}.${DOMAIN}`) - traefik.http.routers.authelia.entrypoints=websecure - traefik.http.routers.authelia.tls.certresolver=le - traefik.http.services.authelia.loadbalancer.server.port=9091 @@ -162,6 +180,12 @@ services: POSTGRES_PASSWORD: ${AUTHELIA_DB_PASSWORD} volumes: - authelia_db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U authelia -d authelia"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s ## ───────────────────────────────────────────── ## Portainer — Docker control plane @@ -171,12 +195,13 @@ services: container_name: portainer restart: unless-stopped networks: [traefik_proxy] + command: --host unix:///var/run/docker.sock volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data labels: - traefik.enable=true - - traefik.http.routers.portainer.rule=Host(`portainer.gate.${DOMAIN}`) + - traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN_PREFIX}.${DOMAIN}`) - traefik.http.routers.portainer.entrypoints=websecure - traefik.http.routers.portainer.tls.certresolver=le - traefik.http.routers.portainer.middlewares=security-headers @@ -194,7 +219,7 @@ services: networks: [traefik_proxy] labels: - traefik.enable=true - - traefik.http.routers.kuma.rule=Host(`uptime.gate.${DOMAIN}`) + - traefik.http.routers.kuma.rule=Host(`uptime.${DOMAIN_PREFIX}.${DOMAIN}`) - traefik.http.routers.kuma.entrypoints=websecure - traefik.http.routers.kuma.tls.certresolver=le - traefik.http.routers.kuma.middlewares=security-headers @@ -216,11 +241,17 @@ services: - umami-db labels: - traefik.enable=true - - traefik.http.routers.umami.rule=Host(`umami.gate.${DOMAIN}`) + - traefik.http.routers.umami.rule=Host(`umami.${DOMAIN_PREFIX}.${DOMAIN}`) - traefik.http.routers.umami.entrypoints=websecure - traefik.http.routers.umami.tls.certresolver=le - traefik.http.routers.umami.middlewares=security-headers - traefik.http.services.umami.loadbalancer.server.port=3000 + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s ## ───────────────────────────────────────────── ## Umami Database — PostgreSQL @@ -236,6 +267,12 @@ services: POSTGRES_PASSWORD: ${UMAMI_DB_PASS} volumes: - umami_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${UMAMI_DB_USER} -d ${UMAMI_DB_NAME}"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s # ───────────────────────────────────────────── # pgAdmin — PostgreSQL administration @@ -250,88 +287,113 @@ services: PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} PGADMIN_CONFIG_SERVER_MODE: 'True' PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' - # Fix CSRF issues behind reverse proxy PGADMIN_CONFIG_WTF_CSRF_CHECK_DEFAULT: 'False' PGADMIN_CONFIG_WTF_CSRF_TIME_LIMIT: 'None' PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'False' - # Trust proxy headers PGADMIN_CONFIG_PROXY_X_HOST_COUNT: '1' PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT: '1' volumes: - pgadmin_data:/var/lib/pgadmin labels: - traefik.enable=true - - traefik.http.routers.pgadmin.rule=Host(`pgadmin.gate.${DOMAIN}`) + - traefik.http.routers.pgadmin.rule=Host(`pgadmin.${DOMAIN_PREFIX}.${DOMAIN}`) - traefik.http.routers.pgadmin.entrypoints=websecure - traefik.http.routers.pgadmin.tls.certresolver=le + - traefik.http.routers.pgadmin.middlewares=security-headers - traefik.http.services.pgadmin.loadbalancer.server.port=80 + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s ## ───────────────────────────────────────────── - ## Prometheus — monitoring + ## Beszel Hub — lightweight server monitoring ## ───────────────────────────────────────────── - prometheus: - image: prom/prometheus:latest - container_name: prometheus + beszel: + image: henrygd/beszel:latest + container_name: beszel restart: unless-stopped networks: [traefik_proxy] volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml - - ./rules.yml:/etc/prometheus/rules.yml - - prometheus_data:/prometheus - - /var/run/docker.sock:/var/run/docker.sock:ro - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.console.libraries=/etc/prometheus/console_libraries' - - '--web.console.templates=/etc/prometheus/consoles' - - '--storage.tsdb.retention.time=200h' - - '--web.enable-lifecycle' + - beszel_data:/beszel_data labels: - traefik.enable=true - - traefik.http.routers.prometheus.rule=Host(`prometheus.gate.${DOMAIN}`) - - traefik.http.routers.prometheus.entrypoints=websecure - - traefik.http.routers.prometheus.tls.certresolver=le - - traefik.http.routers.prometheus.middlewares=authelia@docker,security-headers - - traefik.http.services.prometheus.loadbalancer.server.port=9090 + - traefik.http.routers.beszel.rule=Host(`beszel.${DOMAIN_PREFIX}.${DOMAIN}`) + - traefik.http.routers.beszel.entrypoints=websecure + - traefik.http.routers.beszel.tls.certresolver=le + - traefik.http.routers.beszel.middlewares=security-headers + - traefik.http.services.beszel.loadbalancer.server.port=8090 + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8090"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s ## ───────────────────────────────────────────── - ## Grafana — visualization + ## Gitea — self-hosted Git service ## ───────────────────────────────────────────── - grafana: - image: grafana/grafana:latest - container_name: grafana + gitea: + image: docker.gitea.com/gitea:latest + container_name: gitea restart: unless-stopped networks: [traefik_proxy] environment: - GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} + - USER_UID=1000 + - USER_GID=1000 + - GITEA__database__DB_TYPE=postgres + - GITEA__database__HOST=gitea-db:5432 + - GITEA__database__NAME=${GITEA_DB_NAME} + - GITEA__database__USER=${GITEA_DB_USER} + - GITEA__database__PASSWD=${GITEA_DB_PASSWORD} + - GITEA__server__DOMAIN=git.${DOMAIN_PREFIX}.${DOMAIN} + - GITEA__server__SSH_DOMAIN=git.${DOMAIN_PREFIX}.${DOMAIN} + - GITEA__server__ROOT_URL=https://git.${DOMAIN_PREFIX}.${DOMAIN}/ + - GITEA__server__SSH_PORT=222 + - GITEA__server__SSH_LISTEN_PORT=22 + - GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY} + - GITEA__security__INTERNAL_TOKEN=${GITEA_INTERNAL_TOKEN} volumes: - - grafana_data:/var/lib/grafana + - gitea_data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "222:22" + depends_on: + - gitea-db labels: - traefik.enable=true - - traefik.http.routers.grafana.rule=Host(`grafana.gate.${DOMAIN}`) - - traefik.http.routers.grafana.entrypoints=websecure - - traefik.http.routers.grafana.tls.certresolver=le - - traefik.http.routers.grafana.middlewares=authelia@docker,security-headers - - traefik.http.services.grafana.loadbalancer.server.port=3000 + - traefik.http.routers.gitea.rule=Host(`git.${DOMAIN_PREFIX}.${DOMAIN}`) + - traefik.http.routers.gitea.entrypoints=websecure + - traefik.http.routers.gitea.tls.certresolver=le + - traefik.http.routers.gitea.middlewares=security-headers + - traefik.http.services.gitea.loadbalancer.server.port=3000 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s ## ───────────────────────────────────────────── - ## Alertmanager — alert handling + ## Gitea Database — PostgreSQL ## ───────────────────────────────────────────── - alertmanager: - image: prom/alertmanager:latest - container_name: alertmanager + gitea-db: + image: postgres:15-alpine + container_name: gitea-db restart: unless-stopped networks: [traefik_proxy] + environment: + POSTGRES_DB: ${GITEA_DB_NAME} + POSTGRES_USER: ${GITEA_DB_USER} + POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD} volumes: - - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml - - alertmanager_data:/alertmanager - command: - - '--config.file=/etc/alertmanager/alertmanager.yml' - - '--storage.path=/alertmanager' - labels: - - traefik.enable=true - - traefik.http.routers.alertmanager.rule=Host(`alertmanager.gate.${DOMAIN}`) - - traefik.http.routers.alertmanager.entrypoints=websecure - - traefik.http.routers.alertmanager.tls.certresolver=le - - traefik.http.routers.alertmanager.middlewares=authelia@docker,security-headers - - traefik.http.services.alertmanager.loadbalancer.server.port=9093 + - gitea_db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${GITEA_DB_USER} -d ${GITEA_DB_NAME}"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s diff --git a/prometheus.yml b/prometheus.yml deleted file mode 100644 index eb57ec1..0000000 --- a/prometheus.yml +++ /dev/null @@ -1,43 +0,0 @@ -global: - scrape_interval: 15s - evaluation_interval: 15s - -rule_files: - - rules.yml - -alerting: - alertmanagers: - - static_configs: - - targets: - - alertmanager:9093 - -scrape_configs: - - job_name: 'prometheus' - static_configs: - - targets: ['localhost:9090'] - - - job_name: 'traefik' - static_configs: - - targets: ['traefik:8080'] - metrics_path: /metrics - - - job_name: 'authelia' - static_configs: - - targets: ['authelia:9091'] - - - job_name: 'grafana' - static_configs: - - targets: ['grafana:3000'] - - - job_name: 'docker' - docker_sd_configs: - - host: unix:///var/run/docker.sock - relabel_configs: - - source_labels: [__meta_docker_container_name] - regex: '/(.*)' - target_label: container_name - - source_labels: [__meta_docker_container_label_com_docker_compose_service] - target_label: service - - action: keep - source_labels: [__meta_docker_container_label_com_docker_compose_service] - regex: '.*' \ No newline at end of file diff --git a/rules.yml b/rules.yml deleted file mode 100644 index a96694d..0000000 --- a/rules.yml +++ /dev/null @@ -1,36 +0,0 @@ -groups: - - name: recording_rules - rules: - - record: job:up:sum - expr: sum(up) by (job) - - record: job:up:count - expr: count(up) by (job) - - - name: alerting_rules - rules: - - alert: InstanceDown - expr: up == 0 - for: 5m - labels: - severity: critical - annotations: - summary: "Instance {{ $labels.instance }} down" - description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." - - - alert: TraefikDown - expr: up{job="traefik"} == 0 - for: 2m - labels: - severity: warning - annotations: - summary: "Traefik is down" - description: "Traefik has been down for more than 2 minutes." - - - alert: AutheliaDown - expr: up{job="authelia"} == 0 - for: 5m - labels: - severity: critical - annotations: - summary: "Authelia is down" - description: "Authelia authentication service is unavailable." \ No newline at end of file