From 80e02731dbd05edc937af9935dd5ece418cf3537 Mon Sep 17 00:00:00 2001 From: Jonathan DeMasi Date: Tue, 30 Dec 2025 18:10:29 -0700 Subject: [PATCH] init pangolin role --- .../host_vars/pangolin.jthan.io/vars.yaml | 7 ++ ansible/roles/pangolin/tasks/main.yaml | 61 ++++++++++++++++++ .../pangolin/tasks/templates/config.yaml.j2 | 18 ++++++ .../tasks/templates/docker-compose-yaml.j2 | 54 ++++++++++++++++ .../tasks/templates/dynamic_config.yaml.j2 | 64 +++++++++++++++++++ .../tasks/templates/traefik_config.yaml.j2 | 47 ++++++++++++++ 6 files changed, 251 insertions(+) create mode 100644 ansible/inventories/production/host_vars/pangolin.jthan.io/vars.yaml create mode 100644 ansible/roles/pangolin/tasks/templates/config.yaml.j2 create mode 100644 ansible/roles/pangolin/tasks/templates/docker-compose-yaml.j2 create mode 100644 ansible/roles/pangolin/tasks/templates/dynamic_config.yaml.j2 create mode 100644 ansible/roles/pangolin/tasks/templates/traefik_config.yaml.j2 diff --git a/ansible/inventories/production/host_vars/pangolin.jthan.io/vars.yaml b/ansible/inventories/production/host_vars/pangolin.jthan.io/vars.yaml new file mode 100644 index 0000000..6a7d2c8 --- /dev/null +++ b/ansible/inventories/production/host_vars/pangolin.jthan.io/vars.yaml @@ -0,0 +1,7 @@ +root_pw: "{{ lookup('bitwarden.secrets.lookup', '279ef4de-8dc7-4e55-a548-b3c400107332') }}" +pangolin_base_domain: "pangolin.jthan.io" +pangolin_cert_email: "me@jthan.io" +pangolin_secret_string: "{{ lookup('bitwarden.secrets.lookup', '30efc9d3-4f98-4b1b-b31b-b3c40010c343') }}" +require_email_verification: false +disable_signup_without_invite: true +diablse_user_create_org: true diff --git a/ansible/roles/pangolin/tasks/main.yaml b/ansible/roles/pangolin/tasks/main.yaml index e69de29..32edbbf 100644 --- a/ansible/roles/pangolin/tasks/main.yaml +++ b/ansible/roles/pangolin/tasks/main.yaml @@ -0,0 +1,61 @@ + + +- name: Create a pangolin group + group: + name: pangolin + state: present + gid: 1051 + +- name: Create a pangolin user + user: + name: pangolin + uid: 1051 + group: 1051 + comment: "pangolin user" + shell: /bin/bash + state: present + create_home: yes + +- name: Install epel + package: + name: epel-release + state: present + +- name: Install podman + package: + name: podman + state: present + +- name: Install podman-compose + package: + name: podman-compose + state: present + +- name: Start and enable podman service + service: + name: podman + state: started + enable: true + +- name: Create pangolin config and logging directories + file: + path: "{{ item }}" + state: directory + mode: 0750 + owner: pangolin + group: pangolin + loop: + - /home/pangolin/config + - /home/pangolin/config/db + - /home/pangolin/config/traefik + - /home/pangolin/config/letsencrypt + - /home/pangolin/config/logs + +- name: Create conf + template: + src: templates/stuff + dest: stuff + owner: pangolin + group: pangolin + mode 0600 + notify: If restart a thing handler. diff --git a/ansible/roles/pangolin/tasks/templates/config.yaml.j2 b/ansible/roles/pangolin/tasks/templates/config.yaml.j2 new file mode 100644 index 0000000..aa6ff55 --- /dev/null +++ b/ansible/roles/pangolin/tasks/templates/config.yaml.j2 @@ -0,0 +1,18 @@ +app: + dashboard_url: "https://{{ pangolin_base_domain }}" + +domains: + domain1: + base_domain: "{{ pangolin_base_domain }}" + cert_resolver: "letsencrypt" + +server: + secret: {{ pangolin_secret_string }} + +gerbil: + base_endpoint: "{{ pangolin_base_domain }}" + +flags: + require_email_verification: {{ require_email_verification }} + disable_signup_without_invite: {{ disable_signup_without_invite }} + disable_user_create_org: {{ disable_user_create_org }} diff --git a/ansible/roles/pangolin/tasks/templates/docker-compose-yaml.j2 b/ansible/roles/pangolin/tasks/templates/docker-compose-yaml.j2 new file mode 100644 index 0000000..fb8da0f --- /dev/null +++ b/ansible/roles/pangolin/tasks/templates/docker-compose-yaml.j2 @@ -0,0 +1,54 @@ +services: + pangolin: + image: fosrl/pangolin:latest # https://github.com/fosrl/pangolin/releases + container_name: pangolin + restart: unless-stopped + volumes: + - ./config:/app/config + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] + interval: "3s" + timeout: "3s" + retries: 15 + + gerbil: + image: fosrl/gerbil:latest # https://github.com/fosrl/gerbil/releases + container_name: gerbil + restart: unless-stopped + depends_on: + pangolin: + condition: service_healthy + command: + - --reachableAt=http://gerbil:3004 + - --generateAndSaveKeyTo=/var/config/key + - --remoteConfig=http://pangolin:3001/api/v1/ + volumes: + - ./config/:/var/config + cap_add: + - NET_ADMIN + - SYS_MODULE + ports: + - 51820:51820/udp + - 21820:21820/udp + - 443:443 # Port for traefik because of the network_mode + - 80:80 # Port for traefik because of the network_mode + + traefik: + image: traefik:v3.4.0 + container_name: traefik + restart: unless-stopped + network_mode: service:gerbil # Ports appear on the gerbil service + depends_on: + pangolin: + condition: service_healthy + command: + - --configFile=/etc/traefik/traefik_config.yml + volumes: + - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration + - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates + - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs + +networks: + default: + driver: bridge + name: pangolin diff --git a/ansible/roles/pangolin/tasks/templates/dynamic_config.yaml.j2 b/ansible/roles/pangolin/tasks/templates/dynamic_config.yaml.j2 new file mode 100644 index 0000000..f1fb8ba --- /dev/null +++ b/ansible/roles/pangolin/tasks/templates/dynamic_config.yaml.j2 @@ -0,0 +1,64 @@ +http: + middlewares: + badger: + plugin: + badger: + disableForwardAuth: true + redirect-to-https: + redirectScheme: + scheme: https + + routers: + # HTTP to HTTPS redirect router + main-app-router-redirect: + rule: "Host(`{{ pangolin_base_domain }}`)" + service: next-service + entryPoints: + - web + middlewares: + - redirect-to-https + - badger + + # Next.js router (handles everything except API and WebSocket paths) + next-router: + rule: "Host(`{{ pangolin_base_domain }}`) && !PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN + service: next-service + entryPoints: + - websecure + middlewares: + - badger + tls: + certResolver: letsencrypt + + # API router (handles /api/v1 paths) + api-router: + rule: "Host(`{{ pangolin_base_domain }}`) && PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN + service: api-service + entryPoints: + - websecure + middlewares: + - badger + tls: + certResolver: letsencrypt + + # WebSocket router + ws-router: + rule: "Host(`{{ pangolin_base_domain }}`)" # REPLACE WITH YOUR DOMAIN + service: api-service + entryPoints: + - websecure + middlewares: + - badger + tls: + certResolver: letsencrypt + + services: + next-service: + loadBalancer: + servers: + - url: "http://pangolin:3002" # Next.js server + + api-service: + loadBalancer: + servers: + - url: "http://pangolin:3000" # API/WebSocket server diff --git a/ansible/roles/pangolin/tasks/templates/traefik_config.yaml.j2 b/ansible/roles/pangolin/tasks/templates/traefik_config.yaml.j2 new file mode 100644 index 0000000..e4ed467 --- /dev/null +++ b/ansible/roles/pangolin/tasks/templates/traefik_config.yaml.j2 @@ -0,0 +1,47 @@ +api: + insecure: true + dashboard: true + +providers: + http: + endpoint: "http://pangolin:3001/api/v1/traefik-config" + pollInterval: "5s" + file: + filename: "/etc/traefik/dynamic_config.yml" + +experimental: + plugins: + badger: + moduleName: "github.com/fosrl/badger" + version: "v1.3.0" + +log: + level: "INFO" + format: "common" + +certificatesResolvers: + letsencrypt: + acme: + httpChallenge: + entryPoint: web + email: {{ pangolin_cert_email }} + storage: "/letsencrypt/acme.json" + caServer: "https://acme-v02.api.letsencrypt.org/directory" + +entryPoints: + web: + address: ":80" + websecure: + address: ":443" + transport: + respondingTimeouts: + readTimeout: "30m" + http: + tls: + certResolver: "letsencrypt" + +serversTransport: + insecureSkipVerify: true + +ping: + entryPoint: "web"