From c40270a51c0e6ef8627f089672a478705f536d35 Mon Sep 17 00:00:00 2001 From: gov Date: Fri, 18 Sep 2026 16:09:30 -0400 Subject: [PATCH] feat: pds operator --- playbook.yml | 74 ++++++++++++++++++++++++-- templates/Caddyfile.j2 | 9 ++++ templates/pds-operator-compose.yaml.j2 | 26 +++++++++ templates/pds-operator.env.j2 | 8 +++ vars/main.yml.example | 5 ++ 5 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 templates/pds-operator-compose.yaml.j2 create mode 100644 templates/pds-operator.env.j2 diff --git a/playbook.yml b/playbook.yml index e84f1ec..13365d4 100644 --- a/playbook.yml +++ b/playbook.yml @@ -137,11 +137,60 @@ changed_when: false failed_when: pds_backup_exit_code.stdout != "0" - # ── Monitoring stack (telemetry) ────────────────────────────── - # Deploys Prometheus + Grafana + node_exporter when pds_telemetry_enabled is true. - # All services bind to 127.0.0.1 — access via SSH tunnel: - # ssh -L 3001:localhost:3001 sysadmin@ - # Then open http://localhost:3001 (admin/admin, change on first login) + - name: Clone pds-operator repo + git: + repo: "{{ pds_operator_repo }}" + dest: "{{ pds_data_dir }}/operator" + version: "{{ pds_operator_version | default('main') }}" + update: yes + force: no + when: pds_operator_enabled is defined and pds_operator_enabled | bool + register: operator_repo + notify: restart operator + + - name: Read operator session secret (if exists) + shell: grep -E '^SESSION_SECRET=' {{ pds_data_dir }}/operator/.env 2>/dev/null | cut -d= -f2 + register: operator_existing_secret + changed_when: false + failed_when: false + when: pds_operator_enabled is defined and pds_operator_enabled | bool + + - name: Generate operator session secret (first run) + shell: openssl rand -hex 32 + register: operator_new_secret + when: + - pds_operator_enabled is defined and pds_operator_enabled | bool + - operator_existing_secret.stdout | default('') | length == 0 + + - name: Set operator session secret fact + set_fact: + pds_operator_session_secret: "{{ operator_existing_secret.stdout if (operator_existing_secret.stdout | default('') | length > 0) else operator_new_secret.stdout }}" + when: pds_operator_enabled is defined and pds_operator_enabled | bool + + - name: Create operator data directory + file: + path: "{{ pds_data_dir }}/operator/data" + state: directory + owner: "1000" + group: "1000" + mode: "0755" + when: pds_operator_enabled is defined and pds_operator_enabled | bool + + - name: Deploy operator .env + template: + src: templates/pds-operator.env.j2 + dest: "{{ pds_data_dir }}/operator/.env" + mode: "0600" + when: pds_operator_enabled is defined and pds_operator_enabled | bool + notify: restart operator + + - name: Deploy operator compose + template: + src: templates/pds-operator-compose.yaml.j2 + dest: "{{ pds_data_dir }}/operator/compose.yaml" + mode: "0644" + when: pds_operator_enabled is defined and pds_operator_enabled | bool + notify: restart operator - name: Create monitoring directories file: @@ -199,9 +248,24 @@ shell: cd {{ pds_data_dir }}/monitoring && docker compose up -d when: pds_telemetry_enabled is defined and pds_telemetry_enabled | bool + - name: Flush handlers (operator + monitoring) + meta: flush_handlers + + - name: Operator health check + uri: + url: "https://operator.{{ pds_hostname }}/oauth/client-metadata.json" + method: GET + status_code: 200 + retries: 5 + delay: 10 + when: pds_operator_enabled is defined and pds_operator_enabled | bool + handlers: - name: apply changes shell: cd {{ pds_data_dir }} && docker compose up -d && docker restart caddy - name: restart monitoring shell: cd {{ pds_data_dir }}/monitoring && docker compose up -d --force-recreate + + - name: restart operator + shell: cd {{ pds_data_dir }}/operator && docker compose up -d --build diff --git a/templates/Caddyfile.j2 b/templates/Caddyfile.j2 index 3fdf99e..1a4b312 100644 --- a/templates/Caddyfile.j2 +++ b/templates/Caddyfile.j2 @@ -15,3 +15,12 @@ } reverse_proxy http://localhost:3000 } +{% if pds_operator_enabled is defined and pds_operator_enabled | bool %} +operator.{{ pds_hostname }} { + log { + output stdout + format json + } + reverse_proxy http://localhost:8787 +} +{% endif %} diff --git a/templates/pds-operator-compose.yaml.j2 b/templates/pds-operator-compose.yaml.j2 new file mode 100644 index 0000000..1b58c1d --- /dev/null +++ b/templates/pds-operator-compose.yaml.j2 @@ -0,0 +1,26 @@ +name: pds-operator + +# Admin dashboard for the PDS — deployed by Ansible to +# {{ pds_data_dir }}/operator/compose.yaml + +services: + app: + build: . + env_file: .env + environment: + - HOST=0.0.0.0 # required for reverse proxy to reach + - DB_PATH=/data/data.sqlite + - AUDIT_LOG_PATH=/data/audit.log + ports: + - "127.0.0.1:8787:8787" + volumes: + # bind mount keeps data under /pds — captured by the backup tarball + - ./data:/data + # runs unprivileged; ./data must be owned by uid 1000 (node) + user: node + read_only: true + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + restart: unless-stopped diff --git a/templates/pds-operator.env.j2 b/templates/pds-operator.env.j2 new file mode 100644 index 0000000..3d1b6db --- /dev/null +++ b/templates/pds-operator.env.j2 @@ -0,0 +1,8 @@ +PDS_HOSTNAME={{ pds_hostname }} +PDS_ADMIN_PASSWORD={{ pds_admin_password }} +PDS_ADMIN_IDENTIFIER= +RELAY_HOSTNAME=bsky.network +SESSION_SECRET={{ pds_operator_session_secret }} +PORT=8787 +APPVIEW_URL=https://bsky.app +DASHBOARD_URL=https://operator.{{ pds_hostname }} diff --git a/vars/main.yml.example b/vars/main.yml.example index 1768cdf..7c3e938 100644 --- a/vars/main.yml.example +++ b/vars/main.yml.example @@ -50,3 +50,8 @@ pds_crawlers: https://relay.fire.hose.cam # relay/crawler endpoint # Telemetry & monitoring (OpenTelemetry + Prometheus + Grafana) # Requires recent PDS image (telemetry added in @atproto/pds 0.5.26, distro tag 0.4.5026+) # pds_telemetry_enabled: true + +# PDS Operator (admin dashboard at operator.) +pds_operator_enabled: true +pds_operator_repo: https://tangled.org/brookie.blog/pds-operator +pds_operator_version: main