diff --git a/group_vars/all/09_networks.yml b/group_vars/all/09_networks.yml index 9fd88a0c..54d17bf4 100644 --- a/group_vars/all/09_networks.yml +++ b/group_vars/all/09_networks.yml @@ -100,6 +100,8 @@ defaults_networks: subnet: 192.168.103.192/28 web-app-magento: subnet: 192.168.103.208/28 + web-app-bridgy-fed: + subnet: 192.168.103.224/28 # /24 Networks / 254 Usable Clients web-app-bigbluebutton: diff --git a/group_vars/all/10_ports.yml b/group_vars/all/10_ports.yml index fdf3672d..78e14546 100644 --- a/group_vars/all/10_ports.yml +++ b/group_vars/all/10_ports.yml @@ -74,6 +74,7 @@ ports: web-app-chess: 8050 web-app-bluesky_view: 8051 web-app-magento: 8052 + web-app-bridgy-fed: 8053 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-bridgy-fed/README.md b/roles/web-app-bridgy-fed/README.md new file mode 100644 index 00000000..e1f72fca --- /dev/null +++ b/roles/web-app-bridgy-fed/README.md @@ -0,0 +1,25 @@ +# Bridgy Fed + +## Description +Bridgy Fed bridges ActivityPub (Fediverse), ATProto/Bluesky, and IndieWeb (webmentions/mf2). It mirrors identities and interactions across networks. + +## Overview +This role builds and runs Bridgy Fed as a Docker container and (optionally) starts a Datastore-mode Firestore emulator as a sidecar. It exposes HTTP locally for a front proxy. + +Upstream docs & dev notes: +- User & developer docs: https://fed.brid.gy and https://bridgy-fed.readthedocs.io/ +- Source: https://github.com/snarfed/bridgy-fed +- Local run (reference): `flask run -p 8080` with APPVIEW_HOST/PLC_HOST/BGS_HOST/PDS_HOST set, and Datastore emulator envs + +## Features +- Dockerized Flask app (gunicorn) +- Optional Firestore emulator (Datastore mode) sidecar +- Front proxy integration via `sys-stk-front-proxy` + +## Quick start +1) Set domains and ports in inventory. +2) Enable/disable the emulator in `config/main.yml`. +3) Run the role; your front proxy will publish the app. + +## Notes +- Emulator is **not** for production; it’s in-memory unless you mount a volume/configure import/export. diff --git a/roles/web-app-bridgy-fed/config/main.yml b/roles/web-app-bridgy-fed/config/main.yml new file mode 100644 index 00000000..2f3c4c8b --- /dev/null +++ b/roles/web-app-bridgy-fed/config/main.yml @@ -0,0 +1,34 @@ +features: + matomo: true + css: true + desktop: true + central_database: false + logout: false + oidc: false + +server: + domains: + canonical: + - "bridgyfed.{{ PRIMARY_DOMAIN }}" + csp: + whitelist: {} + flags: {} + +docker: + services: + database: + enabled: false + emulator: + enabled: true + image: 'gcr.io/google.com/cloudsdktool/google-cloud-cli:latest' + application: + image: "python" + version: "3.12-bookworm" + name: "web-app-bridgy-fed" + +rbac: + roles: {} + +source: + repo: "https://github.com/snarfed/bridgy-fed.git" + ref: "main" diff --git a/roles/web-app-bridgy-fed/files/Dockerfile b/roles/web-app-bridgy-fed/files/Dockerfile new file mode 100644 index 00000000..8fa25b0c --- /dev/null +++ b/roles/web-app-bridgy-fed/files/Dockerfile @@ -0,0 +1,49 @@ +# Runtime image for Bridgy Fed (Flask) with a build step that clones upstream +ARG PY_BASE="python:3.12-bookworm" +FROM ${PY_BASE} AS build + +ARG BRIDGY_REPO_URL +ARG BRIDGY_REPO_REF + +# System deps: git, build tools, curl for healthchecks, and gunicorn +RUN apt-get update && apt-get install -y --no-install-recommends \ + git build-essential curl ca-certificates && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app +RUN git clone --depth=1 --branch "${BRIDGY_REPO_REF}" "${BRIDGY_REPO_URL}" ./ + +# Python deps +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Create oauth_dropins static symlink (upstream expects this) +RUN python - <<'PY'\n\ +import oauth_dropins, pathlib, os\n\ +target = pathlib.Path(oauth_dropins.__file__).parent / 'static'\n\ +link = pathlib.Path('/app/oauth_dropins_static')\n\ +try:\n\ + if link.exists() or link.is_symlink():\n\ + link.unlink()\n\ + os.symlink(str(target), str(link))\n\ +except FileExistsError:\n\ + pass\n\ +print('Symlinked oauth_dropins_static ->', target)\n\ +PY + +# Final stage +FROM ${PY_BASE} + +ARG CONTAINER_PORT +ENV PORT=${CONTAINER_PORT:-8080} + +WORKDIR /app +COPY --from=build /app /app + +# Non-root good practice +RUN useradd -r -m -d /nonroot appuser && chown -R appuser:appuser /app +USER appuser + +EXPOSE ${PORT} +# Upstream flask app entry: 'flask_app:app' +CMD ["sh", "-lc", "exec gunicorn -w 2 -k gthread -b 0.0.0.0:${PORT} flask_app:app"] diff --git a/roles/web-app-bridgy-fed/meta/main.yml b/roles/web-app-bridgy-fed/meta/main.yml new file mode 100644 index 00000000..5bb940c9 --- /dev/null +++ b/roles/web-app-bridgy-fed/meta/main.yml @@ -0,0 +1,22 @@ +--- +galaxy_info: + author: "Kevin Veen-Birkenbach" + description: "Bridgy Fed: bridge between ActivityPub (Fediverse), ATProto/Bluesky and IndieWeb." + license: "Infinito.Nexus NonCommercial License" + license_url: "https://s.infinito.nexus/license" + company: | + Kevin Veen-Birkenbach + Consulting & Coaching Solutions + https://www.veen.world + galaxy_tags: + - activitypub + - bluesky + - atproto + - fediverse + - bridge + repository: "https://s.infinito.nexus/code" + issue_tracker_url: "https://s.infinito.nexus/issues" + documentation: "https://fed.brid.gy/docs" + logo: + class: "fa-solid fa-bridge" +dependencies: [] diff --git a/roles/web-app-bridgy-fed/tasks/01_core.yml b/roles/web-app-bridgy-fed/tasks/01_core.yml new file mode 100644 index 00000000..f8f77031 --- /dev/null +++ b/roles/web-app-bridgy-fed/tasks/01_core.yml @@ -0,0 +1,21 @@ +- name: "Load docker and front proxy for {{ application_id }}" + include_role: + name: sys-stk-full-stateless + +- name: "Include front proxy for {{ container_hostname }}:{{ ports.localhost.http[application_id] }}" + include_role: + name: sys-stk-front-proxy + vars: + domain: "{{ container_hostname }}" + http_port: "{{ ports.localhost.http[application_id] }}" + +- name: "Provide Dockerfile" + copy: + src: "Dockerfile" + dest: "{{ docker_compose.directories.instance }}/Dockerfile" + notify: + - docker compose build + +- name: "Run once marker" + set_fact: + run_once_web_app_bridgy_fed: true diff --git a/roles/web-app-bridgy-fed/tasks/main.yml b/roles/web-app-bridgy-fed/tasks/main.yml new file mode 100644 index 00000000..2b56d768 --- /dev/null +++ b/roles/web-app-bridgy-fed/tasks/main.yml @@ -0,0 +1,3 @@ +- name: "Include core routines for '{{ application_id }}'" + include_tasks: "01_core.yml" + when: run_once_web_app_bridgy_fed is not defined diff --git a/roles/web-app-bridgy-fed/templates/Administration.md.j2 b/roles/web-app-bridgy-fed/templates/Administration.md.j2 new file mode 100644 index 00000000..201dd164 --- /dev/null +++ b/roles/web-app-bridgy-fed/templates/Administration.md.j2 @@ -0,0 +1,20 @@ +# Administration + +## Local dev shell (inside container) +```bash +docker compose exec application bash +``` + +## Logs +```bash +docker compose logs -f application +docker compose logs -f emulator # if enabled +``` + +## Notes +- Upstream dev run example: + ```bash + export APPVIEW_HOST=api.bsky.app PLC_HOST=plc.directory BGS_HOST=bsky.network PDS_HOST=atproto.brid.gy + flask --debug run -p 8080 + ``` + (We run via gunicorn in this role.) diff --git a/roles/web-app-bridgy-fed/templates/docker-compose.yml.j2 b/roles/web-app-bridgy-fed/templates/docker-compose.yml.j2 new file mode 100644 index 00000000..540f4993 --- /dev/null +++ b/roles/web-app-bridgy-fed/templates/docker-compose.yml.j2 @@ -0,0 +1,43 @@ +{% include 'roles/docker-compose/templates/base.yml.j2' %} + + application: + build: + context: . + dockerfile: Dockerfile + args: + BRIDGY_REPO_URL: "{{ BRIDGY_REPO_URL }}" + BRIDGY_REPO_REF: "{{ BRIDGY_REPO_REF }}" + CONTAINER_PORT: "{{ container_port | string }}" + image: "{{ BRIDGY_IMAGE }}" + container_name: "{{ BRIDGY_CONTAINER }}" + hostname: "{{ container_hostname }}" + env_file: + - "{{ docker_compose.files.env }}" + ports: + - "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}" +{% include 'roles/docker-container/templates/healthcheck/tcp.yml.j2' %} +{% include 'roles/docker-container/templates/base.yml.j2' %} + depends_on: +{% if EMULATOR_ENABLED | bool %} + - emulator +{% endif %} +{% include 'roles/docker-container/templates/networks.yml.j2' %} + +{% if EMULATOR_ENABLED | bool %} + emulator: + image: "{{ EMULATOR_IMAGE }}" + container_name: "{{ BRIDGY_CONTAINER }}_emulator" + command: > + gcloud emulators firestore start + --host-port=0.0.0.0:{{ EMULATOR_PORT }} + --database-mode=datastore-mode + --quiet + ports: + - "127.0.0.1:{{ EMULATOR_PORT }}:{{ EMULATOR_PORT }}" + environment: + - CLOUDSDK_CORE_DISABLE_PROMPTS=1 + restart: unless-stopped +{% include 'roles/docker-container/templates/networks.yml.j2' %} +{% endif %} + +{% include 'roles/docker-compose/templates/networks.yml.j2' %} diff --git a/roles/web-app-bridgy-fed/templates/env.j2 b/roles/web-app-bridgy-fed/templates/env.j2 new file mode 100644 index 00000000..e86c2409 --- /dev/null +++ b/roles/web-app-bridgy-fed/templates/env.j2 @@ -0,0 +1,18 @@ +# Flask / Gunicorn basics +FLASK_ENV="{{ ENVIRONMENT | default('production') }}" +PORT="{{ container_port }}" +BRIDGY_ADMIN_EMAIL="{{ BRIDGY_ADMIN_EMAIL }}" + +# Bridgy Fed upstream knobs (see README @ GitHub) +APPVIEW_HOST="{{ APPVIEW_HOST }}" +PLC_HOST="{{ PLC_HOST }}" +BGS_HOST="{{ BGS_HOST }}" +PDS_HOST="{{ PDS_HOST }}" + +# Datastore emulator (Datastore-mode Firestore). If sidecar enabled, point here. +GOOGLE_CLOUD_PROJECT="{{ EMULATOR_PROJECT_ID }}" +DATASTORE_EMULATOR_HOST="{{ EMULATOR_ENABLED | ternary(EMULATOR_HOST_INTERNAL, '') }}" +# DATASTORE_DATASET not needed when GOOGLE_CLOUD_PROJECT is set + +# Optional: +# GUNICORN_CMD_ARGS="--log-level info" diff --git a/roles/web-app-bridgy-fed/vars/main.yml b/roles/web-app-bridgy-fed/vars/main.yml new file mode 100644 index 00000000..88b6f46b --- /dev/null +++ b/roles/web-app-bridgy-fed/vars/main.yml @@ -0,0 +1,27 @@ +# General +application_id: "web-app-bridgy-fed" + +# App container +BRIDGY_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.application.name') }}" +BRIDGY_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.application.image') }}" +#BRIDGY_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.application.version')}}" +container_port: 8080 +container_hostname: "{{ domains | get_domain(application_id) }}" +BRIDGY_ADMIN_EMAIL: "{{ users.administrator.email }}" + +# Source +BRIDGY_REPO_URL: "{{ applications | get_app_conf(application_id, 'source.repo') }}" +BRIDGY_REPO_REF: "{{ applications | get_app_conf(application_id, 'source.ref') }}" + +# Emulator sidecar (Datastore-mode Firestore) +EMULATOR_ENABLED: "{{ applications | get_app_conf(application_id, 'docker.services.emulator.enabled') }}" +EMULATOR_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.emulator.image') }}" +EMULATOR_PORT: 8089 +EMULATOR_HOST_INTERNAL: "emulator:8089" +EMULATOR_PROJECT_ID: "bridgy-federated-local" + +# Runtime env defaults for Bridgy Fed (see upstream README) +APPVIEW_HOST: "api.bsky.app" +PLC_HOST: "plc.directory" +BGS_HOST: "bsky.network" +PDS_HOST: "atproto.brid.gy"