feat: Add playbooks to manage supabase

This commit is contained in:
2026-04-15 22:34:06 -04:00
parent b74528b6f1
commit dd5e6c68f7
17 changed files with 193 additions and 1105 deletions

View File

@@ -1,98 +0,0 @@
---
# Backs up a running Appwrite instance per the official backup guide:
# https://appwrite.io/docs/advanced/self-hosting/production/backups
#
# What is backed up:
# - MariaDB: mysqldump (--single-transaction, consistent without downtime)
# - Docker volumes: all data volumes (tar.gz, requires service stop)
# - .env file
#
# Backup is written to: {{ appwrite_backup_root }}/YYYYMMDDTHHMMSS/
#
# Required vars (from inventory):
# appwrite_dir - e.g. /home/ptoal/appwrite
#
# Optional vars:
# appwrite_backup_root - destination parent dir (default: /var/backups/appwrite)
# appwrite_compose_project - compose project name (default: basename of appwrite_dir)
- name: Backup Appwrite
hosts: appwrite
gather_facts: true
become: true
vars:
_compose_project: "{{ appwrite_compose_project | default(appwrite_dir | basename) }}"
backup_root: "{{ appwrite_backup_root | default('/var/backups/appwrite') }}"
backup_dir: "{{ backup_root }}/{{ ansible_date_time.iso8601_basic_short }}"
# appwrite-mariadb volume excluded — covered by the mysqldump below.
# appwrite-cache and appwrite-redis are transient but included for
# completeness; they are safe to omit if backup size is a concern.
appwrite_volumes:
- appwrite-uploads
- appwrite-functions
- appwrite-builds
- appwrite-sites
- appwrite-certificates
- appwrite-config
- appwrite-cache
- appwrite-redis
tasks:
- name: Create backup directory
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: '0700'
- name: Dump MariaDB
# --single-transaction gives a consistent InnoDB snapshot without locking.
# Runs while the service is still up so docker compose exec is available.
ansible.builtin.shell:
cmd: >
docker compose exec -T mariadb
sh -c 'exec mysqldump --all-databases --add-drop-database
--single-transaction --routines --triggers
-uroot -p"$MYSQL_ROOT_PASSWORD"'
> {{ backup_dir }}/mariadb-dump.sql
chdir: "{{ appwrite_dir }}"
changed_when: true
- name: Stop, back up volumes, and restart
block:
- name: Stop Appwrite service
ansible.builtin.systemd:
name: appwrite
state: stopped
- name: Back up Docker volumes
ansible.builtin.command:
cmd: >
docker run --rm
-v {{ _compose_project }}_{{ item }}:/data
-v {{ backup_dir }}:/backup
ubuntu tar czf /backup/{{ item }}.tar.gz -C /data .
loop: "{{ appwrite_volumes }}"
changed_when: true
- name: Back up .env
ansible.builtin.copy:
src: "{{ appwrite_dir }}/.env"
dest: "{{ backup_dir }}/.env"
remote_src: true
mode: '0600'
rescue:
- name: Notify that backup failed
ansible.builtin.debug:
msg: "Backup failed — Appwrite will be restarted. Check {{ backup_dir }} for partial output."
always:
- name: Ensure Appwrite service is started
ansible.builtin.systemd:
name: appwrite
state: started
- name: Report backup location
ansible.builtin.debug:
msg: "Backup written to {{ backup_dir }}"

View File

@@ -1,22 +1,16 @@
---
- name: Dump Supabase prod database to local temp file
hosts: localhost
- name: Dump Supabase database to local temp file
hosts: supabase
connection: local
gather_facts: false
tasks:
- name: Read Supabase prod secrets from Vault
ansible.builtin.set_fact:
_supabase_prod: "{{ lookup('community.hashi_vault.hashi_vault',
'secret=oys/prod/supabase url=' + vault_addr + ' engine_mount_point=kv') }}"
no_log: true
- name: Set backup filename
ansible.builtin.set_fact:
_backup_filename: >-
{{ 'oysqn-prod-' + now(fmt='%Y-%m') + '-monthly.sql.gz'
{{ backup_file_prefix + '-' + now(fmt='%Y-%m') + '-monthly.sql.gz'
if now(fmt='%-d') == '1'
else 'oysqn-prod-' + now(fmt='%Y%m%d-%H%M%S') + '.sql.gz' }}
else backup_file_prefix + '-' + now(fmt='%Y%m%d-%H%M%S') + '.sql.gz' }}
- name: Create local temporary directory
ansible.builtin.tempfile:
@@ -26,16 +20,29 @@
- name: Dump and compress database
ansible.builtin.shell:
cmd: "set -o pipefail && pg_dump '{{ _supabase_prod.postgres_url }}' | gzip > '{{ _tmpdir.path }}/{{ _backup_filename }}'"
cmd: "set -o pipefail && pg_dump '{{ supabase.postgres_url }}' | gzip > '{{ _tmpdir.path }}/{{ _backup_filename }}'"
executable: /bin/bash
changed_when: true
no_log: true
- name: Register backup info for storage play
ansible.builtin.add_host:
name: _backup_info
groups: backup_info
_backup_filename: "{{ _backup_filename }}"
_tmpdir_path: "{{ _tmpdir.path }}"
_backup_file_prefix: "{{ backup_file_prefix }}"
- name: Store backup on bab1 and enforce retention
hosts: bab1.mgmt.toal.ca
hosts: backup_dest
gather_facts: false
vars:
_src_filename: "{{ hostvars['_backup_info']['_backup_filename'] }}"
_src_tmpdir: "{{ hostvars['_backup_info']['_tmpdir_path'] }}"
_prefix: "{{ hostvars['_backup_info']['_backup_file_prefix'] }}"
tasks:
- name: Ensure backup directory exists
ansible.builtin.file:
@@ -45,14 +52,14 @@
- name: Copy backup file to bab1
ansible.builtin.copy:
src: "{{ hostvars['localhost']['_tmpdir']['path'] }}/{{ hostvars['localhost']['_backup_filename'] }}"
dest: "{{ backup_base_dir }}/{{ hostvars['localhost']['_backup_filename'] }}"
src: "{{ _src_tmpdir }}/{{ _src_filename }}"
dest: "{{ backup_base_dir }}/{{ _src_filename }}"
mode: '0640'
- name: Find regular backup files older than retention period
ansible.builtin.find:
paths: "{{ backup_base_dir }}"
patterns: "oysqn-prod-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]*.sql.gz"
patterns: "{{ _prefix }}-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]*.sql.gz"
age: "{{ backup_retain_regular_days }}d"
age_stamp: mtime
register: _regular_old
@@ -66,7 +73,7 @@
- name: Find all regular backup files
ansible.builtin.find:
paths: "{{ backup_base_dir }}"
patterns: "oysqn-prod-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]*.sql.gz"
patterns: "{{ _prefix }}-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]*.sql.gz"
register: _regular_all
- name: Delete oldest regular backups beyond count limit
@@ -78,7 +85,7 @@
- name: Find monthly backup files older than retention period
ansible.builtin.find:
paths: "{{ backup_base_dir }}"
patterns: "oysqn-prod-[0-9][0-9][0-9][0-9]-[0-9][0-9]-monthly.sql.gz"
patterns: "{{ _prefix }}-[0-9][0-9][0-9][0-9]-[0-9][0-9]-monthly.sql.gz"
age: "{{ backup_retain_monthly_days }}d"
age_stamp: mtime
register: _monthly_old
@@ -92,7 +99,7 @@
- name: Find all monthly backup files
ansible.builtin.find:
paths: "{{ backup_base_dir }}"
patterns: "oysqn-prod-[0-9][0-9][0-9][0-9]-[0-9][0-9]-monthly.sql.gz"
patterns: "{{ _prefix }}-[0-9][0-9][0-9][0-9]-[0-9][0-9]-monthly.sql.gz"
register: _monthly_all
- name: Delete oldest monthly backups beyond count limit
@@ -103,6 +110,6 @@
- name: Remove local temporary directory
ansible.builtin.file:
path: "{{ hostvars['localhost']['_tmpdir']['path'] }}"
path: "{{ _src_tmpdir }}"
state: absent
delegate_to: localhost

