From d71caea7d07a9b3490dda0afc34284c8ececa554 Mon Sep 17 00:00:00 2001 From: Lino Silva Date: Tue, 31 Mar 2026 10:31:31 +0100 Subject: [PATCH] feat: Let's encrypt, pocketid --- .../inventories/group_vars/all/app_disks.yml | 13 ++++ ansible/inventories/group_vars/all/main.yml | 8 ++- ansible/inventories/group_vars/all/vault.yml | 25 ++++--- ansible/inventories/group_vars/all/vms.yml | 19 ++--- ansible/playbooks/provision_vms.yml | 7 ++ ansible/roles/pocketid/tasks/main.yml | 69 +++++++++++++++++++ .../pocketid/templates/docker-compose.yml.j2 | 22 ++++++ ansible/roles/traefik/tasks/main.yml | 18 ++++- .../traefik/templates/docker-compose.yml.j2 | 13 +++- .../traefik/templates/remote-services.yml.j2 | 16 +++++ .../roles/traefik/templates/traefik.yml.j2 | 5 ++ 11 files changed, 190 insertions(+), 25 deletions(-) create mode 100644 ansible/inventories/group_vars/all/app_disks.yml create mode 100644 ansible/roles/pocketid/tasks/main.yml create mode 100644 ansible/roles/pocketid/templates/docker-compose.yml.j2 create mode 100644 ansible/roles/traefik/templates/remote-services.yml.j2 diff --git a/ansible/inventories/group_vars/all/app_disks.yml b/ansible/inventories/group_vars/all/app_disks.yml new file mode 100644 index 0000000..162cb27 --- /dev/null +++ b/ansible/inventories/group_vars/all/app_disks.yml @@ -0,0 +1,13 @@ +# Application-specific data disks +# Each app can have its own dedicated disk for data persistence and easy backups + +app_data_disks: + pocketid: + vm: infra-core-1 + vmid: 410 + node: purah + size: "10" + storage: purah-mirror-860gb + disk_id: scsi1 + mount_point: /data/pocket-id + device: /dev/sdb diff --git a/ansible/inventories/group_vars/all/main.yml b/ansible/inventories/group_vars/all/main.yml index 8004467..6a9d0fc 100644 --- a/ansible/inventories/group_vars/all/main.yml +++ b/ansible/inventories/group_vars/all/main.yml @@ -13,5 +13,9 @@ docker_packages: - docker-compose # Traefik configuration -domain: "example.com" # Change to your domain -letsencrypt_email: "admin@example.com" # Change to your email +domain: "lino.cooking" +letsencrypt_email: "letsencrypt@lino.cooking" +cloudflare_api_token: "{{ vault_cloudflare_api_token }}" + +# Pocket ID configuration +pocketid_encryption_key: "{{ vault_pocketid_encryption_key }}" diff --git a/ansible/inventories/group_vars/all/vault.yml b/ansible/inventories/group_vars/all/vault.yml index 8add006..f9e34fb 100644 --- a/ansible/inventories/group_vars/all/vault.yml +++ b/ansible/inventories/group_vars/all/vault.yml @@ -1,9 +1,18 @@ $ANSIBLE_VAULT;1.1;AES256 -30633439346434353439346639633764626635653563666538653835633838643731666435303334 -3661326333363964633038303533316334363830303236350a393538306461356533636565353031 -30643036363662376661656462386235383438623533303139343037616436666161653530376639 -3063343131616333620a336430323062383738663130623139323633613035643539333565663730 -30613964353338653663373234616365303165306166373034633264303235366433396130616435 -66636161373639393166386331346639666361316237353965373562643761613064666566343436 -65353164316262313234653764353837643763363132383935323231376538383933316563326537 -35343235336163653435 +30623838356139653230313437646430626633636134313435643139663435393536616662633161 +3439346361646265653936616664653839373935333238350a643737393965313736316434356536 +34323934386661303032306431306533356335633339633165366530323464336330343862313236 +3366373465393263300a353033623763396561646133363666326232306239656662326137386135 +62623530656162303137326433353431653334343731333934316263316563343164383263386331 +31643766343934386162336335316431343766666361306333396163383863363866346536366436 +35303839333038653635353336343436663130666638656664643639633238613030656533386264 +64643063663934303062376261313535636230376264386265616531323131636130363962646665 +30303430366530356438626231613630393739656162383435643539366530313162393963613530 +39663533666337393238386437633036336332333738666234353261333932366537303936663432 +33383062303362313463333935336430303032363237373836666430343664316638363336656662 +32383962353638616433326466393239396364643533373761326461383365663163323861353533 +33646564373363656632333935333330356561393733663133393161363763313330626263663639 +66653065323432303931313461623764366135613233666131353335373431623032386264623962 +39653963353234623265376432656137656633613636363535616166323438313030383539343330 +64633963666634636666313335356130613762643863653761366661663063613465386530363738 +6537 diff --git a/ansible/inventories/group_vars/all/vms.yml b/ansible/inventories/group_vars/all/vms.yml index 725f771..0bd1a85 100644 --- a/ansible/inventories/group_vars/all/vms.yml +++ b/ansible/inventories/group_vars/all/vms.yml @@ -1,13 +1,14 @@ vms: - # infra-core-1: - # vmid: 410 - # node: purah - # cores: 4 - # memory: 8192 - # disk: 50G - # ip: 10.0.4.10 - # network_bridge: "vmbr0" - # storage: purah-mirror-860gb + infra-core-1: + vmid: 410 + node: purah + template_vmid: 9000 + cores: 4 + memory: 8192 + disk: 50G + ip: 10.0.4.10 + network_bridge: "vmbr0" + storage: purah-mirror-860gb # media-1: # vmid: 420 diff --git a/ansible/playbooks/provision_vms.yml b/ansible/playbooks/provision_vms.yml index be60090..0036b96 100644 --- a/ansible/playbooks/provision_vms.yml +++ b/ansible/playbooks/provision_vms.yml @@ -12,6 +12,13 @@ - keepalived - traefik +- hosts: purah + become: yes + roles: + - base + - docker + - pocketid + # - hosts: all # become: yes # roles: diff --git a/ansible/roles/pocketid/tasks/main.yml b/ansible/roles/pocketid/tasks/main.yml new file mode 100644 index 0000000..99619eb --- /dev/null +++ b/ansible/roles/pocketid/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: Add data disk to VM for pocketid + community.proxmox.proxmox_disk: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + vmid: "{{ app_data_disks.pocketid.vmid }}" + disk: "{{ app_data_disks.pocketid.disk_id }}" + storage: "{{ app_data_disks.pocketid.storage }}" + size: "{{ app_data_disks.pocketid.size }}" + state: present + delegate_to: localhost + become: no + run_once: true + ignore_errors: yes + register: disk_result + +- name: Display disk creation result + debug: + var: disk_result + +- name: Wait for data disk to be available + wait_for: + path: "{{ app_data_disks.pocketid.device }}" + state: present + timeout: 30 + +- name: Check if data disk is formatted + command: "blkid {{ app_data_disks.pocketid.device }}" + register: disk_formatted + failed_when: false + changed_when: false + +- name: Format data disk with ext4 + filesystem: + fstype: ext4 + dev: "{{ app_data_disks.pocketid.device }}" + when: disk_formatted.rc != 0 + +- name: Create pocketid data mount point + file: + path: "{{ app_data_disks.pocketid.mount_point }}" + state: directory + mode: '0755' + +- name: Mount data disk + mount: + path: "{{ app_data_disks.pocketid.mount_point }}" + src: "{{ app_data_disks.pocketid.device }}" + fstype: ext4 + state: mounted + opts: defaults + +- name: Create pocketid directory + file: + path: /opt/pocketid + state: directory + mode: '0755' + +- name: Create docker-compose file for Pocket ID + template: + src: docker-compose.yml.j2 + dest: /opt/pocketid/docker-compose.yml + mode: '0644' +- name: Start Pocket ID + shell: cd /opt/pocketid && docker compose up -d + args: + chdir: /opt/pocketid diff --git a/ansible/roles/pocketid/templates/docker-compose.yml.j2 b/ansible/roles/pocketid/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..2d20a7a --- /dev/null +++ b/ansible/roles/pocketid/templates/docker-compose.yml.j2 @@ -0,0 +1,22 @@ +services: + pocketid: + image: ghcr.io/pocket-id/pocket-id:v2 + container_name: pocketid + restart: unless-stopped + ports: + - "8001:1411" + environment: + - APP_URL=https://auth.{{ domain }} + - TRUST_PROXY=true + - DATABASE_URL=file:/app/data/pocket-id.db + - ENCRYPTION_KEY={{ pocketid_encryption_key }} + - LOG_LEVEL=debug + - SESSION_DURATION=63072000 # 2 years + volumes: + - /data/pocket-id:/app/data + healthcheck: + test: [ "CMD", "/app/pocket-id", "healthcheck" ] + interval: 1m30s + timeout: 5s + retries: 2 + start_period: 10s diff --git a/ansible/roles/traefik/tasks/main.yml b/ansible/roles/traefik/tasks/main.yml index bc21d1f..addcc38 100644 --- a/ansible/roles/traefik/tasks/main.yml +++ b/ansible/roles/traefik/tasks/main.yml @@ -11,6 +11,12 @@ state: directory mode: '0755' +- name: Create traefik dynamic config directory + file: + path: /opt/traefik/data/dynamic + state: directory + mode: '0755' + - name: Create proxy network docker_network: name: proxy @@ -27,6 +33,12 @@ dest: /opt/traefik/data/traefik.yml mode: '0644' +- name: Create remote services configuration + template: + src: remote-services.yml.j2 + dest: /opt/traefik/data/dynamic/remote-services.yml + mode: '0644' + - name: Create docker-compose file template: src: docker-compose.yml.j2 @@ -34,6 +46,6 @@ mode: '0644' - name: Start Traefik - community.docker.docker_compose_v2: - project_src: /opt/traefik - state: present + shell: cd /opt/traefik && docker compose up -d + args: + chdir: /opt/traefik diff --git a/ansible/roles/traefik/templates/docker-compose.yml.j2 b/ansible/roles/traefik/templates/docker-compose.yml.j2 index 1332f0b..e2facd2 100644 --- a/ansible/roles/traefik/templates/docker-compose.yml.j2 +++ b/ansible/roles/traefik/templates/docker-compose.yml.j2 @@ -1,5 +1,3 @@ -version: '3.8' - services: traefik: image: traefik:v3.0 @@ -13,23 +11,32 @@ services: - "80:80" - "443:443" - "8080:8080" # Dashboard + environment: + - CF_DNS_API_TOKEN={{ cloudflare_api_token | default('') }} volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./data/traefik.yml:/traefik.yml:ro + - ./data/dynamic:/etc/traefik/dynamic:ro - ./data/acme.json:/acme.json labels: - "traefik.enable=true" + # HTTP to HTTPS redirect - "traefik.http.routers.traefik.entrypoints=http" - "traefik.http.routers.traefik.rule=Host(`traefik.{{ domain | default('local') }}`)" - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https" - - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https" - "traefik.http.routers.traefik.middlewares=traefik-https-redirect" + # HTTPS with auth - "traefik.http.routers.traefik-secure.entrypoints=https" - "traefik.http.routers.traefik-secure.rule=Host(`traefik.{{ domain | default('local') }}`)" - "traefik.http.routers.traefik-secure.tls=true" - "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare" - "traefik.http.routers.traefik-secure.service=api@internal" + # ForwardAuth middleware pointing to Pocket ID + - "traefik.http.middlewares.pocketid-auth.forwardauth.address=http://auth.{{ domain }}/api/oidc/authorize?client_id=traefik&redirect_uri=https://traefik.{{ domain }}/callback" + - "traefik.http.middlewares.pocketid-auth.forwardauth.trustForwardHeader=true" + - "traefik.http.middlewares.pocketid-auth.forwardauth.authResponseHeaders=X-Forwarded-User" + - "traefik.http.routers.traefik-secure.middlewares=pocketid-auth" networks: proxy: diff --git a/ansible/roles/traefik/templates/remote-services.yml.j2 b/ansible/roles/traefik/templates/remote-services.yml.j2 new file mode 100644 index 0000000..b7b26ed --- /dev/null +++ b/ansible/roles/traefik/templates/remote-services.yml.j2 @@ -0,0 +1,16 @@ +http: + routers: + pocketid: + rule: "Host(`auth.{{ domain }}`)" + entryPoints: + - https + service: pocketid + tls: + certResolver: cloudflare + + services: + pocketid: + loadBalancer: + passHostHeader: true + servers: + - url: "http://10.0.4.10:8001" diff --git a/ansible/roles/traefik/templates/traefik.yml.j2 b/ansible/roles/traefik/templates/traefik.yml.j2 index 6442b66..2824369 100644 --- a/ansible/roles/traefik/templates/traefik.yml.j2 +++ b/ansible/roles/traefik/templates/traefik.yml.j2 @@ -13,6 +13,8 @@ entryPoints: scheme: https https: address: ":443" + forwardedHeaders: + insecure: true traefik: address: ":8080" @@ -23,6 +25,9 @@ providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false + file: + directory: /etc/traefik/dynamic + watch: true certificatesResolvers: cloudflare: