feat: add FRANCIS_HOST to connect to a standalone Francis runtime

FRANCIS_HOST decides where the Francis actor runtime lives. When it is
empty or set to "embedded" (the default), Pocket ID starts the runtime inside its own process, backed by its own database.

Any other value is the address, or a comma-separated list of addresses, of a standalone Francis runtime. Pocket ID then connects to it as a remote actor host and starts no embedded runtime.

Note: connecting to a standalone runtime also needs FRANCIS_HOST_PSK or FRANCIS_HOST_JWT, and optionally (but recommended) FRANCIS_CA.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMgoTZtznSjP4SHTaHbRen
This commit is contained in:
ItalyPaleAle
2026-09-08 14:50:23 +00:00
co-authored by Claude Opus 5
parent 7ddc5d690b
commit dcb1ef5ef1
37 changed files with 1164 additions and 151 deletions
+53 -3
View File
@@ -25,17 +25,26 @@ jobs:
strategy:
fail-fast: false
matrix:
# "francis" selects where the actor runtime lives: embedded in Pocket ID, or a standalone runtime it connects to
include:
- db: sqlite
storage: filesystem
francis: embedded
- db: postgres
storage: filesystem
francis: embedded
- db: sqlite
storage: s3
francis: embedded
- db: sqlite
storage: database
francis: embedded
- db: postgres
storage: database
francis: embedded
- db: sqlite
storage: filesystem
francis: remote
steps:
- name: Checkout code
@@ -109,6 +118,31 @@ jobs:
if: matrix.storage == 's3' && steps.s3-cache.outputs.cache-hit == 'true'
run: docker load < /tmp/localstack-s3-image.tar
- name: Resolve Francis runtime image
id: francis-image
working-directory: ./tests/setup
# The Francis dependency in backend/go.mod is the single source of truth for the version, so the runtime the tests run against always matches the client Pocket ID is built with
run: |
VERSION=$(./francis-version.sh)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "image=ghcr.io/italypaleale/francis:$VERSION" >> "$GITHUB_OUTPUT"
echo "key=francis-$VERSION" >> "$GITHUB_OUTPUT"
- name: Cache Francis runtime Docker image
if: matrix.francis == 'remote'
uses: actions/cache@v5
id: francis-cache
with:
path: /tmp/francis-image.tar
key: ${{ steps.francis-image.outputs.key }}-${{ runner.os }}
- name: Pull and save Francis runtime image
if: matrix.francis == 'remote' && steps.francis-cache.outputs.cache-hit != 'true'
run: |
docker pull "${{ steps.francis-image.outputs.image }}"
docker save "${{ steps.francis-image.outputs.image }}" > /tmp/francis-image.tar
- name: Load Francis runtime image
if: matrix.francis == 'remote' && steps.francis-cache.outputs.cache-hit == 'true'
run: docker load < /tmp/francis-image.tar
- name: Install test dependencies
run: pnpm --filter pocket-id-tests install --frozen-lockfile
@@ -124,11 +158,14 @@ jobs:
cat > .env <<EOF
FILE_BACKEND=${{ matrix.storage }}
FRANCIS_VERSION=${{ steps.francis-image.outputs.version }}
SCIM_SERVICE_PROVIDER_URL=http://localhost:18123/v2
SCIM_SERVICE_PROVIDER_URL_INTERNAL=http://scim-test-server:8080/v2
EOF
if [ "${{ matrix.db }}" = "postgres" ]; then
if [ "${{ matrix.francis }}" = "remote" ]; then
DOCKER_COMPOSE_FILE=docker-compose-francis.yml
elif [ "${{ matrix.db }}" = "postgres" ]; then
DOCKER_COMPOSE_FILE=docker-compose-postgres.yml
elif [ "${{ matrix.storage }}" = "s3" ]; then
DOCKER_COMPOSE_FILE=docker-compose-s3.yml
@@ -150,6 +187,10 @@ jobs:
done
} &
if [ "${{ matrix.francis }}" = "remote" ]; then
docker compose -f "$DOCKER_COMPOSE_FILE" logs -f --no-log-prefix francis-runtime > /tmp/francis-runtime.log 2>&1 &
fi
- name: Run Playwright tests
working-directory: ./tests
run: pnpm exec playwright test
@@ -158,7 +199,7 @@ jobs:
uses: actions/upload-artifact@v7
if: always() && github.event.pull_request.head.ref != 'i18n_crowdin'
with:
name: playwright-report-${{ matrix.db }}-${{ matrix.storage }}
name: playwright-report-${{ matrix.db }}-${{ matrix.storage }}-francis-${{ matrix.francis }}
path: tests/.report
include-hidden-files: true
retention-days: 15
@@ -167,7 +208,16 @@ jobs:
uses: actions/upload-artifact@v7
if: always() && github.event.pull_request.head.ref != 'i18n_crowdin'
with:
name: backend-${{ matrix.db }}-${{ matrix.storage }}
name: backend-${{ matrix.db }}-${{ matrix.storage }}-francis-${{ matrix.francis }}
path: /tmp/backend.log
include-hidden-files: true
retention-days: 15
- name: Upload Francis Runtime Report
uses: actions/upload-artifact@v7
if: always() && matrix.francis == 'remote' && github.event.pull_request.head.ref != 'i18n_crowdin'
with:
name: francis-runtime-${{ matrix.db }}-${{ matrix.storage }}
path: /tmp/francis-runtime.log
include-hidden-files: true
retention-days: 15