diff --git a/.woodpecker/build-caddy.yml b/.woodpecker/build-caddy.yml deleted file mode 100644 index 6987c562..00000000 --- a/.woodpecker/build-caddy.yml +++ /dev/null @@ -1,35 +0,0 @@ -when: - - event: push - path: ".woodpecker/build-caddy.yml" - - event: manual - evaluate: 'PIPELINE == "build-caddy"' - -steps: - - name: build - image: gitea.renner.uno/gitea_admin/ci-caddy-builder:2026-05-01 - commands: - - xcaddy build v2.11.2 - --with github.com/caddy-dns/cloudflare - --with github.com/ss098/certmagic-s3 - --with github.com/hslatman/caddy-crowdsec-bouncer/http@v0.10.0 - --with github.com/hslatman/caddy-crowdsec-bouncer/appsec@v0.10.0 - --with github.com/mholt/caddy-l4 - --output caddy - - ./caddy version - - ./caddy list-modules | grep -c layer4 - - - name: release - image: gitea.renner.uno/gitea_admin/ci-k8s:2026-04-29 - environment: - GITEA_TOKEN: - from_secret: gitea_token - commands: - # Delete old asset from 'latest' release, then upload new one - - "RELEASE=$(curl -s -H \"Authorization: token $GITEA_TOKEN\" https://gitea.renner.uno/api/v1/repos/gitea_admin/homelab/releases/tags/latest)" - - "RELEASE_ID=$(echo $RELEASE | jq -r '.id')" - - "echo \"Release ID: $RELEASE_ID\"" - # Delete existing caddy asset if present - - "ASSET_ID=$(echo $RELEASE | jq -r '.assets[]? | select(.name==\"caddy\") | .id')" - - "[ -n \"$ASSET_ID\" ] && curl -s -X DELETE -H \"Authorization: token $GITEA_TOKEN\" https://gitea.renner.uno/api/v1/repos/gitea_admin/homelab/releases/$RELEASE_ID/assets/$ASSET_ID || true" - # Upload new binary - - "curl -s -X POST -H \"Authorization: token $GITEA_TOKEN\" -F attachment=@caddy https://gitea.renner.uno/api/v1/repos/gitea_admin/homelab/releases/$RELEASE_ID/assets?name=caddy | jq '{name: .name, size: .size}'" diff --git a/.woodpecker/build-ci-images.yml b/.woodpecker/build-ci-images.yml index 6c80280f..55ad4342 100644 --- a/.woodpecker/build-ci-images.yml +++ b/.woodpecker/build-ci-images.yml @@ -67,26 +67,6 @@ steps: --destination gitea.renner.uno/gitea_admin/ci-tofu:$TAG \ --destination gitea.renner.uno/gitea_admin/ci-tofu:latest - - name: build-caddy-builder - image: gcr.io/kaniko-project/executor:v1.23.2-debug - directory: ci/images/caddy-builder - depends_on: [] - environment: - GITEA_USER: gitea_admin - GITEA_TOKEN: - from_secret: gitea_token - commands: - - mkdir -p /kaniko/.docker - - AUTH=$(printf '%s:%s' "$GITEA_USER" "$GITEA_TOKEN" | base64 | tr -d '\n') - - printf '{"auths":{"gitea.renner.uno":{"auth":"%s"}}}' "$AUTH" > /kaniko/.docker/config.json - - TAG=$(date +%Y-%m-%d) - - | - /kaniko/executor \ - --context . \ - --dockerfile Containerfile \ - --destination gitea.renner.uno/gitea_admin/ci-caddy-builder:$TAG \ - --destination gitea.renner.uno/gitea_admin/ci-caddy-builder:latest - - name: build-claude image: gcr.io/kaniko-project/executor:v1.23.2-debug directory: ci/images/claude diff --git a/.woodpecker/deploy-caddy.yml b/.woodpecker/deploy-caddy.yml deleted file mode 100644 index 8fbb959a..00000000 --- a/.woodpecker/deploy-caddy.yml +++ /dev/null @@ -1,35 +0,0 @@ -when: - - event: push - path: - - "ansible/roles/caddy-ha/**" - - "ansible/plays/install-caddy-ha.yml" - - ".woodpecker/deploy-caddy.yml" - - event: manual - evaluate: 'PIPELINE == "deploy-caddy"' - -timeout: 30 - -steps: - - name: deploy-caddy - image: gitea.renner.uno/gitea_admin/ci-ansible:2026-04-29 - environment: - ANSIBLE_FORCE_COLOR: "true" - ANSIBLE_HOST_KEY_CHECKING: "false" - ANSIBLE_ROLES_PATH: "../roles:./roles" - SSH_PRIVATE_KEY: - from_secret: ssh_private_key - VAULT_PASS: - from_secret: vault_pass - PROXMOX_TOKEN_ID: - from_secret: proxmox_token_id - PROXMOX_TOKEN_SECRET: - from_secret: proxmox_token_secret - commands: - - mkdir -p ~/.ssh - - echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519 - - chmod 600 ~/.ssh/id_ed25519 - - printf "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n" > ~/.ssh/config - - cd ansible - - echo "$VAULT_PASS" > .vault_pass && chmod 600 .vault_pass - - export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass - - ansible-playbook -i inventory/ plays/install-caddy-ha.yml -v diff --git a/ansible/inventory/group_vars/proxmox_caddy_ha/vars.yml b/ansible/inventory/group_vars/proxmox_caddy_ha/vars.yml deleted file mode 100644 index 97cc88dd..00000000 --- a/ansible/inventory/group_vars/proxmox_caddy_ha/vars.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -# NetBird client configuration for Caddy HA servers -# Setup key is stored in vault.yml -netbird_setup_key: "{{ vault_caddy_netbird_setup_key }}" diff --git a/ansible/inventory/group_vars/proxmox_caddy_ha/vault.yml b/ansible/inventory/group_vars/proxmox_caddy_ha/vault.yml deleted file mode 100644 index 21550435..00000000 --- a/ansible/inventory/group_vars/proxmox_caddy_ha/vault.yml +++ /dev/null @@ -1,9 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -37363434633432396531333038633664663632333963646630353239316332646638323364383162 -3338306237343739306231613164383064383037616463320a363961313964326438373762666365 -35313464333033653130316539356431303063323861333734313766373061323365613431343966 -3766636265626530370a643261653134313563663035623164353566653139383832313161386562 -62376364336565313739306130336434323638306137326263663030393633333831646562623138 -37313937363338393161313964396462346264633339636464663539383034373837376430366237 -36626664623762623130316163373933383335396463616161333434303863636330633439376131 -62393862393065396238 diff --git a/ansible/plays/install-caddy-ha.yml b/ansible/plays/install-caddy-ha.yml deleted file mode 100644 index 7260c458..00000000 --- a/ansible/plays/install-caddy-ha.yml +++ /dev/null @@ -1,18 +0,0 @@ -- name: Configure Caddy High Availability - hosts: proxmox_caddy_ha - serial: 1 - become: true - - tasks: - - name: Apply Caddy High Availability Role - import_role: - name: caddy-ha - vars: - caddy_ha_vip: 10.2.45.100 - caddy_ha_keepalived_auth_pass: "SecureCaddyHA" - caddy_ha_primary_host: caddy.dmz.renner.uno - caddy_ha_priorities: - "caddy.dmz.renner.uno": 100 - "caddy2.dmz.renner.uno": 90 - "caddy3.dmz.renner.uno": 80 - diff --git a/ansible/plays/install-cloudflared.yml b/ansible/plays/install-cloudflared.yml deleted file mode 100644 index f39d0aad..00000000 --- a/ansible/plays/install-cloudflared.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- name: Setup Cloudflared LXC - hosts: proxmox_cloudflared - remote_user: root - become: true - roles: - - cloudflared-lxc diff --git a/ansible/plays/install-crowdsec-caddy.yml b/ansible/plays/install-crowdsec-caddy.yml deleted file mode 100644 index 9a10aadf..00000000 --- a/ansible/plays/install-crowdsec-caddy.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -- name: Deploy CrowdSec on Caddy HA servers - hosts: proxmox_caddy_ha - serial: 1 - become: true - vars_files: - - vars/rustfs_vault.yml - - vars/caddy_setup_credentials.yml - - [ "vars/caddy_generated_credentials.yml", "vars/empty.yml" ] - - tasks: - - name: Install and configure CrowdSec - import_role: - name: crowdsec - - - name: Apply Caddy High Availability Role with CrowdSec - import_role: - name: caddy-ha - vars: - caddy_ha_vip: 10.2.45.100 - caddy_ha_keepalived_auth_pass: "SecureCaddyHA" - caddy_ha_primary_host: caddy.srv.renner.uno - crowdsec_enabled: true - crowdsec_lapi_url: "http://127.0.0.1:8080" - crowdsec_appsec_url: "http://127.0.0.1:7422" diff --git a/ansible/plays/install-netbird-gateway.yml b/ansible/plays/install-netbird-gateway.yml deleted file mode 100644 index 838b698d..00000000 --- a/ansible/plays/install-netbird-gateway.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -# Deploy NetBird Gateway to Homelab LXC -# Usage: ansible-playbook -i inventory/ plays/install-netbird-gateway.yml -# -# Prerequisites: -# - LXC 260 created via OpenTofu -# - NetBird server running at netbird.renner.uno -# - Setup key generated from NetBird management UI -# - Vault secrets configured: -# - netbird_gateway_setup_key - -- name: Deploy NetBird Gateway - hosts: netbird-gateway.srv.renner.uno - become: true - roles: - - common - - netbird-gateway diff --git a/ansible/plays/vars/caddy_generated_credentials.yml b/ansible/plays/vars/caddy_generated_credentials.yml deleted file mode 100644 index 0d997334..00000000 --- a/ansible/plays/vars/caddy_generated_credentials.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -caddy_ha_s3_access_key: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 62306337353164323163363334613963396563333133313634316164643632646537386463393730 - 3934363964613331326663363762303833393766353537650a663139623966316362646437653362 - 62356231623736333735643639326434333737323737623336366433373433643733323463396132 - 6230626633666137370a316239363837303038633738376531373261303633343635323737323732 - 6166 - -caddy_ha_s3_secret_key: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 35353431396362333634613134643733383562366230303035383563653131383134376238373539 - 3765623535373439343637373733303531653565663561380a306364303962613265623363623231 - 31303430646131313737306361343661373238346166386562636361383437376561373161303234 - 6164313036613065370a393662373466326361396134633434633034616235386366323132373964 - 37386361373262396438396633613865643235346563396434336330636330643438383638343661 - 3365333063663835393635306439613137313161343232333565 - diff --git a/ansible/plays/vars/caddy_setup_credentials.yml b/ansible/plays/vars/caddy_setup_credentials.yml deleted file mode 100644 index b9f8a964..00000000 --- a/ansible/plays/vars/caddy_setup_credentials.yml +++ /dev/null @@ -1,10 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -66343132346639356335353436323062303930326233386334653465663837326237643939623132 -6431623632316361656564333137323038323034326365370a396262643664396533616139623162 -64626635626364383561623964373236316335343633616230353530633933356661326532616666 -3639663032636630330a653264623562303433343830663437313239373135643362373864396537 -62323733373461336164323864346530386262623764386433326163663561626466666634326234 -33356639373364393431373236656537613434323638333033336137306535326133636666373565 -66333363316339376131633434373435343139393666623264366463653262346237333564643536 -39636335376330383133333062616637656530333366633266663531386537626163646566303637 -64653834653439316133323034303764643535353165343635343539613162636635 diff --git a/ansible/roles/caddy-ha/defaults/main.yml b/ansible/roles/caddy-ha/defaults/main.yml deleted file mode 100644 index 9d9b4301..00000000 --- a/ansible/roles/caddy-ha/defaults/main.yml +++ /dev/null @@ -1,37 +0,0 @@ ---- -caddy_ha_primary_host: caddy.dmz.renner.uno -caddy_ha_vip: 10.2.45.100 -caddy_ha_keepalived_auth_pass: "SecureCaddyHA" -caddy_ha_keepalived_interface: "{{ ansible_default_ipv4.interface | default('ens18') }}" - -# Keepalived priorities per host (higher = more preferred) -caddy_ha_priorities: - "caddy.dmz.renner.uno": 100 - "caddy2.dmz.renner.uno": 90 - "caddy3.dmz.renner.uno": 80 - -# RustFS / S3 Storage for Certificates -caddy_ha_s3_endpoint: "s3.renner.uno:9000" -caddy_ha_s3_bucket: "caddy-certificates" -# Default credentials for Caddy (will be overridden by vault or setup script) -# caddy_ha_s3_access_key: "" -# caddy_ha_s3_secret_key: "" -caddy_ha_s3_insecure: true - -# Admin credentials for setup (should be loaded from secure vars/vault) -# caddy_ha_s3_admin_access_key: "..." -# caddy_ha_s3_admin_secret_key: "..." - -caddy_version: "v2.11.2" - -# Caddy Binary Download URL -# Replace 'owner' with the user/org where the caddy-builder repo is located -caddy_ha_caddy_binary_url: "https://git.renner.uno/gitea_admin/homelab/releases/download/latest/caddy" - -# CrowdSec Integration -crowdsec_enabled: true -crowdsec_lapi_url: "http://127.0.0.1:8080" -crowdsec_appsec_url: "http://127.0.0.1:7422" -crowdsec_ticker_interval: "15s" -# API key is set per-host from the crowdsec role output -# crowdsec_caddy_api_key: "" diff --git a/ansible/roles/caddy-ha/files/setup_s3.py b/ansible/roles/caddy-ha/files/setup_s3.py deleted file mode 100644 index 50e2f7cd..00000000 --- a/ansible/roles/caddy-ha/files/setup_s3.py +++ /dev/null @@ -1,199 +0,0 @@ -import boto3 -import sys -import os -import json -import subprocess -import requests -from botocore.client import Config -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest -from botocore.credentials import Credentials -from botocore.exceptions import ClientError -from urllib.parse import urlparse - -endpoint = os.environ.get("S3_ENDPOINT") -admin_access_key = os.environ.get("S3_ADMIN_ACCESS_KEY") -admin_secret_key = os.environ.get("S3_ADMIN_SECRET_KEY") -existing_access_key = os.environ.get("S3_EXISTING_ACCESS_KEY") -existing_secret_key = os.environ.get("S3_EXISTING_SECRET_KEY") -bucket_name = os.environ.get("S3_BUCKET") -insecure = os.environ.get("S3_INSECURE", "false").lower() == "true" -vault_output_file = os.environ.get("S3_VAULT_FILE") -target_user = "caddy-ha" - -if not endpoint.startswith("http"): - endpoint = f"http://{endpoint}" if insecure else f"https://{endpoint}" - -# Security Check: Ensure existing keys are not the admin keys -if existing_access_key and existing_access_key == admin_access_key: - sys.stderr.write("Warning: Provided existing access key matches admin access key. Ignoring to prevent using admin credentials for Caddy.\n") - existing_access_key = None - existing_secret_key = None - -# Test existing keys if provided -if existing_access_key and existing_secret_key: - try: - s3_check = boto3.client('s3', - endpoint_url=endpoint, - aws_access_key_id=existing_access_key, - aws_secret_access_key=existing_secret_key, - config=Config(signature_version='s3v4'), - region_name='us-east-1', - verify=not insecure) - s3_check.head_bucket(Bucket=bucket_name) - # If successful, return existing keys - print(json.dumps({ - "bucket_created": False, - "access_key": existing_access_key, - "secret_key": existing_secret_key, - "used_existing": True - })) - sys.exit(0) - except Exception as e: - sys.stderr.write(f"Warning: Existing keys invalid or insufficient permissions: {e}. Generating new ones.\n") - -# Clients (Admin) -s3 = boto3.client('s3', - endpoint_url=endpoint, - aws_access_key_id=admin_access_key, - aws_secret_access_key=admin_secret_key, - config=Config(signature_version='s3v4'), - region_name='us-east-1', - verify=not insecure) - -# RustFS Admin API helper -def rustfs_admin_request(method, path, params=None, data=None): - import hashlib - if params is None: - params = {} - - url = f"{endpoint}{path}" - - # Calculate Payload Hash - content = data.encode('utf-8') if data else b'' - payload_hash = hashlib.sha256(content).hexdigest() - - headers = {} - if data: - headers["Content-Type"] = "application/json" - headers["x-amz-content-sha256"] = payload_hash - - request = AWSRequest( - method=method, - url=url, - data=data, - params=params, - headers=headers - ) - - # Sign Request - creds = Credentials(admin_access_key, admin_secret_key) - auth = SigV4Auth(creds, "s3", "us-east-1") - auth.add_auth(request) - - # Send Request - response = requests.request( - method=method, - url=request.url, - headers=dict(request.headers), - data=data, - params=params, - verify=not insecure - ) - return response - -def ensure_bucket(): - sys.stderr.write(f"Checking bucket {bucket_name}...\n") - try: - s3.head_bucket(Bucket=bucket_name) - sys.stderr.write(f"Bucket {bucket_name} exists.\n") - except ClientError as e: - error_code = int(e.response['Error']['Code']) - if error_code == 404: - sys.stderr.write(f"Bucket {bucket_name} not found. Creating...\n") - s3.create_bucket(Bucket=bucket_name) - sys.stderr.write(f"Bucket {bucket_name} created.\n") - else: - raise - -def generate_random_key(length=20): - import secrets - import string - alphabet = string.ascii_letters + string.digits - return ''.join(secrets.choice(alphabet) for i in range(length)) - -def ensure_user_and_keys(): - # RustFS specific implementation - - new_secret_key = generate_random_key(40) - - payload = json.dumps({ - "secretKey": new_secret_key, - "status": "enabled" - }) - - # 1. Add User (or update password) - sys.stderr.write(f"Updating user {target_user}...\n") - resp = rustfs_admin_request("PUT", "/rustfs/admin/v3/add-user", params={"accessKey": target_user}, data=payload) - - if resp.status_code != 200: - sys.stderr.write(f"Warning: Failed to create/update user via RustFS API: {resp.status_code} {resp.text}\n") - return None, None - - # 2. Set Policy (Assign 'readwrite' canned policy) - # Note: Ideally we should create a custom policy restricted to the bucket, - # but for now we use 'readwrite' to ensure functionality. - sys.stderr.write(f"Setting policy for user {target_user}...\n") - resp_policy = rustfs_admin_request("PUT", "/rustfs/admin/v3/set-user-or-group-policy", - params={ - "userOrGroup": target_user, - "policyName": "readwrite", - "isGroup": "false" - } - ) - - if resp_policy.status_code != 200: - sys.stderr.write(f"Warning: Failed to set policy for user: {resp_policy.status_code} {resp_policy.text}\n") - # Proceed anyway? Or fail? The user might not work. - # We'll return keys but warn. - - return target_user, new_secret_key - -def encrypt_string(value, var_name): - cmd = ["ansible-vault", "encrypt_string", value, "--name", var_name] - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - raise Exception(f"Failed to encrypt string: {result.stderr}") - return result.stdout - -try: - ensure_bucket() - ak, sk = ensure_user_and_keys() - - if ak and sk and vault_output_file: - # Write to vault file - with open(vault_output_file, "w") as f: - f.write("---\n") - f.write(encrypt_string(ak, "caddy_ha_s3_access_key")) - f.write("\n") - f.write(encrypt_string(sk, "caddy_ha_s3_secret_key")) - f.write("\n") - print(f"Credentials encrypted and written to {vault_output_file}", file=sys.stderr) - elif not ak or not sk: - raise Exception("Failed to generate access keys") - - result = { - "bucket_created": True, - "access_key": ak, - "secret_key": sk, - "used_existing": False - } - - print(json.dumps(result)) - -except Exception as e: - import traceback - traceback.print_exc() - sys.stderr.write(f"Error: {e}\n") - sys.exit(1) - diff --git a/ansible/roles/caddy-ha/handlers/main.yml b/ansible/roles/caddy-ha/handlers/main.yml deleted file mode 100644 index 564894dc..00000000 --- a/ansible/roles/caddy-ha/handlers/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -- name: Restart Keepalived - service: - name: keepalived - state: restarted - -- name: Reload Caddy - service: - name: caddy - state: restarted \ No newline at end of file diff --git a/ansible/roles/caddy-ha/tasks/main.yml b/ansible/roles/caddy-ha/tasks/main.yml deleted file mode 100644 index 8768e1af..00000000 --- a/ansible/roles/caddy-ha/tasks/main.yml +++ /dev/null @@ -1,201 +0,0 @@ ---- -- name: Determine HA State - set_fact: - keepalived_state: "{{ 'MASTER' if inventory_hostname == caddy_ha_primary_host else 'BACKUP' }}" - keepalived_priority: "{{ caddy_ha_priorities[inventory_hostname] | default(80) }}" - -- name: Install dependencies - apt: - pkg: - - debian-keyring - - debian-archive-keyring - - apt-transport-https - - keepalived - - curl - - tar - - libcap2-bin - state: present - update_cache: yes - register: result - -- name: Ensure caddy group exists - group: - name: caddy - system: yes - state: present - -- name: Ensure caddy user exists - user: - name: caddy - group: caddy - system: yes - home: /var/lib/caddy - shell: /usr/sbin/nologin - create_home: yes - state: present - -- name: Download Caddy Custom Binary - get_url: - url: "{{ caddy_ha_caddy_binary_url }}" - dest: /usr/bin/caddy - mode: '0755' - owner: root - group: root - notify: Reload Caddy - register: caddy_download - ignore_errors: true - -- name: Build Caddy with xcaddy (Fallback) - when: caddy_download is failed - block: - - name: Install Build Dependencies (git, curl) - apt: - pkg: - - curl - - git - - tar - state: present - update_cache: yes - - - name: Download Go - get_url: - url: "https://go.dev/dl/go1.24.0.linux-amd64.tar.gz" - dest: "/tmp/go1.24.0.linux-amd64.tar.gz" - - - name: Remove existing Go installation - file: - path: /usr/local/go - state: absent - - - name: Extract Go - unarchive: - src: "/tmp/go1.24.0.linux-amd64.tar.gz" - dest: "/usr/local" - remote_src: yes - - - name: Link Go binary - file: - src: /usr/local/go/bin/go - dest: /usr/bin/go - state: link - force: yes - - - name: Install xcaddy (Add GPG key) - get_url: - url: https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key - dest: /usr/share/keyrings/caddy-xcaddy-archive-keyring.asc - mode: '0644' - - - name: De-armor GPG key - shell: cat /usr/share/keyrings/caddy-xcaddy-archive-keyring.asc | gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg - args: - creates: /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg - - - name: Install xcaddy (Add Repository) - copy: - content: "deb [signed-by=/usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/xcaddy/deb/debian any-version main" - dest: /etc/apt/sources.list.d/caddy-xcaddy.list - - - name: Install xcaddy - apt: - name: xcaddy - state: present - update_cache: yes - - - name: Build Caddy - command: - cmd: > - xcaddy build {{ caddy_version }} - --output /usr/bin/caddy - --with github.com/caddy-dns/cloudflare - --with github.com/ss098/certmagic-s3 - --with github.com/hslatman/caddy-crowdsec-bouncer/http@v0.10.0 - --with github.com/hslatman/caddy-crowdsec-bouncer/appsec@v0.10.0 - environment: - GOCACHE: "/tmp/.gocache" - PATH: "/usr/local/go/bin:{{ ansible_env.PATH }}" - register: xcaddy_build - notify: Reload Caddy - -# Compatibility: Allow binding to 80/443 without root -- name: Set capabilities for Caddy - command: setcap cap_net_bind_service=+ep /usr/bin/caddy - when: caddy_download.changed or (xcaddy_build is defined and xcaddy_build.changed) - -- name: Create Caddy config directory - file: - path: /etc/caddy - state: directory - owner: caddy - group: caddy - mode: '0755' - -- name: Ensure Caddy log directory exists - file: - path: /var/log/caddy - state: directory - owner: caddy - group: caddy - mode: '0755' - -- name: Configure Caddyfile - template: - src: Caddyfile.j2 - dest: /etc/caddy/Caddyfile - owner: caddy - group: caddy - mode: '0644' - notify: Reload Caddy - -- name: Install Systemd Service - template: - src: caddy.service.j2 - dest: /etc/systemd/system/caddy.service - owner: root - group: root - mode: '0644' - notify: Reload Caddy - -- name: Setup Caddy Dashboard Generator - template: - src: generate_dashboard.py.j2 - dest: /usr/local/bin/generate_dashboard.py - mode: '0755' - -- name: Setup Dashboard Service - template: - src: caddy-dashboard.service.j2 - dest: /etc/systemd/system/caddy-dashboard.service - mode: '0644' - -- name: Setup Dashboard Timer - template: - src: caddy-dashboard.timer.j2 - dest: /etc/systemd/system/caddy-dashboard.timer - mode: '0644' - -- name: Enable Dashboard Timer - systemd: - name: caddy-dashboard.timer - state: started - enabled: yes - daemon_reload: yes - -- name: Enable and Start Caddy Service - systemd: - name: caddy - state: started - enabled: yes - daemon_reload: yes - -- name: Configure Keepalived - template: - src: keepalived.conf.j2 - dest: /etc/keepalived/keepalived.conf - owner: root - group: root - mode: '0644' - notify: Restart Keepalived - vars: - keepalived_vip: "{{ caddy_ha_vip }}" - keepalived_auth_pass: "{{ caddy_ha_keepalived_auth_pass }}" diff --git a/ansible/roles/caddy-ha/templates/Caddyfile.j2 b/ansible/roles/caddy-ha/templates/Caddyfile.j2 deleted file mode 100644 index ddc57e09..00000000 --- a/ansible/roles/caddy-ha/templates/Caddyfile.j2 +++ /dev/null @@ -1,395 +0,0 @@ -# The Caddyfile is an easy way to configure your Caddy web server. -# -# Unless the file starts with a global options block, the first -# uncommented line is always the address of your site. -# -# To use your own domain name (with automatic HTTPS), first make -# sure your domain's A/AAAA DNS records are properly pointed to -# this machine's public IP, then replace ":80" below with your -# domain name. - -{ -# BEGIN ANSIBLE MANAGED BLOCK - # Admin API for metrics - listen on all interfaces for Prometheus scraping - admin :2019 - - servers { - trusted_proxies static 127.0.0.1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 - metrics - } - - # Layer4 TCP proxy — Gitea SSH on port 2222 - layer4 { - :2222 { - route { - proxy gitea.srv.renner.uno:22 - } - } - } - -{% if crowdsec_enabled | default(true) and crowdsec_caddy_api_key is defined %} - # CrowdSec configuration - crowdsec { - api_url {{ crowdsec_lapi_url }} - api_key {{ crowdsec_caddy_api_key }} - ticker_interval {{ crowdsec_ticker_interval }} -{% if crowdsec_appsec_url is defined %} - appsec_url {{ crowdsec_appsec_url }} -{% endif %} - } - - # Log configuration for CrowdSec analysis - log { - output file /var/log/caddy/access.log { - roll_size 30MiB - roll_keep 5 - } - } -{% endif %} -# END ANSIBLE MANAGED BLOCK - email tls@renner.uno -# acme_dns cloudflare { -# zone_token 8NcEXu8fSEv-gRVnh1kdL_sCh0cRCcUnRLgPGTA0 -# api_token 9Oncv6wU-R85y0FmgPnMQHhrANLiO2osRMNdRNeJ -# } - - default_sni caddy.renner.uno - -} - -# BEGIN ANSIBLE MANAGED BLOCK: Error Page Snippet -(custom_error) { - handle_errors { - rewrite * /custom_error.html - file_server { - root /var/www/html/errors - } - } -} -# END ANSIBLE MANAGED BLOCK: Error Page Snippet - -{% if crowdsec_enabled | default(true) and crowdsec_caddy_api_key is defined %} -# BEGIN ANSIBLE MANAGED BLOCK: CrowdSec Snippet -(crowdsec_protect) { - crowdsec -{% if crowdsec_appsec_url is defined %} - appsec -{% endif %} -} -# END ANSIBLE MANAGED BLOCK: CrowdSec Snippet -{% endif %} - -(cloudflare) { - tls { - dns cloudflare { - zone_token 8NcEXu8fSEv-gRVnh1kdL_sCh0cRCcUnRLgPGTA0 - api_token 9Oncv6wU-R85y0FmgPnMQHhrANLiO2osRMNdRNeJ - } - resolvers 1.1.1.1 - } - -} - -(loadbalancer) { - lb_policy ip_hash - #fail_duration 30s - health_interval 15s - health_timeout 10s -} - - -caddy.nerdcave.eu { - import cloudflare - # Set this path to your site's directory. - root * /usr/share/caddy - - # Enable the static file server. - file_server - - # Another common task is to set up a reverse proxy: - # reverse_proxy localhost:8080 - - # Or serve a PHP site through php-fpm: - # php_fastcgi localhost:9000 -} - -caddy.renner.uno { - import cloudflare - root * /var/www/caddy-dashboard - file_server -} - -# Internal Dashboard Access (Port 80) for Monitoring Script -:80 { - root * /var/www/caddy-dashboard - file_server -} - -pve.nerdcave.eu { - import cloudflare -# import cloudflare - redir https://pve.renner.uno{uri} permanent -} - -pve.renner.uno { - import cloudflare -# import cloudflare - #import authelia - reverse_proxy { - to https://jupiter.renner.uno:8006 https://neptune.renner.uno:8006 https://saturn.renner.uno:8006 - health_port 8006 - #dynamic srv srv+https://_pveweb._tcp.nerdcave.eu { - # resolvers 10.2.40.12 10.2.0.11 - #} - import loadbalancer - } -} - -pbs.renner.uno { - import cloudflare - - reverse_proxy https://pbs.obg.renner.uno:8007 - } - -home.nerdcave.eu { - import cloudflare - log -# import cloudflare - redir https://home.renner.uno{uri} permanent -} - -home.renner.uno { - import cloudflare - reverse_proxy http://10.2.30.11:8123 { - flush_interval -1 - transport http { - keepalive 30s - keepalive_idle_conns 10 - } - } -} - -home.internal.renner.uno { - import cloudflare - reverse_proxy http://10.2.30.11:8123 { - flush_interval -1 - transport http { - keepalive 30s - keepalive_idle_conns 10 - } - } -} - -photos.nerdcave.eu { - import cloudflare - redir https://photos.renner.uno{uri} permanent -} - -photos.renner.uno { - import cloudflare -# import cloudflare - redir /mo/sharing/mGwOwC3ZW https://immich.renner.uno/share/i8gRTJ6Kj0IczirRP6y-BvKcq3zXUnSIEYdtWjlcRJvDaHjStUgBmdKYeU58xZkzTWE - redir /mo/sharing/4qry70DZG https://immich.renner.uno/share/i8gRTJ6Kj0IczirRP6y-BvKcq3zXUnSIEYdtWjlcRJvDaHjStUgBmdKYeU58xZkzTWE - #reverse_proxy https://immich.renner.uno - redir https://immich.renner.uno{uri} permanent -} - -file.nerdcave.eu { - import cloudflare -# import cloudflare - reverse_proxy https://nas.renner.uno:7001 -} -file.renner.uno { - import cloudflare -# import cloudflare - reverse_proxy https://nas.renner.uno:7001 -} - -docs.renner.uno { - import cloudflare - - @local { - remote_ip 10.3.0.0/16 - #vars {tls_client_subject} "" - } - #@trusted { - # vars {tls_client_issuer} "CN=Renner Home Intermediate CA,O=Renner Home" - #} - #import cloudflare - handle @local { - reverse_proxy http://paperless.srv.renner.uno:8000 - } - handle { - route { - # always forward outpost path to actual outpost - reverse_proxy /outpost.goauthentik.io/* http://authentik-app-1.srv.renner.uno:9000 http://authentik-app-2.srv.renner.uno:9000 { - header_up Host {http.reverse_proxy.upstream.hostport} - lb_policy round_robin - } - - # forward authentication to outpost - forward_auth http://authentik-app-1.srv.renner.uno:9000 http://authentik-app-2.srv.renner.uno:9000 { - uri /outpost.goauthentik.io/auth/caddy - - # capitalization of the headers is important, otherwise they will be empty - copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version - - # trust both authentik HA nodes - trusted_proxies 10.2.40.0/24 - } - reverse_proxy http://paperless.srv.renner.uno:8000 - } - } -} - - -influx.renner.uno { - import cloudflare -# import cloudflare - reverse_proxy http://influxdb.srv.renner.uno:8086 -} - -dash.renner.uno { - import cloudflare -# import cloudflare - reverse_proxy http://grafana.srv.renner.uno:3000 -} - - -media.renner.uno { - import cloudflare -# import cloudflare - #reverse_proxy http://10.2.40.15:8096 - reverse_proxy http://nas.renner.uno:8096 -} - -git.renner.uno { - import cloudflare -# import cloudflare - reverse_proxy http://gitea.srv.renner.uno:3000 -} - -auth.renner.uno { - import cloudflare - reverse_proxy http://authentik-app-1.srv.renner.uno:9000 http://authentik-app-2.srv.renner.uno:9000 { - lb_policy round_robin - health_uri /-/health/live/ - health_interval 10s - } -} - -audiobooks.renner.uno { - import cloudflare - reverse_proxy http://audiobookshelf.srv.renner.uno:13378 - } - -wazuh.renner.uno { - import cloudflare - log { - level info - } - reverse_proxy https://wazuh.srv.renner.uno:443 { - transport http { - tls_insecure_skip_verify - } - } - } - - -# Refer to the Caddy docs for more information: -# https://caddyserver.com/docs/caddyfile -# BEGIN ANSIBLE MANAGED BLOCK homeassistant-dev -homeassistant-dev.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://10.2.40.121:8123 -} -# END ANSIBLE MANAGED BLOCK homeassistant-dev - -# BEGIN ANSIBLE MANAGED BLOCK GITEA -gitea.renner.uno { - reverse_proxy gitea.srv.renner.uno:3000 - import cloudflare -} -# END ANSIBLE MANAGED BLOCK GITEA - -# BEGIN ANSIBLE MANAGED BLOCK IMMICH -immich.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://immich.srv.renner.uno:2283 -} -# END ANSIBLE MANAGED BLOCK IMMICH - -ical-proxy.renner.uno { - import cloudflare - reverse_proxy http://ical-proxy.srv.renner.uno:8080 -} - - -# BEGIN ANSIBLE MANAGED BLOCK MUSIC-ASSISTANT -music.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://music-assistant.iot.renner.uno:8095 -} -# END ANSIBLE MANAGED BLOCK MUSIC-ASSISTANT - -# BEGIN ANSIBLE MANAGED BLOCK WIKI -wiki.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://wiki.srv.renner.uno:80 -} -# END ANSIBLE MANAGED BLOCK WIKI - - -# BEGIN ANSIBLE MANAGED BLOCK WOODPECKER -ci.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://woodpecker.srv.renner.uno:8000 -} -# END ANSIBLE MANAGED BLOCK WOODPECKER - -# BEGIN ANSIBLE MANAGED BLOCK OWNCLOUD -owncloud.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://owncloud.srv.renner.uno:9200 -} -# END ANSIBLE MANAGED BLOCK OWNCLOUD - -# BEGIN ANSIBLE MANAGED BLOCK ONLYOFFICE -office.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://owncloud.srv.renner.uno:9980 { - header_down X-Frame-Options "ALLOWALL" - header_down -Content-Security-Policy - } -} -# END ANSIBLE MANAGED BLOCK ONLYOFFICE - -# BEGIN ANSIBLE MANAGED BLOCK GATUS -status.renner.uno { - import cloudflare - import custom_error - reverse_proxy http://gatus.srv.renner.uno:8080 -} -# END ANSIBLE MANAGED BLOCK GATUS - -# BEGIN ANSIBLE MANAGED BLOCK KASM -kasm.renner.uno { - import cloudflare - import custom_error - reverse_proxy https://kasm.srv.renner.uno:443 { - transport http { - tls_insecure_skip_verify - keepalive 30s - } - flush_interval -1 - } -} -# END ANSIBLE MANAGED BLOCK KASM - diff --git a/ansible/roles/caddy-ha/templates/caddy-dashboard.service.j2 b/ansible/roles/caddy-ha/templates/caddy-dashboard.service.j2 deleted file mode 100644 index 9b47dd15..00000000 --- a/ansible/roles/caddy-ha/templates/caddy-dashboard.service.j2 +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Generate Caddy HA Dashboard -After=network.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/python3 /usr/local/bin/generate_dashboard.py -User=root -Group=root - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/caddy-ha/templates/caddy-dashboard.timer.j2 b/ansible/roles/caddy-ha/templates/caddy-dashboard.timer.j2 deleted file mode 100644 index 95a25086..00000000 --- a/ansible/roles/caddy-ha/templates/caddy-dashboard.timer.j2 +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Run Caddy Dashboard Generator every 5 seconds - -[Timer] -OnBootSec=10s -OnUnitActiveSec=5s - -[Install] -WantedBy=timers.target diff --git a/ansible/roles/caddy-ha/templates/caddy.service.j2 b/ansible/roles/caddy-ha/templates/caddy.service.j2 deleted file mode 100644 index 1292e12d..00000000 --- a/ansible/roles/caddy-ha/templates/caddy.service.j2 +++ /dev/null @@ -1,35 +0,0 @@ -# caddy.service -# -# For using Caddy with a config file. -# -# Make sure the ExecStart and ExecReload commands are correct -# for your installation. -# -# See https://caddyserver.com/docs/install for instructions. -# -# WARNING: This service does not use the --resume flag, so if you -# use the API to make changes, they will be overwritten by the -# Caddyfile next time the service is restarted. If you intend to -# use Caddy's API to configure it, add the --resume flag to the -# `caddy run` command or use the caddy-api.service file instead. - -[Unit] -Description=Caddy -Documentation=https://caddyserver.com/docs/ -After=network.target network-online.target -Requires=network-online.target - -[Service] -Type=notify -User=caddy -Group=caddy -ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile -ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force -TimeoutStopSec=5s -LimitNOFILE=1048576 -PrivateTmp=true -ProtectSystem=full -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/caddy-ha/templates/generate_dashboard.py.j2 b/ansible/roles/caddy-ha/templates/generate_dashboard.py.j2 deleted file mode 100644 index 0fb5c2b0..00000000 --- a/ansible/roles/caddy-ha/templates/generate_dashboard.py.j2 +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python3 -import subprocess -import socket -import datetime -import os -import sys -import ssl -import urllib.request - -# Constants -HOSTNAME = socket.gethostname() -VIP_IP = "10.2.45.100" # Adjust if your VIP is different -OUTPUT_FILE = "/var/www/caddy-dashboard/index.html" -NODES = [ - {% for host in groups['proxmox_caddy_ha'] %} - { - "name": "{{ hostvars[host].ansible_host }} ({{ host }})", - "host": "{{ hostvars[host].ansible_host }}" - }, - {% endfor %} -] - -{% raw %} -def check_remote_node_status(host): - """Checks the status of a remote node by fetching its dashboard.""" - try: - # Use HTTP on port 80 to bypass TLS/SNI issues - url = f"http://{host}" - req = urllib.request.Request(url) - - with urllib.request.urlopen(req, timeout=1) as response: - html = response.read().decode('utf-8') - if "MASTER (Active Node)" in html: - return "Online (MASTER)" - elif "BACKUP (Standby Node)" in html: - return "Online (BACKUP)" - else: - return "Online" - except Exception: - return "Offline" - -def get_service_status(service): - """Checks if a systemd service is active.""" - try: - subprocess.check_call(["systemctl", "is-active", "--quiet", service]) - return "Active" - except subprocess.CalledProcessError: - return "Inactive" - except FileNotFoundError: - return "Not Installed" - -def get_keepalived_role(): - """Checks if the VIP is present on any interface.""" - try: - output = subprocess.check_output(["ip", "addr", "show"]).decode() - if VIP_IP in output: - return "MASTER (Active Node)" - return "BACKUP (Standby Node)" - except Exception: - return "Unknown" - -def generate_html(): - caddy_status = get_service_status("caddy") - keepalived_status = get_service_status("keepalived") - node_role = get_keepalived_role() - last_updated = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Generate Node Status List - nodes_html = "" - for node in NODES: - status = check_remote_node_status(node["host"]) - - status_class = "inactive" - if "Online" in status: - status_class = "active" - if "MASTER" in status: - status_class = "master" - elif "BACKUP" in status: - status_class = "backup" - - nodes_html += f""" -