diff --git a/group_vars/all/09_ports.yml b/group_vars/all/09_ports.yml index 636049c9..9d4d2173 100644 --- a/group_vars/all/09_ports.yml +++ b/group_vars/all/09_ports.yml @@ -72,6 +72,7 @@ ports: web-svc-logout: 8048 web-app-bookwyrm: 8049 web-app-chess: 8050 + web-app-bluesky_view: 8051 web-app-bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port public: # The following ports should be changed to 22 on the subdomain via stream mapping diff --git a/roles/web-app-bluesky/config/main.yml b/roles/web-app-bluesky/config/main.yml index 289b15ba..72e6ca48 100644 --- a/roles/web-app-bluesky/config/main.yml +++ b/roles/web-app-bluesky/config/main.yml @@ -1,19 +1,38 @@ -images: - pds: "ghcr.io/bluesky-social/pds:latest" -pds: - version: "latest" features: matomo: true css: true desktop: true - central_database: true + central_database: false logout: true server: domains: canonical: web: "bskyweb.{{ PRIMARY_DOMAIN }}" api: "bluesky.{{ PRIMARY_DOMAIN }}" + view: "view.bluesky.{{ PRIMARY_DOMAIN }}" + csp: + whitelist: + connect-src: + - "{{ WEB_PROTOCOL }}://{{ BLUESKY_API_DOMAIN }}" + - https://plc.directory + - https://bsky.social + - https://api.bsky.app + - https://public.api.bsky.app + - https://events.bsky.app + - https://statsigapi.net + - https://ip.bsky.app + - wss://bsky.network + - wss://*.bsky.app docker: services: database: - enabled: true + enabled: false + web: + enabled: true # @see https://github.com/bluesky-social/social-app + view: + enabled: false + pds: + image: "ghcr.io/bluesky-social/pds" + version: "latest" + volumes: + pds_data: "pds_data" \ No newline at end of file diff --git a/roles/web-app-bluesky/schema/main.yml b/roles/web-app-bluesky/schema/main.yml index adf84572..3b83adb7 100644 --- a/roles/web-app-bluesky/schema/main.yml +++ b/roles/web-app-bluesky/schema/main.yml @@ -7,7 +7,3 @@ credentials: description: "PLC rotation key in hex format (32 bytes)" algorithm: "sha256" validation: "^[a-f0-9]{64}$" - admin_password: - description: "Initial admin password for Bluesky PDS" - algorithm: "plain" - validation: "^.{12,}$" \ No newline at end of file diff --git a/roles/web-app-bluesky/tasks/01_pds.yml b/roles/web-app-bluesky/tasks/01_pds.yml new file mode 100644 index 00000000..94c2240d --- /dev/null +++ b/roles/web-app-bluesky/tasks/01_pds.yml @@ -0,0 +1,30 @@ +# The following lines should be removed when the following issue is closed: +# https://github.com/bluesky-social/pds/issues/52 + +- name: Download pdsadmin tarball + get_url: + url: "https://github.com/lhaig/pdsadmin/releases/download/v1.0.0-dev/pdsadmin_Linux_x86_64.tar.gz" + dest: "{{ BLUESKY_PDSADMIN_TMP_TAR }}" + mode: '0644' + notify: + - docker compose up + - docker compose build + +- name: Create {{ BLUESKY_PDSADMIN_DIR }} + file: + path: "{{ BLUESKY_PDSADMIN_DIR }}" + state: directory + mode: '0755' + +- name: Extract pdsadmin tarball + unarchive: + src: "{{ BLUESKY_PDSADMIN_TMP_TAR }}" + dest: "{{ BLUESKY_PDSADMIN_DIR }}" + remote_src: yes + mode: '0755' + +- name: Ensure pdsadmin is executable + file: + path: "{{ BLUESKY_PDSADMIN_FILE }}" + mode: '0755' + state: file diff --git a/roles/web-app-bluesky/tasks/02_social_app.yml b/roles/web-app-bluesky/tasks/02_social_app.yml new file mode 100644 index 00000000..9eab2420 --- /dev/null +++ b/roles/web-app-bluesky/tasks/02_social_app.yml @@ -0,0 +1,8 @@ +- name: clone social app repository + git: + repo: "https://github.com/bluesky-social/social-app.git" + dest: "{{ BLUESKY_SOCIAL_APP_DIR }}" + version: "main" + notify: + - docker compose up + - docker compose build \ No newline at end of file diff --git a/roles/web-app-bluesky/tasks/03_dns.yml b/roles/web-app-bluesky/tasks/03_dns.yml new file mode 100644 index 00000000..11927916 --- /dev/null +++ b/roles/web-app-bluesky/tasks/03_dns.yml @@ -0,0 +1,73 @@ +--- +# Creates Cloudflare DNS records for Bluesky: +# - PDS/API host (A/AAAA) +# - Handle TXT verification (_atproto) +# - Optional Web UI host (A/AAAA) +# - Optional custom AppView host (A/AAAA) +# +# Requirements: +# DNS_PROVIDER == 'cloudflare' +# CLOUDFLARE_API_TOKEN set +# +# Inputs (inventory/vars): +# BLUESKY_API_DOMAIN, BLUESKY_WEB_DOMAIN, BLUESKY_VIEW_DOMAIN +# BLUESKY_WEB_ENABLED (bool), BLUESKY_VIEW_ENABLED (bool) +# PRIMARY_DOMAIN +# networks.internet.ip4 (and optionally networks.internet.ip6) + +- name: "DNS (Cloudflare) for Bluesky – base records" + include_role: + name: sys-dns-cloudflare-records + when: DNS_PROVIDER | lower == 'cloudflare' + vars: + cloudflare_records: + # 1) PDS / API host + - type: A + zone: "{{ BLUESKY_API_DOMAIN | to_zone }}" + name: "{{ BLUESKY_API_DOMAIN }}" + content: "{{ networks.internet.ip4 }}" + proxied: false + + - type: AAAA + zone: "{{ BLUESKY_API_DOMAIN | to_zone }}" + name: "{{ BLUESKY_API_DOMAIN }}" + content: "{{ networks.internet.ip6 | default('') }}" + proxied: false + state: "{{ (networks.internet.ip6 is defined and (networks.internet.ip6 | string) | length > 0) | ternary('present','absent') }}" + + # 2) Handle verification for primary handle (Apex) + - type: TXT + zone: "{{ PRIMARY_DOMAIN | to_zone }}" + name: "_atproto.{{ PRIMARY_DOMAIN }}" + value: "did=did:web:{{ BLUESKY_API_DOMAIN }}" + + # 3) Web UI host (only if enabled) + - type: A + zone: "{{ BLUESKY_WEB_DOMAIN | to_zone }}" + name: "{{ BLUESKY_WEB_DOMAIN }}" + content: "{{ networks.internet.ip4 }}" + proxied: true + state: "{{ (BLUESKY_WEB_ENABLED | bool) | ternary('present','absent') }}" + + - type: AAAA + zone: "{{ BLUESKY_WEB_DOMAIN | to_zone }}" + name: "{{ BLUESKY_WEB_DOMAIN }}" + content: "{{ networks.internet.ip6 | default('') }}" + proxied: true + state: "{{ (BLUESKY_WEB_ENABLED | bool) and (networks.internet.ip6 is defined) and ((networks.internet.ip6 | string) | length > 0) | ternary('present','absent') }}" + + # 4) Custom AppView host (only if you actually run one and it's not api.bsky.app) + - type: A + zone: "{{ BLUESKY_VIEW_DOMAIN | to_zone }}" + name: "{{ BLUESKY_VIEW_DOMAIN }}" + content: "{{ networks.internet.ip4 }}" + proxied: false + state: "{{ (BLUESKY_VIEW_ENABLED | bool) and (BLUESKY_VIEW_DOMAIN != 'api.bsky.app') | ternary('present','absent') }}" + + - type: AAAA + zone: "{{ BLUESKY_VIEW_DOMAIN | to_zone }}" + name: "{{ BLUESKY_VIEW_DOMAIN }}" + content: "{{ networks.internet.ip6 | default('') }}" + proxied: false + state: "{{ (BLUESKY_VIEW_ENABLED | bool) and (BLUESKY_VIEW_DOMAIN != 'api.bsky.app') and (networks.internet.ip6 is defined) and ((networks.internet.ip6 | string) | length > 0) | ternary('present','absent') }}" + diff --git a/roles/web-app-bluesky/tasks/main.yml b/roles/web-app-bluesky/tasks/main.yml index 50a1bb30..22cf6ba4 100644 --- a/roles/web-app-bluesky/tasks/main.yml +++ b/roles/web-app-bluesky/tasks/main.yml @@ -1,48 +1,39 @@ - name: "include docker-compose role" include_role: name: docker-compose + vars: + docker_compose_flush_handlers: false -- name: "include role sys-stk-front-proxy for {{ application_id }}" +- name: "Include front proxy for {{ BLUESKY_API_DOMAIN }}:{{ BLUESKY_API_PORT }}" include_role: name: sys-stk-front-proxy vars: - domain: "{{ item.domain }}" - http_port: "{{ item.http_port }}" - loop: - - { domain: "{{domains[application_id].api", http_port: "{{ports.localhost.http['web-app-bluesky_api']}}" } - - { domain: "{{domains[application_id].web}}", http_port: "{{ports.localhost.http['web-app-bluesky_web']}}" } + domain: "{{ BLUESKY_API_DOMAIN }}" + http_port: "{{ BLUESKY_API_PORT }}" -# The following lines should be removed when the following issue is closed: -# https://github.com/bluesky-social/pds/issues/52 +- name: "Include front proxy for {{ BLUESKY_WEB_DOMAIN }}:{{ BLUESKY_WEB_PORT }}" + include_role: + name: sys-stk-front-proxy + vars: + domain: "{{ BLUESKY_WEB_DOMAIN }}" + http_port: "{{ BLUESKY_WEB_PORT }}" + when: BLUESKY_WEB_ENABLED | bool -- name: Download pdsadmin tarball - get_url: - url: "https://github.com/lhaig/pdsadmin/releases/download/v1.0.0-dev/pdsadmin_Linux_x86_64.tar.gz" - dest: "{{pdsadmin_temporary_tar_path}}" - mode: '0644' +- name: "Include front proxy for {{ BLUESKY_VIEW_DOMAIN }}:{{ BLUESKY_VIEW_PORT }}" + include_role: + name: sys-stk-front-proxy + vars: + domain: "{{ BLUESKY_VIEW_DOMAIN }}" + http_port: "{{ BLUESKY_VIEW_PORT }}" + when: BLUESKY_VIEW_ENABLED | bool -- name: Create {{pdsadmin_folder_path}} - file: - path: "{{pdsadmin_folder_path}}" - state: directory - mode: '0755' - -- name: Extract pdsadmin tarball - unarchive: - src: "{{pdsadmin_temporary_tar_path}}" - dest: "{{pdsadmin_folder_path}}" - remote_src: yes - mode: '0755' +- name: "Execute PDS routines" + ansible.builtin.include_tasks: "01_pds.yml" -- name: Ensure pdsadmin is executable - file: - path: "{{pdsadmin_file_path}}" - mode: '0755' - state: file +- name: "Execute Social App routines" + ansible.builtin.include_tasks: "02_social_app.yml" + when: BLUESKY_WEB_ENABLED | bool -- name: clone social app repository - git: - repo: "https://github.com/bluesky-social/social-app.git" - dest: "{{social_app_path}}" - version: "main" - notify: docker compose up +- name: "DNS for Bluesky" + include_tasks: "03_dns.yml" + when: DNS_PROVIDER | lower == 'cloudflare' \ No newline at end of file diff --git a/roles/web-app-bluesky/templates/docker-compose.yml.j2 b/roles/web-app-bluesky/templates/docker-compose.yml.j2 index e96ebed2..54e540a3 100644 --- a/roles/web-app-bluesky/templates/docker-compose.yml.j2 +++ b/roles/web-app-bluesky/templates/docker-compose.yml.j2 @@ -3,40 +3,32 @@ pds: {% set container_port = 3000 %} {% set container_healthcheck = 'xrpc/_health' %} - image: "{{ applications | get_app_conf(application_id, 'images.pds', True) }}" + image: "{{ BLUESKY_PDS_IMAGE }}:{{ BLUESKY_PDS_VERSION }}" {% include 'roles/docker-container/templates/base.yml.j2' %} volumes: - - pds_data:/opt/pds - - {{pdsadmin_file_path}}:/usr/local/bin/pdsadmin:ro + - pds_data:{{ BLUESKY_PDS_DATA_DIR }} + - {{ BLUESKY_PDSADMIN_FILE }}:/usr/local/bin/pdsadmin:ro ports: - - "127.0.0.1:{{ports.localhost.http['web-app-bluesky_api']}}:{{ container_port }}" + - "127.0.0.1:{{ BLUESKY_API_PORT }}:{{ container_port }}" {% include 'roles/docker-container/templates/healthcheck/wget.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %} -# Deactivated for the moment @see https://github.com/bluesky-social/social-app +{% if BLUESKY_WEB_ENABLED %} +{% set container_port = 8100 %} web: command: ["bskyweb","serve"] build: - context: "{{ social_app_path }}" - dockerfile: Dockerfile - # It doesn't compile yet with this parameters. @todo Fix it - args: - REACT_APP_PDS_URL: "{{ WEB_PROTOCOL }}://{{domains[application_id].api}}" # URL des PDS - REACT_APP_API_URL: "{{ WEB_PROTOCOL }}://{{domains[application_id].api}}" # API-URL des PDS - REACT_APP_SITE_NAME: "{{ PRIMARY_DOMAIN | upper }} - Bluesky" - REACT_APP_SITE_DESCRIPTION: "Decentral Social " + context: "{{ BLUESKY_SOCIAL_APP_DIR }}" + dockerfile: Dockerfile pull_policy: never ports: - - "127.0.0.1:{{ports.localhost.http['web-app-bluesky_web']}}:8100" - healthcheck: - test: ["CMD", "sh", "-c", "for pid in $(ls /proc | grep -E '^[0-9]+$'); do if cat /proc/$pid/cmdline 2>/dev/null | grep -q 'bskywebserve'; then exit 0; fi; done; exit 1"] - interval: 30s - timeout: 10s - retries: 3 + - "127.0.0.1:{{ BLUESKY_WEB_PORT }}:{{ container_port }}" +{% include 'roles/docker-container/templates/healthcheck/tcp.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %} +{% endif %} {% include 'roles/docker-compose/templates/volumes.yml.j2' %} pds_data: - + name: {{ BLUESKY_PDS_DATA_VOLUME }} {% include 'roles/docker-compose/templates/networks.yml.j2' %} \ No newline at end of file diff --git a/roles/web-app-bluesky/templates/env.j2 b/roles/web-app-bluesky/templates/env.j2 index 8ae049cb..d35c4275 100644 --- a/roles/web-app-bluesky/templates/env.j2 +++ b/roles/web-app-bluesky/templates/env.j2 @@ -1,21 +1,21 @@ -PDS_HOSTNAME="{{domains[application_id].api}}" -PDS_ADMIN_EMAIL="{{ applications.bluesky.users.administrator.email}}" -PDS_SERVICE_DID="did:web:{{domains[application_id].api}}" +PDS_HOSTNAME="{{ BLUESKY_API_DOMAIN }}" +PDS_ADMIN_EMAIL="{{ BLUESKY_ADMIN_EMAIL }}" +PDS_SERVICE_DID="did:web:{{ BLUESKY_API_DOMAIN }}" # See https://mattdyson.org/blog/2024/11/self-hosting-bluesky-pds/ PDS_SERVICE_HANDLE_DOMAINS=".{{ PRIMARY_DOMAIN }}" -PDS_JWT_SECRET="{{ bluesky_jwt_secret }}" -PDS_ADMIN_PASSWORD="{{bluesky_admin_password}}" -PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="{{ bluesky_rotation_key }}" +PDS_JWT_SECRET="{{ BLUESKY_JWT_SECRET }}" +PDS_ADMIN_PASSWORD="{{ BLUESKY_ADMIN_PASSWORD }}" +PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="{{ BLUESKY_ROTATION_KEY }}" PDS_CRAWLERS=https://bsky.network PDS_EMAIL_SMTP_URL=smtps://{{ users['no-reply'].email }}:{{ users['no-reply'].mailu_token }}@{{ SYSTEM_EMAIL.HOST }}:{{ SYSTEM_EMAIL.PORT }}/ PDS_EMAIL_FROM_ADDRESS={{ users['no-reply'].email }} -LOG_ENABLED=true -PDS_BLOBSTORE_DISK_LOCATION=/opt/pds/blocks -PDS_DATA_DIRECTORY: /opt/pds -PDS_BLOB_UPLOAD_LIMIT: 52428800 +LOG_ENABLED={{ MODE_DEBUG | string | lower }} +PDS_BLOBSTORE_DISK_LOCATION={{ BLUESKY_PDS_BLOBSTORE_LOCATION }} +PDS_DATA_DIRECTORY={{ BLUESKY_PDS_DATA_DIR }} +PDS_BLOB_UPLOAD_LIMIT=52428800 PDS_DID_PLC_URL=https://plc.directory -PDS_BSKY_APP_VIEW_URL=https://{{domains[application_id].web}} -PDS_BSKY_APP_VIEW_DID=did:web:{{domains[application_id].web}} +PDS_BSKY_APP_VIEW_URL={{ BLUESKY_VIEW_URL }} +PDS_BSKY_APP_VIEW_DID={{ BLUESKY_VIEW_DID }} PDS_REPORT_SERVICE_URL=https://mod.bsky.app PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac diff --git a/roles/web-app-bluesky/vars/main.yml b/roles/web-app-bluesky/vars/main.yml index 8fbaa171..ba15f447 100644 --- a/roles/web-app-bluesky/vars/main.yml +++ b/roles/web-app-bluesky/vars/main.yml @@ -1,11 +1,45 @@ -application_id: "web-app-bluesky" -social_app_path: "{{ docker_compose.directories.services }}/social-app" +# General +application_id: "web-app-bluesky" + +## Bluesky + +## Social App +BLUESKY_SOCIAL_APP_DIR: "{{ docker_compose.directories.services }}/social-app" # This should be removed when the following issue is closed: # https://github.com/bluesky-social/pds/issues/52 -pdsadmin_folder_path: "{{ docker_compose.directories.volumes }}/pdsadmin" -pdsadmin_file_path: "{{pdsadmin_folder_path}}/pdsadmin" -pdsadmin_temporary_tar_path: "/tmp/pdsadmin.tar.gz" -bluesky_jwt_secret: "{{ applications | get_app_conf(application_id, 'credentials.jwt_secret') }}" -bluesky_admin_password: "{{ applications | get_app_conf(application_id, 'credentials.admin_password') }}" -bluesky_rotation_key: "{{ applications | get_app_conf(application_id, 'credentials.plc_rotation_key_k256_private_key_hex') }}" \ No newline at end of file + +## PDS +BLUESKY_PDSADMIN_DIR: "{{ [ docker_compose.directories.volumes, 'pdsadmin' ] | path_join }}" +BLUESKY_PDSADMIN_FILE: "{{ [ BLUESKY_PDSADMIN_DIR, 'pdsadmin' ] | path_join }}" +BLUESKY_PDSADMIN_TMP_TAR: "/tmp/pdsadmin.tar.gz" +BLUESKY_PDS_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.pds.image') }}" +BLUESKY_PDS_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.pds.version') }}" +BLUESKY_PDS_DATA_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.pds_data') }}" +BLUESKY_PDS_DATA_DIR: "/opt/pds" +BLUESKY_PDS_BLOBSTORE_LOCATION: "{{ [ BLUESKY_PDS_DATA_DIR, 'blocks' ] | path_join }}" + +## Web +BLUESKY_WEB_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.web.enabled') }}" +BLUESKY_WEB_DOMAIN: "{{ domains[application_id].web }}" +BLUESKY_WEB_PORT: "{{ ports.localhost.http['web-app-bluesky_web'] }}" + +## View +BLUESKY_VIEW_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.view.enabled') }}" +BLUESKY_VIEW_DOMAIN: "{{ domains[application_id].view if BLUESKY_VIEW_ENABLED else 'api.bsky.app' }}" +BLUESKY_VIEW_URL: "{{ WEB_PROTOCOL }}://{{ BLUESKY_VIEW_DOMAIN }}" +BLUESKY_VIEW_DID: "did:web:{{ BLUESKY_VIEW_DOMAIN }}" +BLUESKY_VIEW_PORT: "{{ ports.localhost.http['web-app-bluesky_view'] | default(8053) }}" + +## Server +BLUESKY_API_DOMAIN: "{{ domains[application_id].api }}" +BLUESKY_API_PORT: "{{ ports.localhost.http['web-app-bluesky_api'] }}" + +## Credentials +BLUESKY_JWT_SECRET: "{{ applications | get_app_conf(application_id, 'credentials.jwt_secret') }}" +BLUESKY_ROTATION_KEY: "{{ applications | get_app_conf(application_id, 'credentials.plc_rotation_key_k256_private_key_hex') }}" + +## Admin +BLUESKY_ADMIN_EMAIL: "{{ users.administrator.email }}" +BLUESKY_ADMIN_PASSWORD: "{{ users.administrator.password }}" +