PocketBase on Labs crash-looped since the SSO deploy (PR #17): mounting --migrationsDir for the first time replayed the entire migration history against a database that was provisioned out-of-band (empty _migrations ledger) and died on 1778948471_created_content.js. On top of that the team_members->auth migration had its own crash paths and trapped OAuth2 config inside a one-shot, env-dependent migration. - pb_migrations/1000000000_baseline_ledger_sync.js: detects an out-of-band provisioned DB (schema exists, ledger empty) and marks the 79 historical migrations as applied; no-op on fresh or already-synced DBs - pb_migrations/1781000000_team_members_to_auth.js: idempotency guard, relation->text conversion WITH data preservation (PocketBase diffs fields by id, so the column is backed up and restored via SQL), unique index rebuild, no silent catches, env only as fast-path - pb_hooks/entra_oidc.pb.js + pb_hooks/utils.js: reconcile the Entra OIDC provider from ENTRA_* env on every bootstrap + cron tick (compare-before-save, warn-once); heals environments that migrated without secrets and supports secret rotation without re-apply - pb_hooks/team_members.pb.js: require() pattern — JSVM runs callbacks as isolated programs, top-level helpers are not in scope (adopts Leroy's fix from the fix-sso branch) - infra/*/site/deploy-playbook.yml: health-gate after compose up — the deploy fails loudly with container logs when PocketBase does not become healthy (runs #83-#88 were green while PB crash-looped) - docker-compose.yml: .env.local is optional again - docs/auth-spec.md + AI_AGENT.md: ledger/reconciler documentation, ADRs 006-008, never-rename-applied-migrations warning Verified locally against PocketBase v0.30.4 with a 7-scenario DoD matrix (fresh DB +/- env, out-of-band DB with data incl. data preservation, populated-ledger upgrade, late-secrets healing, re-run guard, hook provisioning): 15/15 pass. npm test 112/112. Closes #18 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
103 lines
3.2 KiB
YAML
103 lines
3.2 KiB
YAML
- name: Deploy Learning Platform with Ansible
|
|
hosts: all
|
|
become: yes
|
|
gather_facts: yes
|
|
vars:
|
|
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
|
|
|
tasks:
|
|
- name: Set destination directory
|
|
ansible.builtin.set_fact:
|
|
dest_dir: "/opt/learning-platform"
|
|
|
|
- name: Ensure dependencies are installed
|
|
ansible.builtin.package:
|
|
name:
|
|
- docker-ce
|
|
state: present
|
|
ignore_errors: yes
|
|
register: docker_valid
|
|
|
|
- name: Include geerlingguy.docker role conditionally
|
|
include_role:
|
|
name: geerlingguy.docker
|
|
when: docker_valid is failed
|
|
|
|
- name: Ensure target directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ dest_dir }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Copy compose.yml to destination
|
|
ansible.builtin.copy:
|
|
src: compose.yml
|
|
dest: "{{ dest_dir }}/compose.yml"
|
|
|
|
- name: Copy pb_migrations to destination
|
|
ansible.builtin.copy:
|
|
src: ../../../pb_migrations
|
|
dest: "{{ dest_dir }}/"
|
|
mode: "0755"
|
|
|
|
- name: Copy pb_hooks to destination
|
|
ansible.builtin.copy:
|
|
src: ../../../pb_hooks
|
|
dest: "{{ dest_dir }}/"
|
|
mode: "0755"
|
|
|
|
- name: Create .env file for secrets
|
|
ansible.builtin.copy:
|
|
dest: "{{ dest_dir }}/.env"
|
|
content: |
|
|
ANTHROPIC_API_KEY={{ anthropic_api_key | default('') }}
|
|
ENTRA_TENANT_ID={{ entra_tenant_id | default('') }}
|
|
ENTRA_CLIENT_ID={{ entra_client_id | default('') }}
|
|
ENTRA_CLIENT_SECRET={{ entra_client_secret | default('') }}
|
|
ENTRA_ADMIN_EMAILS={{ entra_admin_emails | default('') }}
|
|
mode: '0600'
|
|
|
|
- name: Pull latest image
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ dest_dir }}"
|
|
state: present
|
|
files: compose.yml
|
|
pull: always
|
|
|
|
- name: Start services with Docker Compose
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ dest_dir }}"
|
|
state: present
|
|
files: compose.yml
|
|
recreate: always
|
|
|
|
# A failed migration aborts `pocketbase serve` and the container crash-loops
|
|
# while `docker compose up` still reports success (issue #18). Gate the
|
|
# deploy on PocketBase actually serving its health endpoint.
|
|
- name: Wait for PocketBase to become healthy
|
|
ansible.builtin.command: docker exec pocketbase-learning wget -q -O - http://127.0.0.1:8090/api/health
|
|
register: pb_health
|
|
until: pb_health.rc == 0
|
|
retries: 18
|
|
delay: 5
|
|
changed_when: false
|
|
ignore_errors: yes
|
|
|
|
- name: Collect PocketBase logs for diagnosis
|
|
ansible.builtin.command: docker logs --tail 80 pocketbase-learning
|
|
register: pb_logs
|
|
when: pb_health is failed
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Abort deploy — PocketBase is not healthy
|
|
ansible.builtin.fail:
|
|
msg: |
|
|
PocketBase did not become healthy after the deploy (health endpoint unreachable).
|
|
Recent container logs:
|
|
{{ pb_logs.stdout | default('') }}
|
|
{{ pb_logs.stderr | default('') }}
|
|
when: pb_health is failed
|