View File

@@ -1,176 +0,0 @@
---
# Bootstraps a fresh Appwrite instance:
# 1. Creates the console admin user
# 2. Creates the BAB project
# 3. Registers web platforms (CORS allowed origins)
# 4. Generates an Ansible automation API key
# 5. Stores the API key secret in Vault at kv/oys/bab-appwrite-api-key
#
# Run once per environment after install_appwrite.yml.
# Safe to re-run: account and project creation tolerate 409.
# Platform and API key creation are NOT idempotent — re-running creates
# duplicates. Delete stale entries from the console.
#
# Required vars (from inventory):
# appwrite_domain - e.g. appwrite.toal.ca (used to build admin URL)
# appwrite_project - project ID to create
# appwrite_project_name - human-readable project name (default: BAB)
# appwrite_web_platforms - list of {name, hostname} dicts for CORS origins
#
# Note: uses appwrite_domain directly, not appwrite_admin_uri, because
# appwrite_admin_uri may point to an app-layer proxy (e.g. nginx) that
# does not expose the Appwrite admin/console endpoints.
- name: Bootstrap Appwrite — Admin, Project, and API Key
hosts: appwrite
gather_facts: false
vars:
appwrite_admin_uri: "https://{{ appwrite_domain }}/v1"
tasks:
- name: Read admin credentials from Vault
community.hashi_vault.vault_kv2_get:
path: oys/bab_admin
engine_mount_point: kv
register: vault_admin
no_log: true
delegate_to: localhost
- name: Create Appwrite console admin account
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/account"
method: POST
body_format: json
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
body:
userId: "{{ appwrite_admin_user_id | default('bab-admin') }}"
email: "{{ vault_admin.secret.bab_admin_user }}"
password: "{{ vault_admin.secret.bab_admin_password }}"
status_code: [201, 409, 501]
return_content: true
delegate_to: localhost
no_log: true
- name: Create admin session
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/account/sessions/email"
method: POST
body_format: json
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
body:
email: "{{ vault_admin.secret.bab_admin_user }}"
password: "{{ vault_admin.secret.bab_admin_password }}"
status_code: [201]
return_content: true
register: admin_session
delegate_to: localhost
no_log: false
- name: Create JWT from admin session
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/account/jwt"
method: POST
body_format: json
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
Cookie: "{{ admin_session.cookies_string }}"
status_code: [201]
return_content: true
register: admin_jwt
delegate_to: localhost
no_log: true
- name: Get admin user teams
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/teams"
method: GET
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
X-Appwrite-JWT: "{{ admin_jwt.json.jwt }}"
status_code: [200]
return_content: true
register: admin_teams
delegate_to: localhost
- name: Create BAB project
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/projects"
method: POST
body_format: json
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
X-Appwrite-JWT: "{{ admin_jwt.json.jwt }}"
body:
projectId: "{{ appwrite_project }}"
name: "{{ appwrite_project_name | default('BAB') }}"
teamId: "{{ admin_teams.json.teams[0]['$id'] }}"
region: default
status_code: [201, 409]
return_content: true
delegate_to: localhost
no_log: false
- name: Register web platforms (CORS allowed origins)
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/projects/{{ appwrite_project }}/platforms"
method: POST
body_format: json
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
X-Appwrite-JWT: "{{ admin_jwt.json.jwt }}"
body:
type: web
name: "{{ item.name }}"
hostname: "{{ item.hostname }}"
status_code: [201]
return_content: true
loop: "{{ appwrite_web_platforms | default([]) }}"
delegate_to: localhost
- name: Create Ansible automation API key
ansible.builtin.uri:
url: "{{ appwrite_admin_uri }}/projects/{{ appwrite_project }}/keys"
method: POST
body_format: json
headers:
X-Appwrite-Project: console
X-Appwrite-Response-Format: "1.6"
X-Appwrite-JWT: "{{ admin_jwt.json.jwt }}"
body:
name: ansible-automation
scopes:
- databases.read
- databases.write
- collections.read
- collections.write
- attributes.read
- attributes.write
- indexes.read
- indexes.write
- documents.read
- documents.write
- users.read
- users.write
status_code: [201]
return_content: true
register: api_key
delegate_to: localhost
no_log: true
- name: Store API key secret in Vault
community.hashi_vault.vault_kv2_write:
path: oys/bab-appwrite-api-key
engine_mount_point: kv
data:
appwrite_api_key: "{{ api_key.json.secret }}"
delegate_to: localhost
no_log: true

View File

