Compare commits

...
7 Commits
Author SHA1 Message Date
govandClaude Opus 5.5 e691cabf6f fix: run cities labeler from its release image
Deploy PDS / deploy (push) Canceled after 0s
Pull git.cosocial.info/cosocial-canada/atproto-cosocial-labeler instead of
cloning and building on the server, which took several minutes per deploy.
Watchtower picks up new :latest releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:37:32 -05:00
gov f998954be9 feat: deploy cities labeler 2026-11-04 16:55:41 -05:00
gov 98121c0195 docs: instructions for enrolling a passkey
Deploy PDS / deploy (push) Canceled after 0s
2026-09-20 18:48:19 -04:00
gov c6122b9507 fix: host mapping for pds operator to make reqs outside docker
Deploy PDS / deploy (push) Canceled after 0s
2026-09-20 18:40:01 -04:00
gov fa3de78336 fix: less opinionated pds operator hostname
Deploy PDS / deploy (push) Canceled after 0s
2026-09-20 18:17:21 -04:00
Boris Mann c11ce119d4 Adding smtp url as a secret and sending from techops @ cosocial.ca
Deploy PDS / deploy (push) Canceled after 0s
2026-09-20 10:51:16 -07:00
gov 6428560093 Merge pull request 'feat: pds operator' (#1) from gov/pds-operator into main
Deploy PDS / deploy (push) Canceled after 0s
Reviewed-on: #1
2026-09-18 20:10:08 +00:00
9 changed files with 134 additions and 4 deletions

No files matched your search

+14
View File
@@ -56,6 +56,20 @@ jobs:
if [ -n "${{ vars.PDS_PRIMARY_COLOR }}" ]; then if [ -n "${{ vars.PDS_PRIMARY_COLOR }}" ]; then
echo "pds_primary_color: \"${{ vars.PDS_PRIMARY_COLOR }}\"" >> vars/main.yml echo "pds_primary_color: \"${{ vars.PDS_PRIMARY_COLOR }}\"" >> vars/main.yml
fi fi
# Optional: email/SMTP
if [ -n "${{ secrets.SMTP_URL }}" ]; then
echo "pds_email_smtp_url: ${{ secrets.SMTP_URL }}" >> vars/main.yml
fi
echo "pds_email_from_address: techops@cosocial.ca" >> vars/main.yml
# Optional: cities labeler
if [ -n "${{ vars.PDS_CITIES_HOSTNAME }}" ]; then
cat >> vars/main.yml <<EOF
pds_cities_enabled: true
pds_cities_hostname: ${{ vars.PDS_CITIES_HOSTNAME }}
pds_cities_app_password: ${{ secrets.PDS_CITIES_APP_PASSWORD }}
pds_cities_signing_key: ${{ secrets.PDS_CITIES_SIGNING_KEY }}
EOF
fi
- name: Run playbook - name: Run playbook
run: | run: |
+26
View File
@@ -107,6 +107,19 @@ Requires a recent PDS image (telemetry added in `@atproto/pds` 0.5.26, distro ta
| `pds_telemetry_enabled` | `false` | Enable OpenTelemetry + monitoring stack | | `pds_telemetry_enabled` | `false` | Enable OpenTelemetry + monitoring stack |
| `pds_grafana_image_tag` | `13.1.1` | Grafana Docker image tag (pinned — no floating major tag) | | `pds_grafana_image_tag` | `13.1.1` | Grafana Docker image tag (pinned — no floating major tag) |
### Cities labeler
Optional. Runs the release image of [atproto-cosocial-labeler](https://git.cosocial.info/CoSocial-Canada/atproto-cosocial-labeler) (city badges requested over Bluesky DMs) from `/pds/cities`, behind Caddy at `pds_cities_hostname`. Its label database lives in `cities/data`, so it's included in PDS backups. Watchtower pulls new `:latest` releases hourly, like the PDS image.
| Variable | Required | Default | Description |
|---|---|---|---|
| `pds_cities_enabled` | no | — | Deploy the cities labeler |
| `pds_cities_image` | no | `git.cosocial.info/cosocial-canada/atproto-cosocial-labeler:latest` | Labeler image; pin a version tag to opt out of auto-updates |
| `pds_cities_hostname` | yes (if enabled) | — | Public DNS name for the labeler service (needs an A record) |
| `pds_cities_handle` | no | `cities.<pds_hostname>` | Labeler account handle |
| `pds_cities_app_password` | yes (if enabled) | — | Labeler account app password with DM access |
| `pds_cities_signing_key` | yes (if enabled) | — | Label signing key (hex); its public key must match the labeler's DID document |
### Backup service ### Backup service
| Variable | Required | Default | Description | | Variable | Required | Default | Description |
@@ -168,6 +181,13 @@ Create an account:
pdsadmin account create pdsadmin account create
``` ```
Enroll an operator (admin dashboard) passkey — prints a single-use link, expires in 15 minutes; open it on the operator hostname over HTTPS (passkeys are per-hostname and need a secure context). Rerun to enroll another device or recover after losing all passkeys:
```bash
cd /pds/operator
sudo docker compose exec app node dist/cli.js enroll
```
## Teardown ## Teardown
Run on the server to remove everything, use with caution: Run on the server to remove everything, use with caution:
@@ -200,6 +220,9 @@ Auto-deploys on push to `main`. Set up these in Gitea repo settings (Settings >
| `PDS_TELEMETRY_ENABLED` | *(optional)* Set to `true` to enable telemetry + monitoring stack | | `PDS_TELEMETRY_ENABLED` | *(optional)* Set to `true` to enable telemetry + monitoring stack |
| `PDS_SERVICE_NAME` | *(optional)* PDS display name for branding | | `PDS_SERVICE_NAME` | *(optional)* PDS display name for branding |
| `PDS_PRIMARY_COLOR` | *(optional)* Primary color (hex) for branding | | `PDS_PRIMARY_COLOR` | *(optional)* Primary color (hex) for branding |
| `PDS_CITIES_HOSTNAME` | *(optional)* Set to deploy the cities labeler at this hostname |
Outgoing emails are sent from `techops@cosocial.ca` (fixed in the workflow).
**Secrets** (sensitive credentials): **Secrets** (sensitive credentials):
@@ -208,5 +231,8 @@ Auto-deploys on push to `main`. Set up these in Gitea repo settings (Settings >
| `SSH_PRIVATE_KEY` | Private SSH key for the `sysadmin` user on the target server | | `SSH_PRIVATE_KEY` | Private SSH key for the `sysadmin` user on the target server |
| `OVH_S3_ACCESS_KEY` | OVH S3 access key for blob storage | | `OVH_S3_ACCESS_KEY` | OVH S3 access key for blob storage |
| `OVH_S3_SECRET_KEY` | OVH S3 secret key for blob storage | | `OVH_S3_SECRET_KEY` | OVH S3 secret key for blob storage |
| `SMTP_URL` | *(optional)* SMTP URL with credentials (e.g., `smtps://user:pass@smtp.example.com/`) |
| `PDS_CITIES_APP_PASSWORD` | *(with cities)* Labeler account app password with DM access |
| `PDS_CITIES_SIGNING_KEY` | *(with cities)* Label signing key (hex) |
The workflow installs Ansible, writes the SSH key from a secret, generates `inventory.yml` and `vars/main.yml` from vars and secrets, and runs the playbook. The workflow installs Ansible, writes the SSH key from a secret, generates `inventory.yml` and `vars/main.yml` from vars and secrets, and runs the playbook.
+39 -2
View File
@@ -192,6 +192,31 @@
when: pds_operator_enabled is defined and pds_operator_enabled | bool when: pds_operator_enabled is defined and pds_operator_enabled | bool
notify: restart operator notify: restart operator
- name: Create cities labeler data directory
file:
path: "{{ pds_data_dir }}/cities/data"
state: directory
owner: "1000"
group: "1000"
mode: "0755"
when: pds_cities_enabled is defined and pds_cities_enabled | bool
- name: Deploy cities labeler .env
template:
src: templates/cities-labeler.env.j2
dest: "{{ pds_data_dir }}/cities/.env"
mode: "0600"
when: pds_cities_enabled is defined and pds_cities_enabled | bool
notify: restart cities
- name: Deploy cities labeler compose
template:
src: templates/cities-labeler-compose.yaml.j2
dest: "{{ pds_data_dir }}/cities/compose.yaml"
mode: "0644"
when: pds_cities_enabled is defined and pds_cities_enabled | bool
notify: restart cities
- name: Create monitoring directories - name: Create monitoring directories
file: file:
path: "{{ item }}" path: "{{ item }}"
@@ -248,18 +273,27 @@
shell: cd {{ pds_data_dir }}/monitoring && docker compose up -d shell: cd {{ pds_data_dir }}/monitoring && docker compose up -d
when: pds_telemetry_enabled is defined and pds_telemetry_enabled | bool when: pds_telemetry_enabled is defined and pds_telemetry_enabled | bool
- name: Flush handlers (operator + monitoring) - name: Flush handlers (operator + cities + monitoring)
meta: flush_handlers meta: flush_handlers
- name: Operator health check - name: Operator health check
uri: uri:
url: "https://operator.{{ pds_hostname }}/oauth/client-metadata.json" url: "https://{{ pds_operator_hostname }}/oauth/client-metadata.json"
method: GET method: GET
status_code: 200 status_code: 200
retries: 5 retries: 5
delay: 10 delay: 10
when: pds_operator_enabled is defined and pds_operator_enabled | bool when: pds_operator_enabled is defined and pds_operator_enabled | bool
- name: Cities labeler health check
uri:
url: "https://{{ pds_cities_hostname }}/xrpc/_health"
method: GET
status_code: 200
retries: 5
delay: 10
when: pds_cities_enabled is defined and pds_cities_enabled | bool
handlers: handlers:
- name: apply changes - name: apply changes
shell: cd {{ pds_data_dir }} && docker compose up -d && docker restart caddy shell: cd {{ pds_data_dir }} && docker compose up -d && docker restart caddy
@@ -269,3 +303,6 @@
- name: restart operator - name: restart operator
shell: cd {{ pds_data_dir }}/operator && docker compose up -d --build shell: cd {{ pds_data_dir }}/operator && docker compose up -d --build
- name: restart cities
shell: cd {{ pds_data_dir }}/cities && docker compose pull && docker compose up -d
+10 -1
View File
@@ -16,7 +16,7 @@
reverse_proxy http://localhost:3000 reverse_proxy http://localhost:3000
} }
{% if pds_operator_enabled is defined and pds_operator_enabled | bool %} {% if pds_operator_enabled is defined and pds_operator_enabled | bool %}
operator.{{ pds_hostname }} { {{ pds_operator_hostname }} {
log { log {
output stdout output stdout
format json format json
@@ -24,3 +24,12 @@ operator.{{ pds_hostname }} {
reverse_proxy http://localhost:8787 reverse_proxy http://localhost:8787
} }
{% endif %} {% endif %}
{% if pds_cities_enabled is defined and pds_cities_enabled | bool %}
{{ pds_cities_hostname }} {
log {
output stdout
format json
}
reverse_proxy http://localhost:4100
}
{% endif %}
+27
View File
@@ -0,0 +1,27 @@
name: cities-labeler
# Cities labeler — deployed by Ansible to
# {{ pds_data_dir }}/cities/compose.yaml
services:
app:
image: {{ pds_cities_image | default('git.cosocial.info/cosocial-canada/atproto-cosocial-labeler:latest') }}
env_file: .env
ports:
- "127.0.0.1:4100:4100"
# the host's /etc/hosts maps the PDS hostname to 127.0.1.1 (cloud-init
# manage_etc_hosts), which Docker propagates to container DNS — pin the
# real address so the labeler can log in to the PDS
extra_hosts:
- "{{ pds_hostname }}:{{ hostvars[inventory_hostname].ansible_default_ipv4.address }}"
volumes:
# bind mount keeps the label database under /pds — captured by the backup tarball
- ./data:/data
# runs unprivileged; ./data must be owned by uid 1000
user: "1000:1000"
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
restart: unless-stopped
+3
View File
@@ -0,0 +1,3 @@
LABELER_HANDLE={{ pds_cities_handle | default('cities.' ~ pds_hostname) }}
LABELER_APP_PASSWORD={{ pds_cities_app_password }}
SIGNING_KEY_HEX={{ pds_cities_signing_key }}
+5
View File
@@ -13,6 +13,11 @@ services:
- AUDIT_LOG_PATH=/data/audit.log - AUDIT_LOG_PATH=/data/audit.log
ports: ports:
- "127.0.0.1:8787:8787" - "127.0.0.1:8787:8787"
# the host's /etc/hosts maps the PDS hostname to 127.0.1.1 (cloud-init
# manage_etc_hosts), which Docker propagates to container DNS — pin the
# real address so the operator can reach the PDS
extra_hosts:
- "{{ pds_hostname }}:{{ hostvars[inventory_hostname].ansible_default_ipv4.address }}"
volumes: volumes:
# bind mount keeps data under /pds — captured by the backup tarball # bind mount keeps data under /pds — captured by the backup tarball
- ./data:/data - ./data:/data
+1 -1
View File
@@ -5,4 +5,4 @@ RELAY_HOSTNAME=bsky.network
SESSION_SECRET={{ pds_operator_session_secret }} SESSION_SECRET={{ pds_operator_session_secret }}
PORT=8787 PORT=8787
APPVIEW_URL=https://bsky.app APPVIEW_URL=https://bsky.app
DASHBOARD_URL=https://operator.{{ pds_hostname }} DASHBOARD_URL=https://{{ pds_operator_hostname }}
+9
View File
@@ -55,3 +55,12 @@ pds_crawlers: https://relay.fire.hose.cam # relay/crawler endpoint
pds_operator_enabled: true pds_operator_enabled: true
pds_operator_repo: https://tangled.org/brookie.blog/pds-operator pds_operator_repo: https://tangled.org/brookie.blog/pds-operator
pds_operator_version: main pds_operator_version: main
pds_operator_hostname: pds-operator.cosocial.ca
# Cities labeler (DM-driven city badges, serves labels at pds_cities_hostname)
# pds_cities_enabled: true
# pds_cities_image: git.cosocial.info/cosocial-canada/atproto-cosocial-labeler:latest
# pds_cities_hostname: cities-labeler.cosocial.ca
# pds_cities_handle: cities.pds.cosocial.ca
# pds_cities_app_password: CHANGE_ME # app password with DM access
# pds_cities_signing_key: CHANGE_ME # SIGNING_KEY_HEX; its public key is in the labeler's DID doc