Got things working. Added CLAUDE instructions

This commit is contained in:
2026-03-14 13:48:23 -04:00
parent 2ab06e86f8
commit 1622177ba0
17 changed files with 1190 additions and 80 deletions

View File

@@ -2,6 +2,7 @@
- name: Prepare Backend Host for BAB
hosts: bab1.mgmt.toal.ca
become: true
tags: deps
tasks:
- name: Update all packages to latest
@@ -55,41 +56,78 @@
- name: Userspace setup
hosts: bab1.mgmt.toal.ca
vars:
appwrite_version: "1.7.4"
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: Download official docker-compose.yml for Appwrite {{ appwrite_version }}
- 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://raw.githubusercontent.com/appwrite/appwrite/{{ appwrite_version }}/docker-compose.yml"
url: "https://appwrite.io/install/compose"
dest: "{{ appwrite_dir }}/docker-compose.yml"
mode: '0644'
force: true
notify: Restart appwrite service
tags: configure
- name: Apply site-specific customizations to docker-compose.yml
ansible.builtin.include_tasks: tasks/patch_appwrite_compose.yml
ansible.builtin.include_tasks:
file: tasks/patch_appwrite_compose.yml
apply:
tags: configure
tags: configure
- name: Start Appwrite stack
ansible.builtin.command:
argv:
- docker
- compose
- up
- -d
chdir: "{{ appwrite_dir }}"
changed_when: true
- 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

@@ -0,0 +1,43 @@
---
- name: Install Prometheus Node Exporter
hosts: bab1.mgmt.toal.ca
become: true
tasks:
- name: Pull node-exporter image
community.docker.docker_image:
name: quay.io/prometheus/node-exporter
tag: "v{{ node_exporter_version }}"
source: pull
tags: image
- name: Run node-exporter container
community.docker.docker_container:
name: node-exporter
image: "quay.io/prometheus/node-exporter:v{{ node_exporter_version }}"
state: started
restart_policy: unless-stopped
# Host network gives accurate interface metrics without NAT
network_mode: host
# Required for per-process CPU/memory metrics
pid_mode: host
# Disable SELinux relabelling so we can bind-mount / read-only
# without risking a recursive chcon on the entire filesystem
security_opts:
- label=disable
capabilities:
- SYS_TIME
volumes:
- /:/host:ro,rslave
command:
- --path.rootfs=/host
- --web.listen-address=:{{ node_exporter_port }}
tags: configure
- name: Allow node-exporter port through firewalld
ansible.posix.firewalld:
port: "{{ node_exporter_port }}/tcp"
permanent: true
state: enabled
immediate: true
tags: configure

View File

@@ -7,18 +7,72 @@
# 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,
@@ -28,3 +82,4 @@
line: " - {{ appwrite_dir }}/tmp:/tmp:z"
insertafter: "appwrite-builds:/storage/builds:rw"
state: present
notify: Restart appwrite service

View File

@@ -0,0 +1,179 @@
# 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('appwrite_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('appwrite_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

@@ -0,0 +1,16 @@
[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

@@ -52,3 +52,9 @@
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