@@ -1,161 +0,0 @@
---
- name: Prepare Backend Host for BAB
hosts: bab1.mgmt.toal.ca
become: true
tags: deps
tasks:
# A FQDN system hostname causes NetworkManager to write the domain suffix as a
# 'search' entry in /etc/resolv.conf. Docker inherits this into every container.
# The Appwrite executor uses randomly-generated short hostnames to reach runtime
# containers via DNS; with a search domain present, those names get the suffix
# appended, upstream DNS returns SERVFAIL, and musl's resolver does not fall back
# to the absolute name — breaking function execution with curl error 6.
- name: Assert system hostname is not a FQDN
ansible.builtin.assert:
that: "'.' not in ansible_hostname"
fail_msg: >-
System hostname '{{ ansible_hostname }}' is a FQDN. Shorten it first:
hostnamectl set-hostname {{ ansible_hostname.split('.')[0] }}
- name: Check for search domain in /etc/resolv.conf
ansible.builtin.command:
cmd: grep -c '^search ' /etc/resolv.conf
register: resolv_search
changed_when: false
failed_when: false
- name: Assert no search domain in /etc/resolv.conf
ansible.builtin.assert:
that: resolv_search.rc != 0
fail_msg: >-
/etc/resolv.conf contains a 'search' domain. This is typically caused by a
FQDN system hostname. Shorten the hostname and reconnect the NM interface
to regenerate resolv.conf without the search entry.
- name: Update all packages to latest
ansible.builtin.dnf:
name: "*"
state: latest
update_only: true
- name: CodeReady Builder Repo Enabled
community.general.rhsm_repository:
name: "codeready-builder-for-rhel-9-{{ ansible_architecture }}-rpms"
state: enabled
- name: EPEL GPG Key installed
ansible.builtin.rpm_key:
key: https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9
state: present
fingerprint: 'FF8A D134 4597 106E CE81 3B91 8A38 72BF 3228 467C'
- name: Add Docker CE repository
ansible.builtin.yum_repository:
name: docker-ce
description: Docker CE Stable
baseurl: https://download.docker.com/linux/rhel/9/$basearch/stable
gpgcheck: true
gpgkey: https://download.docker.com/linux/rhel/gpg
enabled: true
- name: Dependencies are installed
ansible.builtin.dnf:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
- https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
state: present
- name: Ensure Docker service is enabled and started
ansible.builtin.systemd:
name: docker
enabled: true
state: started
- name: Ensure ansible user is in docker group
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: true
- name: Userspace setup
hosts: bab1.mgmt.toal.ca
vars:
appwrite_version: "1.8.1"
appwrite_dir: /home/ptoal/appwrite
appwrite_socket: /var/run/docker.sock
appwrite_web_port: 8080
appwrite_websecure_port: 8443
handlers:
- name: Restart appwrite service
ansible.builtin.systemd:
name: appwrite
state: restarted
become: true
tasks:
- name: Ensure appwrite image pulled from docker hub
community.docker.docker_image:
name: appwrite/appwrite
tag: "{{ appwrite_version }}"
source: pull
tags: image
- name: Ensure appwrite directory exists
ansible.builtin.file:
path: "{{ appwrite_dir }}"
state: directory
mode: '0755'
tags: configure
- name: Deploy Appwrite .env from template
ansible.builtin.template:
src: appwrite.env.j2
dest: "{{ appwrite_dir }}/.env"
mode: '0600'
notify: Restart appwrite service
tags: configure
- name: Download official production docker-compose.yml
ansible.builtin.get_url:
url: "https://appwrite.io/install/compose"
dest: "{{ appwrite_dir }}/docker-compose.yml"
mode: '0644'
notify: Restart appwrite service
tags: configure
- name: Apply site-specific customizations to docker-compose.yml
ansible.builtin.include_tasks:
file: tasks/patch_appwrite_compose.yml
apply:
tags: configure
tags: configure
- name: Deploy appwrite systemd unit
ansible.builtin.template:
src: appwrite.service.j2
dest: /etc/systemd/system/appwrite.service
mode: '0644'
become: true
notify: Restart appwrite service
tags: configure
- name: Enable and start appwrite systemd service
ansible.builtin.systemd:
name: appwrite
enabled: true
daemon_reload: true
state: started
become: true
tags: configure
- name: Prune dangling images after install
community.docker.docker_prune:
images: true
images_filters:
dangling: true
tags: image

View File

@@ -1,46 +0,0 @@
---
- name: Provision Beta Test User Accounts
hosts: appwrite
gather_facts: false
tasks:
- name: Load json for boats
ansible.builtin.set_fact:
boat_docs: "{{ lookup( 'ansible.builtin.file', 'files/database/boat.json' ) | ansible.builtin.from_json }}"
interval_template_docs: "{{ lookup( 'ansible.builtin.file', 'files/database/intervalTemplate.json' ) | ansible.builtin.from_json }}"
- name: Use Appwrite REST API to Load data
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases/{{ bab_database.id }}/collections/boat/documents"
method: POST
body_format: json
headers:
X-Appwrite-Response-Format: '{{ appwrite_response_format }}'
X-Appwrite-Project: '{{ appwrite_project }}'
X-Appwrite-Key: '{{ appwrite_api_key }}'
body:
documentId: "{{ item['$id'] }}"
data: "{{ item| ansible.utils.remove_keys(target=['$id','$databaseId','$collectionId']) }}"
status_code: [201, 409]
return_content: true
register: appwrite_api_result
loop: '{{ boat_docs.documents }}'
delegate_to: localhost
- name: Use Appwrite REST API to Load IntervalTemplate data
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases/{{ bab_database.id }}/collections/intervalTemplate/documents"
method: POST
body_format: json
headers:
X-Appwrite-Response-Format: '{{ appwrite_response_format }}'
X-Appwrite-Project: '{{ appwrite_project }}'
X-Appwrite-Key: '{{ appwrite_api_key }}'
body:
documentId: "{{ item['$id'] }}"
data: "{{ item| ansible.utils.remove_keys(target=['$id','$databaseId','$collectionId']) }}"
status_code: [201, 409]
return_content: true
register: appwrite_api_result
loop: '{{ interval_template_docs.documents }}'
delegate_to: localhost

View File

@@ -1,61 +0,0 @@
---
# TODO: This doesn't have any real idempotency. Can't compare current and desired states.
- name: Provision Database
hosts: appwrite
gather_facts: false
module_defaults:
ansible.builtin.uri:
body_format: json
headers:
X-Appwrite-Response-Format: '{{ appwrite_response_format | default("1.6") }}'
X-Appwrite-Project: '{{ appwrite_project }}'
X-Appwrite-Key: '{{ appwrite_api_key }}'
return_content: true
tasks:
- name: Use Appwrite REST API to create new database
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases"
method: POST
body:
databaseId: "{{ bab_database.id }}"
name: "{{ bab_database.name }}"
enabled: "{{ bab_database.enabled }}"
status_code: [201, 409]
register: appwrite_api_result
delegate_to: localhost
- name: Create Collections
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases/{{ bab_database.id }}/collections/"
method: POST
body:
collectionId: "{{ item.id }}"
name: "{{ item.name }}"
permissions: "{{ item.permissions }}"
status_code: [201, 409]
register: appwrite_api_result
loop: '{{ db_schema.collections }}'
delegate_to: localhost
# - name: Create Attributes
# ansible.builtin.debug:
# msg: "{{ lookup('ansible.builtin.template', 'appwrite_attribute_template.json.j2') }}"
# register: appwrite_api_result
# loop: "{{ bab_database.collections | subelements('attributes', skip_missing=True) }}"
# # delegate_to: localhost
- name: Create Attributes
ansible.builtin.uri:
url: >-
{{ appwrite_api_uri }}/databases/{{ bab_database.id }}/collections/{{ item[0].id }}/attributes/{{
(item[1].format is defined and item[1].format != '') | ternary(item[1].format, item[1].type) }}
method: POST
body: "{{ lookup('ansible.builtin.template', 'appwrite_attribute_template.json.j2') }}"
status_code: [202, 409]
register: appwrite_api_result
loop: "{{ db_schema.collections | subelements('attributes', skip_missing=True) }}"
delegate_to: localhost
# - name: Display response
# ansible.builtin.debug:
# var: appwrite_api_result

View File

@@ -0,0 +1,58 @@
---
# Provision BAB project secrets in Vault from the toallab Supabase admin instance.
#
# Reads admin-level secrets from supabase_admin_vault_path (kv/data/toallab/supabase),
# constructs the per-project Postgres URL, and writes the full set of app-facing secrets
# to supabase_vault_path (per-environment, e.g. kv/data/oys/dev/supabase).
#
# ASSUMED: kv/data/toallab/supabase contains keys: anon_key, service_key, db_password
# ASSUMED: supabase_api_url, supabase_db_host, supabase_db_port, supabase_db_name
# are set in host_vars for each supabase logical host.
#
# Usage:
# ansible-navigator run playbooks/provision_supabase_project.yml --mode stdout --limit supabase-dev
# ansible-navigator run playbooks/provision_supabase_project.yml --mode stdout --limit supabase-prod
- name: Provision Supabase project secrets in Vault
hosts: supabase
connection: local
gather_facts: false
tasks:
- name: Read Supabase admin secrets from Vault
community.hashi_vault.vault_kv2_get:
path: "{{ supabase_admin_vault_path | regex_replace('^kv/data/', '') }}"
engine_mount_point: kv
url: "{{ vault_addr }}"
register: _admin
no_log: true
- name: Verify required keys are present in admin vault
ansible.builtin.assert:
that:
- _admin.secret.anon_key | default('') | length > 0
- _admin.secret.service_key | default('') | length > 0
- _admin.secret.db_password | default('') | length > 0
fail_msg: >-
Missing required keys in {{ supabase_admin_vault_path }}.
Expected: anon_key, service_key, db_password.
no_log: true
- name: Write project secrets to Vault
community.hashi_vault.vault_kv2_write:
path: "{{ supabase_vault_path | regex_replace('^kv/data/', '') }}"
engine_mount_point: kv
url: "{{ vault_addr }}"
data:
url: "{{ supabase_api_url }}"
anon_key: "{{ _admin.secret.anon_key }}"
service_key: "{{ _admin.secret.service_key }}"
postgres_url: >-
postgresql://postgres:{{ _admin.secret.db_password }}@{{ supabase_db_host }}:{{ supabase_db_port }}/{{ supabase_db_name }}
no_log: true
- name: Report result
ansible.builtin.debug:
msg: >-
Project secrets written to {{ supabase_vault_path }}
(url, anon_key, service_key, postgres_url)

View File

@@ -1,31 +0,0 @@
---
- name: Provision Beta Test User Accounts
hosts: appwrite:&prod
gather_facts: false
tasks:
- name: Use Appwrite REST API to create new user
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/users/argon2"
method: POST
body_format: json
headers:
Content-Type: application/json
X-Appwrite-Response-Format: '{{ appwrite_response_format | default("1.6") }}'
X-Appwrite-Project: '{{ appwrite_project }}'
X-Appwrite-Key: '{{ appwrite_api_key }}'
body:
userId: "{{ item.userid }}"
password: "{{ item.password }}"
email: "{{ item.email | default(omit) }}"
name: "{{ item.name }}"
status_code: [201, 409]
return_content: true
register: appwrite_api_result
loop: '{{ bab_users }}'
delegate_to: localhost
no_log: true
- name: Display response
ansible.builtin.debug:
var: appwrite_api_result

View File

@@ -1,52 +0,0 @@
---
- name: Gather Information about Database
hosts: appwrite:&dev
gather_facts: false
module_defaults:
ansible.builtin.uri:
body_format: json
headers:
X-Appwrite-Response-Format: '{{ appwrite_response_format }}'
X-Appwrite-Project: '{{ appwrite_project }}'
X-Appwrite-Key: '{{ appwrite_api_key }}'
return_content: true
tasks:
- name: Get Users
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/users"
method: GET
register: appwrite_api_result
delegate_to: localhost
- name: Display response
ansible.builtin.debug:
var: appwrite_api_result
- name: Get database info
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases/{{ bab_database.id }}"
method: GET
register: appwrite_api_result
delegate_to: localhost
- name: Get collection info
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases/{{ bab_database.id }}/collections"
method: GET
register: appwrite_collections
delegate_to: localhost
- name: Get documents from each table
ansible.builtin.uri:
url: "{{ appwrite_api_uri }}/databases/{{ bab_database.id }}/collections/{{ item['$id'] }}/documents"
method: GET
loop: "{{ appwrite_collections.json.collections }}"
delegate_to: localhost
register: document_results
- name: Save Data
ansible.builtin.copy:
dest: 'files/database/{{ item.item.name }}.json'
content: '{{ item.json }}'
loop: "{{ document_results.results }}"
delegate_to: localhost

View File

@@ -1,64 +1,51 @@
---
- name: Sync Supabase secrets to Gitea repo variables
hosts: localhost
hosts: supabase
connection: local
gather_facts: false
tasks:
- name: Read Supabase dev secrets from Vault
- name: Construct env file content
ansible.builtin.set_fact:
_supabase_dev: "{{ lookup('community.hashi_vault.hashi_vault',
'secret=oys/dev/supabase url=' + vault_addr + ' engine_mount_point=kv') }}"
no_log: true
_env_file: |
SUPABASE_URL={{ supabase.url }}
SUPABASE_ANON_KEY={{ supabase.anon_key }}
no_log: false
- name: Read Supabase prod secrets from Vault
ansible.builtin.set_fact:
_supabase_prod: "{{ lookup('community.hashi_vault.hashi_vault',
'secret=oys/prod/supabase url=' + vault_addr + ' engine_mount_point=kv') }}"
no_log: true
- name: Read Gitea API token from Vault
ansible.builtin.set_fact:
_gitea_token: "{{ lookup('community.hashi_vault.hashi_vault',
'secret=oys/shared/infra/gitea_token url=' + vault_addr + ' engine_mount_point=kv') }}"
no_log: true
- name: Construct ENV_FILE_DEV content
ansible.builtin.set_fact:
_env_file_dev: |
SUPABASE_URL={{ _supabase_dev.url }}
SUPABASE_ANON_KEY={{ _supabase_dev.anon_key }}
no_log: true
- name: Construct ENV_FILE_PROD content
ansible.builtin.set_fact:
_env_file_prod: |
SUPABASE_URL={{ _supabase_prod.url }}
SUPABASE_ANON_KEY={{ _supabase_prod.anon_key }}
no_log: true
- name: Update ENV_FILE_DEV Gitea variable
- name: Check if Gitea variable exists
ansible.builtin.uri:
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_owner }}/{{ gitea_repo }}/actions/variables/ENV_FILE_DEV"
method: PUT
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_owner }}/{{ gitea_repo }}/actions/variables/{{ gitea_variable_name }}"
method: GET
headers:
Authorization: "token {{ _gitea_token.value }}"
Authorization: "token {{ gitea_token.token }}"
status_code: [200, 404]
register: _gitea_var_check
no_log: true
- name: Create Gitea variable
ansible.builtin.uri:
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_owner }}/{{ gitea_repo }}/actions/variables/{{ gitea_variable_name }}"
method: POST
headers:
Authorization: "token {{ gitea_token.token }}"
Content-Type: application/json
body_format: json
body:
value: "{{ _env_file_dev }}"
status_code: [201, 204]
value: "{{ _env_file }}"
status_code: [201]
when: _gitea_var_check.status == 404
no_log: true
- name: Update ENV_FILE_PROD Gitea variable
- name: Update Gitea variable
ansible.builtin.uri:
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_owner }}/{{ gitea_repo }}/actions/variables/ENV_FILE_PROD"
url: "{{ gitea_base_url }}/api/v1/repos/{{ gitea_owner }}/{{ gitea_repo }}/actions/variables/{{ gitea_variable_name }}"
method: PUT
headers:
Authorization: "token {{ _gitea_token.value }}"
Authorization: "token {{ gitea_token.token }}"
Content-Type: application/json
body_format: json
body:
value: "{{ _env_file_prod }}"
status_code: [201, 204]
value: "{{ _env_file }}"
status_code: [204]
when: _gitea_var_check.status == 200
no_log: true

View File

@@ -1,85 +0,0 @@
---
# Applies site-specific customizations to docker-compose.yml after it has been
# written by the Appwrite upgrade container or downloaded fresh during install.
#
# Required variables (define in calling play):
# appwrite_dir - absolute path to the appwrite directory on the host
# appwrite_socket - host path to the container socket
# appwrite_web_port - host port to map to container port 80 (default 8080)
# appwrite_websecure_port - host port to map to container port 443 (default 8443)
# appwrite_traefik_trusted_ips - CIDRs Traefik trusts for X-Forwarded-For (default 0.0.0.0/0)
#
# Notifies: "Restart appwrite service" — must be defined in the calling play.
- name: Pin Traefik image to minimum compatible version
# traefik:2.11 (without patch) is incompatible with Docker Engine >= 29.
ansible.builtin.replace:
path: "{{ appwrite_dir }}/docker-compose.yml"
regexp: 'image: traefik:.*'
replace: "image: traefik:{{ appwrite_traefik_version | default('2.11.31') }}"
notify: Restart appwrite service
- name: Replace dev build image with official appwrite image
# The downloaded compose may contain image: appwrite-dev with a build: stanza
# for local source builds. Replace with the pinned official image.
ansible.builtin.replace:
path: "{{ appwrite_dir }}/docker-compose.yml"
regexp: 'image: appwrite-dev'
replace: "image: appwrite/appwrite:{{ appwrite_version }}"
notify: Restart appwrite service
- name: Remap traefik HTTP port
ansible.builtin.replace:
path: "{{ appwrite_dir }}/docker-compose.yml"
regexp: '- "?80:80"?'
replace: "- {{ appwrite_web_port }}:80"
notify: Restart appwrite service
- name: Remap traefik HTTPS port
ansible.builtin.replace:
path: "{{ appwrite_dir }}/docker-compose.yml"
regexp: '- "?443:443"?'
replace: "- {{ appwrite_websecure_port }}:443"
notify: Restart appwrite service
- name: Trust X-Forwarded-For from HAProxy on appwrite_web entrypoint
ansible.builtin.lineinfile:
path: "{{ appwrite_dir }}/docker-compose.yml"
line: " - --entrypoints.appwrite_web.forwardedHeaders.trustedIPs={{ appwrite_traefik_trusted_ips | default('0.0.0.0/0') }}"
insertafter: '.*entrypoints\.appwrite_web\.address.*'
state: present
notify: Restart appwrite service
- name: Accept PROXY protocol v2 from HAProxy on appwrite_web entrypoint
ansible.builtin.lineinfile:
path: "{{ appwrite_dir }}/docker-compose.yml"
line: " - --entrypoints.appwrite_web.proxyProtocol.trustedIPs={{ appwrite_traefik_trusted_ips | default('0.0.0.0/0') }}"
insertafter: '.*entrypoints\.appwrite_web\.address.*'
state: present
notify: Restart appwrite service
- name: Trust X-Forwarded-For from HAProxy on appwrite_websecure entrypoint
ansible.builtin.lineinfile:
path: "{{ appwrite_dir }}/docker-compose.yml"
line: " - --entrypoints.appwrite_websecure.forwardedHeaders.trustedIPs={{ appwrite_traefik_trusted_ips | default('0.0.0.0/0') }}"
insertafter: '.*entrypoints\.appwrite_websecure\.address.*'
state: present
notify: Restart appwrite service
- name: Accept PROXY protocol v2 from HAProxy on appwrite_websecure entrypoint
ansible.builtin.lineinfile:
path: "{{ appwrite_dir }}/docker-compose.yml"
line: " - --entrypoints.appwrite_websecure.proxyProtocol.trustedIPs={{ appwrite_traefik_trusted_ips | default('0.0.0.0/0') }}"
insertafter: '.*entrypoints\.appwrite_websecure\.address.*'
state: present
notify: Restart appwrite service
- name: Add host tmp mount to openruntimes-executor for docker file sharing
# Inserts after the last occurrence of appwrite-builds:/storage/builds:rw,
# which is in the openruntimes-executor volumes section.
ansible.builtin.lineinfile:
path: "{{ appwrite_dir }}/docker-compose.yml"
line: " - {{ appwrite_dir }}/tmp:/tmp:z"
insertafter: "appwrite-builds:/storage/builds:rw"
state: present
notify: Restart appwrite service

View File

@@ -1,79 +0,0 @@
---
# Performs one upgrade+migrate cycle for a single Appwrite target version.
# Called in a loop from upgrade_appwrite.yml with loop_var: appwrite_target_version.
- name: "Pull appwrite/appwrite image:{{ appwrite_target_version }}"
community.docker.docker_image:
name: appwrite/appwrite
tag: "{{ appwrite_target_version }}"
source: pull
- name: "Run Appwrite upgrade container for {{ appwrite_target_version }}"
# Runs with -i so stdin can answer all interactive prompts.
# Prompt order: overwrite confirmation, HTTP port, HTTPS port, API key,
# Appwrite hostname, CNAME hostname, SSL email — all accept defaults except overwrite.
# The container writes docker-compose.yml then attempts docker compose up internally;
# that step fails because we manage the socket/service lifecycle ourselves.
# We only fail this task if the compose file backup was not created (file not written).
ansible.builtin.command:
argv:
- docker
- run
- --rm
- -i
- --volume
- "{{ appwrite_socket }}:/var/run/docker.sock"
- --volume
- "{{ appwrite_dir }}:/usr/src/code/appwrite:rw"
- --entrypoint=upgrade
- "appwrite/appwrite:{{ appwrite_target_version }}"
stdin: "y\n\n\n\n\n\n\n"
register: upgrade_container_result
changed_when: true
failed_when: "'creating backup' not in upgrade_container_result.stdout"
- name: Re-apply site customizations after upgrade container rewrote docker-compose.yml
ansible.builtin.include_tasks: patch_appwrite_compose.yml
- name: "Bring up Appwrite stack at {{ appwrite_target_version }}"
ansible.builtin.command:
argv:
- docker
- compose
- up
- -d
chdir: "{{ appwrite_dir }}"
changed_when: true
- name: Wait for appwrite container to be running
ansible.builtin.command:
argv:
- docker
- compose
- ps
- --status
- running
- --services
chdir: "{{ appwrite_dir }}"
register: running_services
until: "'appwrite' in running_services.stdout"
retries: 30
delay: 10
changed_when: false
- name: "Run database migration for {{ appwrite_target_version }}"
ansible.builtin.command:
argv:
- docker
- compose
- exec
- -T
- appwrite
- migrate
chdir: "{{ appwrite_dir }}"
register: migration_result
changed_when: true
- name: Show migration output
ansible.builtin.debug:
var: migration_result.stdout_lines

View File

@@ -1,179 +0,0 @@
# Appwrite environment configuration
# Generated by Ansible — do not edit manually on the host
# Secrets come from vault-encrypted group_vars or secrets.yml
_APP_ENV={{ appwrite_env | default('production') }}
_APP_LOCALE={{ appwrite_locale | default('en') }}
_APP_OPTIONS_ABUSE={{ appwrite_options_abuse | default('enabled') }}
_APP_OPTIONS_FORCE_HTTPS={{ appwrite_options_force_https | default('enabled') }}
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS={{ appwrite_options_functions_force_https | default('enabled') }}
_APP_OPTIONS_ROUTER_FORCE_HTTPS={{ appwrite_options_router_force_https | default('disabled') }}
_APP_OPTIONS_ROUTER_PROTECTION={{ appwrite_options_router_protection | default('disabled') }}
# Security — vault required
_APP_OPENSSL_KEY_V1={{ vault_appwrite_openssl_key }}
# Domains
_APP_DOMAIN={{ appwrite_domain }}
_APP_DOMAIN_CNAME={{ appwrite_domain_cname | default(appwrite_domain) }}
_APP_CUSTOM_DOMAIN_DENY_LIST={{ appwrite_custom_domain_deny_list | default('example.com,test.com,app.example.com') }}
_APP_DOMAIN_FUNCTIONS={{ appwrite_domain_functions }}
_APP_DOMAIN_SITES={{ appwrite_domain_sites | default('sites.localhost') }}
_APP_DOMAIN_TARGET_CNAME={{ appwrite_domain_target_cname | default(appwrite_domain) }}
_APP_DOMAIN_TARGET_A={{ appwrite_domain_target_a | default('127.0.0.1') }}
_APP_DOMAIN_TARGET_AAAA={{ appwrite_domain_target_aaaa | default('::1') }}
_APP_DOMAIN_TARGET_CAA={{ appwrite_domain_target_caa | default('') }}
_APP_DNS={{ appwrite_dns | default('8.8.8.8') }}
# Console access
_APP_CONSOLE_WHITELIST_ROOT={{ appwrite_console_whitelist_root | default('enabled') }}
_APP_CONSOLE_WHITELIST_EMAILS={{ appwrite_console_whitelist_emails | default('') }}
_APP_CONSOLE_WHITELIST_IPS={{ appwrite_console_whitelist_ips | default('') }}
_APP_CONSOLE_HOSTNAMES={{ appwrite_console_hostnames | default('') }}
# System
_APP_SYSTEM_EMAIL_NAME={{ appwrite_system_email_name | default('Appwrite') }}
_APP_SYSTEM_EMAIL_ADDRESS={{ appwrite_system_email_address }}
_APP_SYSTEM_TEAM_EMAIL={{ appwrite_system_team_email | default(appwrite_system_email_address) }}
_APP_SYSTEM_RESPONSE_FORMAT={{ appwrite_system_response_format | default('') }}
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS={{ appwrite_system_security_email_address | default(appwrite_system_email_address) }}
_APP_EMAIL_SECURITY={{ appwrite_email_security | default('') }}
_APP_EMAIL_CERTIFICATES={{ appwrite_email_certificates | default('') }}
_APP_USAGE_STATS={{ appwrite_usage_stats | default('enabled') }}
_APP_LOGGING_PROVIDER={{ appwrite_logging_provider | default('') }}
_APP_LOGGING_CONFIG={{ appwrite_logging_config | default('') }}
_APP_USAGE_AGGREGATION_INTERVAL={{ appwrite_usage_aggregation_interval | default(30) }}
_APP_USAGE_TIMESERIES_INTERVAL={{ appwrite_usage_timeseries_interval | default(30) }}
_APP_USAGE_DATABASE_INTERVAL={{ appwrite_usage_database_interval | default(900) }}
_APP_WORKER_PER_CORE={{ appwrite_worker_per_core | default(6) }}
_APP_CONSOLE_SESSION_ALERTS={{ appwrite_console_session_alerts | default('disabled') }}
_APP_COMPRESSION_ENABLED={{ appwrite_compression_enabled | default('enabled') }}
_APP_COMPRESSION_MIN_SIZE_BYTES={{ appwrite_compression_min_size_bytes | default(1024) }}
# Redis
_APP_REDIS_HOST={{ appwrite_redis_host | default('redis') }}
_APP_REDIS_PORT={{ appwrite_redis_port | default(6379) }}
_APP_REDIS_USER={{ appwrite_redis_user | default('') }}
_APP_REDIS_PASS={{ appwrite_redis_pass | default('') }}
# Database — vault required
_APP_DB_HOST={{ appwrite_db_host | default('mariadb') }}
_APP_DB_PORT={{ appwrite_db_port | default(3306) }}
_APP_DB_SCHEMA={{ appwrite_db_schema | default('appwrite') }}
_APP_DB_USER={{ appwrite_db_user | default('appwrite') }}
_APP_DB_PASS={{ vault_appwrite_db_pass }}
_APP_DB_ROOT_PASS={{ vault_appwrite_db_root_pass }}
# Stats/metrics
_APP_INFLUXDB_HOST={{ appwrite_influxdb_host | default('influxdb') }}
_APP_INFLUXDB_PORT={{ appwrite_influxdb_port | default(8086) }}
_APP_STATSD_HOST={{ appwrite_statsd_host | default('telegraf') }}
_APP_STATSD_PORT={{ appwrite_statsd_port | default(8125) }}
# SMTP — vault required for password
_APP_SMTP_HOST={{ appwrite_smtp_host }}
_APP_SMTP_PORT={{ appwrite_smtp_port | default(587) }}
_APP_SMTP_SECURE={{ appwrite_smtp_secure | default('true') }}
_APP_SMTP_USERNAME={{ appwrite_smtp_username }}
_APP_SMTP_PASSWORD={{ vault_appwrite_smtp_password }}
# SMS
_APP_SMS_PROVIDER={{ appwrite_sms_provider | default('') }}
_APP_SMS_FROM={{ appwrite_sms_from | default('') }}
# Storage
_APP_STORAGE_LIMIT={{ appwrite_storage_limit | default(30000000) }}
_APP_STORAGE_PREVIEW_LIMIT={{ appwrite_storage_preview_limit | default(20000000) }}
_APP_STORAGE_ANTIVIRUS={{ appwrite_storage_antivirus | default('disabled') }}
_APP_STORAGE_ANTIVIRUS_HOST={{ appwrite_storage_antivirus_host | default('clamav') }}
_APP_STORAGE_ANTIVIRUS_PORT={{ appwrite_storage_antivirus_port | default(3310) }}
_APP_STORAGE_DEVICE={{ appwrite_storage_device | default('local') }}
_APP_STORAGE_S3_ACCESS_KEY={{ appwrite_storage_s3_access_key | default('') }}
_APP_STORAGE_S3_SECRET={{ appwrite_storage_s3_secret | default('') }}
_APP_STORAGE_S3_REGION={{ appwrite_storage_s3_region | default('us-east-1') }}
_APP_STORAGE_S3_BUCKET={{ appwrite_storage_s3_bucket | default('') }}
_APP_STORAGE_S3_ENDPOINT={{ appwrite_storage_s3_endpoint | default('') }}
_APP_STORAGE_DO_SPACES_ACCESS_KEY={{ appwrite_storage_do_spaces_access_key | default('') }}
_APP_STORAGE_DO_SPACES_SECRET={{ appwrite_storage_do_spaces_secret | default('') }}
_APP_STORAGE_DO_SPACES_REGION={{ appwrite_storage_do_spaces_region | default('us-east-1') }}
_APP_STORAGE_DO_SPACES_BUCKET={{ appwrite_storage_do_spaces_bucket | default('') }}
_APP_STORAGE_BACKBLAZE_ACCESS_KEY={{ appwrite_storage_backblaze_access_key | default('') }}
_APP_STORAGE_BACKBLAZE_SECRET={{ appwrite_storage_backblaze_secret | default('') }}
_APP_STORAGE_BACKBLAZE_REGION={{ appwrite_storage_backblaze_region | default('us-west-004') }}
_APP_STORAGE_BACKBLAZE_BUCKET={{ appwrite_storage_backblaze_bucket | default('') }}
_APP_STORAGE_LINODE_ACCESS_KEY={{ appwrite_storage_linode_access_key | default('') }}
_APP_STORAGE_LINODE_SECRET={{ appwrite_storage_linode_secret | default('') }}
_APP_STORAGE_LINODE_REGION={{ appwrite_storage_linode_region | default('eu-central-1') }}
_APP_STORAGE_LINODE_BUCKET={{ appwrite_storage_linode_bucket | default('') }}
_APP_STORAGE_WASABI_ACCESS_KEY={{ appwrite_storage_wasabi_access_key | default('') }}
_APP_STORAGE_WASABI_SECRET={{ appwrite_storage_wasabi_secret | default('') }}
_APP_STORAGE_WASABI_REGION={{ appwrite_storage_wasabi_region | default('eu-central-1') }}
_APP_STORAGE_WASABI_BUCKET={{ appwrite_storage_wasabi_bucket | default('') }}
# Functions / Compute
_APP_FUNCTIONS_SIZE_LIMIT={{ appwrite_functions_size_limit | default(30000000) }}
_APP_COMPUTE_SIZE_LIMIT={{ appwrite_compute_size_limit | default(30000000) }}
_APP_FUNCTIONS_BUILD_SIZE_LIMIT={{ appwrite_functions_build_size_limit | default(2000000000) }}
_APP_FUNCTIONS_TIMEOUT={{ appwrite_functions_timeout | default(900) }}
_APP_FUNCTIONS_BUILD_TIMEOUT={{ appwrite_functions_build_timeout | default(900) }}
_APP_COMPUTE_BUILD_TIMEOUT={{ appwrite_compute_build_timeout | default(900) }}
_APP_FUNCTIONS_CONTAINERS={{ appwrite_functions_containers | default(10) }}
_APP_FUNCTIONS_CPUS={{ appwrite_functions_cpus | default(0) }}
_APP_COMPUTE_CPUS={{ appwrite_compute_cpus | default(0) }}
_APP_FUNCTIONS_MEMORY={{ appwrite_functions_memory | default(0) }}
_APP_COMPUTE_MEMORY={{ appwrite_compute_memory | default(0) }}
_APP_FUNCTIONS_MEMORY_SWAP={{ appwrite_functions_memory_swap | default(0) }}
_APP_FUNCTIONS_RUNTIMES={{ appwrite_functions_runtimes | default('node-16.0,php-8.0,python-3.9,ruby-3.0,deno-1.40') }}
_APP_EXECUTOR_SECRET={{ vault_appwrite_executor_secret }}
_APP_EXECUTOR_HOST={{ appwrite_executor_host | default('http://exc1/v1') }}
_APP_BROWSER_HOST={{ appwrite_browser_host | default('http://appwrite-browser:3000/v1') }}
_APP_EXECUTOR_RUNTIME_NETWORK={{ appwrite_executor_runtime_network | default('runtimes') }}
_APP_FUNCTIONS_ENVS={{ appwrite_functions_envs | default('node-16.0,php-7.4,python-3.9,ruby-3.0') }}
_APP_FUNCTIONS_INACTIVE_THRESHOLD={{ appwrite_functions_inactive_threshold | default(60) }}
_APP_COMPUTE_INACTIVE_THRESHOLD={{ appwrite_compute_inactive_threshold | default(60) }}
DOCKERHUB_PULL_USERNAME={{ appwrite_dockerhub_username | default('') }}
DOCKERHUB_PULL_PASSWORD={{ appwrite_dockerhub_password | default('') }}
DOCKERHUB_PULL_EMAIL={{ appwrite_dockerhub_email | default('') }}
OPEN_RUNTIMES_NETWORK={{ appwrite_open_runtimes_network | default('runtimes') }}
_APP_FUNCTIONS_RUNTIMES_NETWORK={{ appwrite_functions_runtimes_network | default('runtimes') }}
_APP_COMPUTE_RUNTIMES_NETWORK={{ appwrite_compute_runtimes_network | default('runtimes') }}
_APP_DOCKER_HUB_USERNAME={{ appwrite_docker_hub_username | default('') }}
_APP_DOCKER_HUB_PASSWORD={{ appwrite_docker_hub_password | default('') }}
_APP_FUNCTIONS_MAINTENANCE_INTERVAL={{ appwrite_functions_maintenance_interval | default(3600) }}
_APP_COMPUTE_MAINTENANCE_INTERVAL={{ appwrite_compute_maintenance_interval | default(3600) }}
# Sites
_APP_SITES_TIMEOUT={{ appwrite_sites_timeout | default(900) }}
_APP_SITES_RUNTIMES={{ appwrite_sites_runtimes | default('static-1,node-22,flutter-3.29') }}
# VCS / GitHub — vault required for secrets
_APP_VCS_GITHUB_APP_NAME={{ appwrite_vcs_github_app_name }}
_APP_VCS_GITHUB_PRIVATE_KEY="{{ vault_appwrite_github_private_key }}"
_APP_VCS_GITHUB_APP_ID={{ appwrite_vcs_github_app_id }}
_APP_VCS_GITHUB_CLIENT_ID={{ appwrite_vcs_github_client_id }}
_APP_VCS_GITHUB_CLIENT_SECRET={{ vault_appwrite_github_client_secret }}
_APP_VCS_GITHUB_WEBHOOK_SECRET="{{ vault_appwrite_github_webhook_secret }}"
# Maintenance
_APP_MAINTENANCE_INTERVAL={{ appwrite_maintenance_interval | default(86400) }}
_APP_MAINTENANCE_DELAY={{ appwrite_maintenance_delay | default(0) }}
_APP_MAINTENANCE_START_TIME={{ appwrite_maintenance_start_time | default('00:00') }}
_APP_MAINTENANCE_RETENTION_CACHE={{ appwrite_maintenance_retention_cache | default(2592000) }}
_APP_MAINTENANCE_RETENTION_EXECUTION={{ appwrite_maintenance_retention_execution | default(1209600) }}
_APP_MAINTENANCE_RETENTION_AUDIT={{ appwrite_maintenance_retention_audit | default(1209600) }}
_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE={{ appwrite_maintenance_retention_audit_console | default(15778800) }}
_APP_MAINTENANCE_RETENTION_ABUSE={{ appwrite_maintenance_retention_abuse | default(86400) }}
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY={{ appwrite_maintenance_retention_usage_hourly | default(8640000) }}
_APP_MAINTENANCE_RETENTION_SCHEDULES={{ appwrite_maintenance_retention_schedules | default(86400) }}
# GraphQL
_APP_GRAPHQL_MAX_BATCH_SIZE={{ appwrite_graphql_max_batch_size | default(10) }}
_APP_GRAPHQL_MAX_COMPLEXITY={{ appwrite_graphql_max_complexity | default(250) }}
_APP_GRAPHQL_MAX_DEPTH={{ appwrite_graphql_max_depth | default(3) }}
# Migrations
_APP_MIGRATIONS_FIREBASE_CLIENT_ID={{ appwrite_migrations_firebase_client_id | default('') }}
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET={{ appwrite_migrations_firebase_client_secret | default('') }}
# AI
_APP_ASSISTANT_OPENAI_API_KEY={{ appwrite_assistant_openai_api_key | default('') }}

View File

@@ -1,16 +0,0 @@
[Unit]
Description=Appwrite stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ appwrite_dir }}
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target

View File

@@ -1,60 +0,0 @@
---
- name: Upgrade Appwrite
hosts: bab1.mgmt.toal.ca
vars:
appwrite_dir: /home/ptoal/appwrite
appwrite_socket: /var/run/docker.sock
appwrite_web_port: 8080
appwrite_websecure_port: 8443
# Sequential upgrade path: cannot skip minor versions.
upgrade_path:
- "1.6.2"
- "1.7.4"
- "1.8.1"
tasks:
- name: Get current Appwrite container info
community.docker.docker_container_info:
name: appwrite
register: appwrite_container_info
- name: Set current Appwrite version fact
ansible.builtin.set_fact:
current_appwrite_version: >-
{{ appwrite_container_info.container.Config.Image.split(':') | last
if appwrite_container_info.exists
else '0.0.0' }}
- name: Show current Appwrite version
ansible.builtin.debug:
msg: "Current Appwrite version: {{ current_appwrite_version }}"
- name: Back up MariaDB data volume before upgrade
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --volume
- appwrite-mariadb:/data:ro
- --volume
- "{{ appwrite_dir }}:/backup"
- alpine
- tar
- czf
- /backup/mariadb-backup-pre-upgrade.tar.gz
- /data
changed_when: true
- name: Upgrade through each intermediate version
ansible.builtin.include_tasks: tasks/upgrade_appwrite_step.yml
loop: "{{ upgrade_path }}"
loop_control:
loop_var: appwrite_target_version
when: appwrite_target_version is version(current_appwrite_version, '>')
- name: Prune dangling images left by upgrade
community.docker.docker_prune:
images: true
images_filters:
dangling: true