Compare commits

...

6 Commits

Author SHA1 Message Date
cf4bfc971c Add node exporter 2026-03-14 13:49:16 -04:00
1622177ba0 Got things working. Added CLAUDE instructions 2026-03-14 13:48:23 -04:00
2ab06e86f8 Fix lint 2026-03-14 08:48:46 -04:00
e3876e9271 Fix upgrade 2026-03-14 08:48:30 -04:00
5c53dbdaf2 Add CLAUDE files. Add upgrade 2026-03-13 23:48:33 -04:00
38a40ea174 No longer need port for webhook 2025-11-17 17:17:30 -05:00
23 changed files with 1432 additions and 866 deletions

View File

@@ -0,0 +1,12 @@
---
name: ansible-idempotency-reviewer
description: Reviews Ansible playbooks for idempotency issues. Use when adding new tasks or before running playbooks against production. Flags POST-only API calls missing 409 handling, uri tasks without state checks, shell/command tasks without creates/removes/changed_when, and non-idempotent register/when patterns.
---
You are an Ansible idempotency expert. When given a playbook or task list:
1. Identify tasks that will fail or produce unintended side effects on re-runs
2. For `ansible.builtin.uri` POST calls, check for `status_code: [201, 409]` or equivalent guard
3. Flag `ansible.builtin.shell`/`command` tasks lacking `creates:`, `removes:`, or `changed_when: false`
4. Suggest idempotent alternatives for each flagged task
5. Note tasks that are inherently non-idempotent and require manual intervention

View File

@@ -0,0 +1,8 @@
Write a session handoff file for the current session.
Steps:
1. Read `templates/claude-templates.md` and find the Session Handoff template (Template 4). Use the Light Handoff if this is a small project (under 5 sessions), Full Handoff otherwise.
2. Fill in every field based on what was accomplished in this session. Be specific — include exact file paths for every output, exact numbers discovered, and conditional logic established.
3. Write the handoff to `./docs/summaries/handoff-[today's date]-[topic].md`.
4. If a previous handoff file exists in `./docs/summaries/`, move it to `./docs/archive/handoffs/`.
5. Tell me the file path of the new handoff and summarize what it contains.

View File

@@ -0,0 +1,13 @@
Process an input document into a structured source summary.
Steps:
1. Read `templates/claude-templates.md` and find the Source Document Summary template (Template 1). Use the Light Source Summary if this is a small project (under 5 sessions), Full Source Summary otherwise.
2. Read the document at: $ARGUMENTS
3. Extract all information into the template format. Pay special attention to:
- EXACT numbers — do not round or paraphrase
- Requirements in IF/THEN/BUT/EXCEPT format
- Decisions with rationale and rejected alternatives
- Open questions marked as OPEN, ASSUMED, or MISSING
4. Write the summary to `./docs/summaries/source-[filename].md`.
5. Move the original document to `./docs/archive/`.
6. Tell me: what was extracted, what's unclear, and what needs follow-up.

View File

@@ -0,0 +1,13 @@
Report on the current project state.
Steps:
1. Read `./docs/summaries/00-project-brief.md` for project context.
2. Find and read the latest `handoff-*.md` file in `./docs/summaries/` for current state.
3. List all files in `./docs/summaries/` to understand what's been processed.
4. Report:
- **Project:** name and type from the project brief
- **Current phase:** based on the project phase tracker
- **Last session:** what was accomplished (from the latest handoff)
- **Next steps:** what the next session should do (from the latest handoff)
- **Open questions:** anything unresolved
- **Summary file count:** how many files in docs/summaries/ (warn if approaching 15)

1
.gitignore vendored
View File

@@ -271,3 +271,4 @@ secrets.yml
.swp
# Ignore vscode
.vscode/
.ansible/.lock

54
CLAUDE.md Normal file
View File

@@ -0,0 +1,54 @@
# CLAUDE.md
## Session Start
Read the latest handoff in docs/summaries/ if one exists. Load only the files that handoff references — not all summaries. If no handoff exists, ask: what is the goal this session and what is the target deliverable.
Before starting work, state: what you understand the project state to be, what you plan to do this session, and any open questions.
## Identity
You work with Patrick, a Solutions Architect. This repo is Ansible automation for the BAB (Borrow a Boat) backend — an Appwrite-based service on a single RHEL 9 host (`bab1.mgmt.toal.ca`). Automation runs via Ansible Automation Platform (AAP) in production, ansible-navigator locally. Patrick has expert-level Ansible knowledge — do not explain Ansible basics.
## Project
**Repo:** Ansible playbooks and EDA rulebooks managing a full Appwrite backend lifecycle on RHEL 9.
**Host:** `bab1.mgmt.toal.ca`
**Run locally:** `ansible-navigator run playbooks/<name>.yml --mode stdout`
**Run with extra vars:** `ansible-navigator run playbooks/deploy_application.yml --mode stdout -e artifact_url=<url>`
**Lint:** `ansible-navigator lint playbooks/ --mode stdout`
**Collections:** `ansible-galaxy collection install -r requirements.yml`
Load `docs/context/architecture.md` when working on playbooks, EDA rulebooks, or Appwrite API tasks.
## Rules
1. Do not mix unrelated project contexts in one session.
2. Write state to disk, not conversation. After completing meaningful work, write a summary to docs/summaries/ using templates from templates/claude-templates.md. Include: decisions with rationale, exact numbers, file paths, open items.
3. Before compaction or session end, write to disk: every number, every decision with rationale, every open question, every file path, exact next action.
4. When switching work types (development → documentation → review), write a handoff to docs/summaries/handoff-[date]-[topic].md and suggest a new session.
5. Do not silently resolve open questions. Mark them OPEN or ASSUMED.
6. Do not bulk-read documents. Process one at a time: read, summarize to disk, release from context before reading next. For the detailed protocol, read docs/context/processing-protocol.md.
7. Sub-agent returns must be structured, not free-form prose. Use output contracts from templates/claude-templates.md.
## Where Things Live
- `templates/claude-templates.md` — summary, handoff, decision, analysis, task, output contract templates (read on demand)
- `docs/summaries/` — active session state (latest handoff + decision records + source summaries)
- `docs/context/` — reusable domain knowledge, loaded only when relevant
- `architecture.md` — playbook inventory, EDA rulebooks, Appwrite API pattern, collections
- `processing-protocol.md` — full document processing steps
- `archive-rules.md` — summary lifecycle and file archival rules
- `subagent-rules.md` — when to use subagents vs. main agent
- `docs/archive/` — processed raw files. Do not read unless explicitly told.
- `output/deliverables/` — final outputs
## Error Recovery
If context degrades or auto-compact fires unexpectedly: write current state to docs/summaries/recovery-[date].md, tell the user what may have been lost, suggest a fresh session.
## Before Delivering Output
Verify: exact numbers preserved, open questions marked OPEN, output matches what was requested (not assumed), no Ansible idempotency regressions introduced, summary written to disk for this session's work.
All Ansible files (playbooks, task files, templates, vars) must end with a trailing newline.

View File

@@ -1,4 +0,0 @@
- Build template for ENV file with secrets management
- Deploy podman-compose.yml file (template this, too)
- Build systemd auto-startup
- podman-compose startup.

View File

@@ -0,0 +1,122 @@
# Session Handoff: Appwrite Stack Setup & Infrastructure Hardening
**Date:** 2026-03-14
**Session Duration:** ~3 hours
**Session Focus:** Bring Appwrite stack to production-ready state on bab1.mgmt.toal.ca — env templating, systemd, secrets, networking, monitoring, ansible-navigator fixes
**Context Usage at Handoff:** ~64%
---
## What Was Accomplished
1. Created Appwrite `.env` Jinja2 template → `playbooks/templates/appwrite.env.j2`
2. Created systemd unit template → `playbooks/templates/appwrite.service.j2`
3. Created Prometheus node exporter playbook → `playbooks/install_node_exporter.yml`
4. Moved all Appwrite vars to inventory → `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/appwrite.yml`
5. Created monitoring vars → `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/monitoring.yml`
6. Created secrets file using HashiCorp Vault lookups → `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/secrets.yml`
7. Rewrote `playbooks/install_appwrite.yml` — added .env deploy, systemd, tags (`deps`/`image`/`configure`), handler, production compose URL
8. Heavily extended `playbooks/tasks/patch_appwrite_compose.yml` — Traefik pin, image fix, forwardedHeaders, proxyProtocol, handler notifications
9. Added docker prune after upgrade → `playbooks/upgrade_appwrite.yml`
10. Added `community.hashi_vault` to `requirements.yml`
11. Fixed ansible-navigator pipelining — moved config to `environment-variables.set` in `~/.ansible-navigator.yml`; also added SSH multiplexing, fact caching, retry file suppression, profile_tasks via CALLBACKS_ENABLED
12. Deleted `secrets.yml.example` — contained plaintext secrets (security risk)
---
## Exact State of Work in Progress
- **503 from appwrite.toal.ca**: proxyProtocol patch added to `patch_appwrite_compose.yml` but **not yet re-run against the host**. The appwrite stack on bab1 is still running the old compose without `proxyProtocol.trustedIPs`. Next action: run `ansible-navigator run playbooks/install_appwrite.yml --mode stdout --skip-tags deps,image` to apply patches and restart.
- **Vault secret not populated**: `kv/oys/bab-appwrite` in HashiCorp Vault (http://nas.lan.toal.ca:8200) has not been populated. The `secrets.yml` will fail lookups until this is done.
---
## Decisions Made This Session
- **DECISION: HashiCorp Vault over ansible-vault for secrets** BECAUSE AAP and dev workflows both need access; 1Password-based ansible-vault is local-only. Vault path: `kv/data/oys/bab-appwrite`. All secrets stored as fields in one KV secret. STATUS: confirmed.
- **DECISION: appwrite.io/install/compose as compose source** BECAUSE the GitHub raw URL pointed to a dev compose (image: appwrite-dev, custom entrypoint: `php -e app/http.php`) that fails with the official image. STATUS: confirmed.
- **DECISION: Traefik pinned to 2.11.31** BECAUSE traefik:2.11 (floating tag) is incompatible with Docker Engine >= 29. STATUS: confirmed.
- **DECISION: systemd Type=oneshot RemainAfterExit=yes** BECAUSE `docker compose up -d` exits after starting containers; oneshot keeps the unit in "active" state. STATUS: confirmed.
- **DECISION: node exporter uses security_opts label=disable** BECAUSE on RHEL 9 with SELinux enforcing, `:z` on a `/` bind-mount would recursively relabel the entire filesystem. label=disable avoids this for a read-only mount. STATUS: confirmed.
- **DECISION: ANSIBLE_VAULT_IDENTITY_LIST moved to navigator set env vars** BECAUSE `ansible.config.path` does not auto-mount the file into the EE — the path is set via ANSIBLE_CONFIG env var but the file is never present at that path inside the container. STATUS: confirmed.
- **DECISION: profile_tasks via ANSIBLE_CALLBACKS_ENABLED, not ANSIBLE_STDOUT_CALLBACK** BECAUSE profile_tasks is an aggregate callback, not a stdout callback. Setting it as STDOUT_CALLBACK caused `'sort_order'` error. STATUS: confirmed.
---
## Key Numbers Generated or Discovered This Session
- `appwrite_version: "1.8.1"` — current pinned version in install_appwrite.yml
- `appwrite_traefik_version: "2.11.31"` — minimum Traefik version for Docker >29
- `appwrite_web_port: 8080` — host port mapping for Traefik HTTP
- `appwrite_websecure_port: 8443` — host port mapping for Traefik HTTPS
- `appwrite_traefik_trusted_ips: "192.168.0.0/22"` — HAProxy subnet, used for both forwardedHeaders AND proxyProtocol trustedIPs
- `node_exporter_version: "1.9.0"`, `node_exporter_port: 9100`
- HAProxy backend config: `send-proxy-v2 check-send-proxy` on both `appwrite` and `babdevapi` backends → Traefik MUST have proxyProtocol enabled
- Context at handoff: 128.2k / 200k tokens (64%)
---
## Conditional Logic Established
- IF compose source is GitHub raw URL THEN it may be the dev build compose (image: appwrite-dev) BECAUSE Appwrite's main branch docker-compose.yml is for local development
- IF Traefik `proxyProtocol.trustedIPs` is not set THEN HAProxy `send-proxy-v2` causes 503 BECAUSE Traefik reads the PROXY protocol header as malformed HTTP/TLS data
- IF `ansible.config.path` is set in navigator config WITHOUT a volume mount THEN the ansible.cfg settings are silently ignored inside the EE BECAUSE the file is not present at that path in the container
---
## Files Created or Modified
| File Path | Action | Description |
|-----------|--------|-------------|
| `playbooks/templates/appwrite.env.j2` | Created | Full Appwrite .env template; secrets use `vault_appwrite_*` vars |
| `playbooks/templates/appwrite.service.j2` | Created | systemd unit, Type=oneshot RemainAfterExit=yes |
| `playbooks/install_appwrite.yml` | Modified | Added .env deploy, systemd, tags, handler, production compose URL |
| `playbooks/tasks/patch_appwrite_compose.yml` | Modified | Added Traefik pin, image fix, forwardedHeaders, proxyProtocol, handler notifications |
| `playbooks/upgrade_appwrite.yml` | Modified | Added docker prune after upgrade |
| `playbooks/install_node_exporter.yml` | Created | Prometheus node exporter; pid_mode=host, label=disable, SYS_TIME cap |
| `requirements.yml` | Modified | Added community.hashi_vault |
| `~/.ansible-navigator.yml` | Modified | Replaced file-mount approach with environment-variables.set; added SSH mux, fact caching |
| `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/appwrite.yml` | Created | All non-secret Appwrite vars |
| `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/monitoring.yml` | Created | node_exporter_version, node_exporter_port |
| `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/secrets.yml` | Modified | HashiCorp Vault lookups for vault_appwrite_* vars |
| `secrets.yml.example` | Deleted | Contained plaintext secrets — security risk |
---
## What the NEXT Session Should Do
1. **First**: Populate HashiCorp Vault secret at `kv/oys/bab-appwrite` with fields: `openssl_key`, `db_pass`, `db_root_pass`, `smtp_password`, `executor_secret`, `github_client_secret`, `github_webhook_secret`, `github_private_key`
2. **Then**: Run `ansible-navigator run playbooks/install_appwrite.yml --mode stdout --skip-tags deps,image` to apply proxyProtocol patch and restart the Appwrite stack
3. **Then**: Verify `curl -v https://appwrite.toal.ca` no longer returns 503
4. **Then**: Install `community.hashi_vault` in the `ee-demo` execution environment (currently missing from the EE image)
5. **Then**: Run `ansible-navigator run playbooks/install_node_exporter.yml --mode stdout` to deploy node exporter
---
## Open Questions Requiring User Input
- [x] **Vault secret population**: RESOLVED 2026-03-14 — populated by hand at `kv/oys/bab-appwrite`.
- [x] **`_APP_DOMAIN_TARGET`**: RESOLVED 2026-03-14 — added to `appwrite.env.j2` defaulting to `appwrite_domain`. Fixes `Domain::__construct() null` in console.php:49.
- [x] **community.hashi_vault in EE**: RESOLVED 2026-03-14 — added to `ee-demo` EE image.
- [x] **SSH_AUTH_SOCK not passed to EE**: RESOLVED 2026-03-14 — confirmed working.
---
## Assumptions That Need Validation
- ASSUMED: `appwrite.io/install/compose` returns the production compose for 1.8.x — validate by inspecting the downloaded file on next run
- ASSUMED: Traefik entrypoint names in production compose are `appwrite_web` and `appwrite_websecure` — these were confirmed in the dev compose; verify they match in production compose
- ASSUMED: `community.hashi_vault.hashi_vault` lookup returns `data.data` fields directly for KV v2 — validate by running a test lookup
---
## What NOT to Re-Read
- The HAProxy config (provided inline by user) — key facts preserved above
- The original Appwrite `.env` (provided inline by user) — fields captured in `appwrite.env.j2`
## Files to Load Next Session
- `playbooks/install_appwrite.yml` — if continuing install/configure work
- `playbooks/tasks/patch_appwrite_compose.yml` — if debugging compose patches
- `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/secrets.yml` — if working on vault integration
- `docs/summaries/handoff-2026-03-14-appwrite-setup.md` — this file (load at session start)

View File

@@ -0,0 +1,29 @@
# Project Brief: BAB Backend Ansible
**Created:** 2026-03-14
**Last Updated:** 2026-03-14
## Project
- **Name:** BAB (Borrow a Boat) Backend Ansible
- **Type:** Ansible automation for Appwrite-based backend on RHEL 9
- **Host:** `bab1.mgmt.toal.ca`
- **Production Runner:** AAP (Ansible Automation Platform)
- **Dev Runner:** ansible-navigator with `ee-demo` execution environment
## Scope
Full lifecycle management of an Appwrite backend: host provisioning, Nginx, Gitea Act Runner, database schema, seed data, user provisioning, TLS certificates, EDA rulebooks for Gitea webhooks and Alertmanager alerts, ServiceNow integration for incident/problem creation.
## Input Documents
| Document | Path | Processed? | Summary At |
|----------|------|-----------|------------|
| Architecture reference | `docs/context/architecture.md` | Yes | self |
## Known Constraints
- No inventory file in repo — dev inventory at `~/Dev/inventories/bab-inventory/`, prod managed by AAP
- Sensitive files gitignored: `ansible.cfg`, `secrets.yml`, `.vault_password`
- `provision_database.yml` idempotency is incomplete — noted in that file
- Do not refer to AWX; production platform is AAP
## Project Phase Tracker
| Phase | Status | Summary File | Date |
|-------|--------|-------------|------|
| Initial setup | Complete | — | 2026-03-14 |

View File

@@ -0,0 +1,43 @@
---
name: Appwrite domain target fix and idempotency
description: Corrections to previous session's diagnosis and compose download idempotency
type: decision
date: 2026-03-14
---
## Decisions / Corrections
### _APP_DOMAIN_TARGET_CNAME (CORRECTS previous handoff)
Previous session recorded: `_APP_DOMAIN_TARGET` added to fix null Domain crash.
**That was wrong.** `_APP_DOMAIN_TARGET` is deprecated since Appwrite 1.7.0.
The compose file's `environment:` blocks pass only:
- `_APP_DOMAIN_TARGET_CNAME`
- `_APP_DOMAIN_TARGET_A`
- `_APP_DOMAIN_TARGET_AAAA`
- `_APP_DOMAIN_TARGET_CAA`
`_APP_DOMAIN_TARGET` is never injected into containers. It was silently ignored.
**Fix:** Replaced `_APP_DOMAIN_TARGET` with `_APP_DOMAIN_TARGET_CNAME` in
`playbooks/templates/appwrite.env.j2`. Added `_APP_DOMAIN_TARGET_CAA` (default: '').
`_APP_DOMAIN_TARGET_CNAME` defaults to `appwrite_domain` (appwrite.toal.ca).
**Why:** PHP `console.php:49` constructs a Domain object from `_APP_DOMAIN_TARGET_CNAME`.
Null → TypeError crash on every `/v1/console/variables` request.
### get_url force: true removed (idempotency)
`force: true` on the compose download caused the task to always report `changed`,
triggering a service restart on every playbook run.
**Fix:** Removed `force: true` from `playbooks/install_appwrite.yml` get_url task.
File is now only downloaded if absent. Upgrade playbook handles re-downloads.
## State After This Session
- Appwrite console loads without error ✅
- Stack running on bab1.mgmt.toal.ca ✅
- install_appwrite.yml is idempotent ✅
- node_exporter install: complete, metrics confirmed ✅

View File

@@ -0,0 +1,84 @@
# Session Handoff: Appwrite Stack Setup & Infrastructure Hardening
**Date:** 2026-03-14
**Session Duration:** ~4 hours
**Session Focus:** Bring Appwrite stack to production-ready state on bab1.mgmt.toal.ca
**Context Usage at Handoff:** ~70%
---
## Current State
The install playbook is ready to run. All open questions from the session are resolved. The stack on bab1 is running but with an unpatched compose (no proxyProtocol, old entrypoint issue). **One run of the playbook will bring everything current.**
---
## What Was Accomplished This Session
1. Appwrite `.env` Jinja2 template → `playbooks/templates/appwrite.env.j2`
2. Systemd unit template → `playbooks/templates/appwrite.service.j2`
3. Prometheus node exporter playbook → `playbooks/install_node_exporter.yml`
4. Appwrite inventory vars → `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/appwrite.yml`
5. Monitoring inventory vars → `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/monitoring.yml`
6. HashiCorp Vault secret lookups → `~/Dev/inventories/bab-inventory/host_vars/bab1.mgmt.toal.ca/secrets.yml`
7. `playbooks/install_appwrite.yml` — .env deploy, systemd, tags (`deps`/`image`/`configure`), restart handler, production compose URL (`appwrite.io/install/compose`)
8. `playbooks/tasks/patch_appwrite_compose.yml` — Traefik 2.11.31 pin, image fix (appwrite-dev→official), forwardedHeaders + proxyProtocol trustedIPs for both entrypoints, handler notifications
9. `playbooks/upgrade_appwrite.yml` — docker prune after upgrade
10. `requirements.yml` — added `community.hashi_vault`
11. `~/.ansible-navigator.yml` — pipelining fixed (ANSIBLE_CONFIG file was never mounted into EE; replaced with `environment-variables.set`); SSH multiplexing, fact caching, profile_tasks via CALLBACKS_ENABLED
12. Deleted `secrets.yml.example` — contained plaintext secrets
---
## Key Numbers
- `appwrite_version: "1.8.1"`
- `appwrite_traefik_version: "2.11.31"` — minimum for Docker Engine >= 29
- `appwrite_web_port: 8080`, `appwrite_websecure_port: 8443`
- `appwrite_traefik_trusted_ips: "192.168.0.0/22"` — HAProxy subnet; used for both `forwardedHeaders.trustedIPs` and `proxyProtocol.trustedIPs`
- `node_exporter_version: "1.9.0"`, `node_exporter_port: 9100`
- Vault path: `kv/data/oys/bab-appwrite` (populated 2026-03-14)
---
## Decisions Made
| Decision | Rationale |
|----------|-----------|
| HashiCorp Vault for secrets | AAP + dev both need access; 1Password ansible-vault is local-only |
| `appwrite.io/install/compose` as compose source | GitHub raw URL pointed to dev compose with `image: appwrite-dev` and broken entrypoint override |
| Traefik pinned to 2.11.31 | Floating `traefik:2.11` tag incompatible with Docker Engine >= 29 |
| `proxyProtocol.trustedIPs` on both Traefik entrypoints | HAProxy uses `send-proxy-v2` on both `appwrite` and `babdevapi` backends; without this Traefik returns 503 |
| `_APP_DOMAIN_TARGET` added to .env template | Appwrite 1.8.x `console.php:49` constructs a `Domain` object from this var; null = crash |
| systemd `Type=oneshot RemainAfterExit=yes` | `docker compose up -d` exits after starting containers; oneshot keeps unit active |
| node exporter `security_opts: label=disable` | `:z` on `/` bind-mount would recursively relabel entire filesystem under RHEL 9 SELinux |
| `profile_tasks` via `ANSIBLE_CALLBACKS_ENABLED` | It's an aggregate callback, not a stdout callback; `ANSIBLE_STDOUT_CALLBACK=profile_tasks` causes `'sort_order'` error |
---
## What the NEXT Session Should Do
1. **Run the install playbook** (skipping deps and image pull since stack is already running):
```bash
ansible-navigator run playbooks/install_appwrite.yml --mode stdout --skip-tags deps,image
```
2. **Verify** `curl -v https://appwrite.toal.ca` returns 200 (not 503)
3. **Verify** Appwrite console loads without `Domain::__construct() null` error
4. **Run node exporter**:
```bash
ansible-navigator run playbooks/install_node_exporter.yml --mode stdout
```
5. **Verify** `curl http://bab1.mgmt.toal.ca:9100/metrics` returns Prometheus metrics
---
## Open Questions
None. All issues from the session are resolved.
---
## Files to Load Next Session
- `playbooks/install_appwrite.yml` — if continuing install/configure work
- `playbooks/tasks/patch_appwrite_compose.yml` — if debugging compose patches
- `docs/context/architecture.md` — for Appwrite API or EDA work

View File

@@ -1,837 +0,0 @@
# WARNING!
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '10m'
version: '3'
services:
traefik:
image: docker.io/traefik:2.9
container_name: appwrite-traefik
<<: *x-logging
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
- --entrypoints.appwrite_websecure.forwardedHeaders.trustedIPs=10.0.0.0/8
- --entrypoints.appwrite_websecure.proxyProtocol.trustedIPs=10.0.0.0/8
# - --entrypoints.appwrite_web.forwardedHeaders.trustedIPs=192.168.2.1/32
# - --entrypoints.appwrite_web.proxyProtocol.trustedIPs=192.168.2.1/32
# - --entrypoints.appwrite_websecure.forwardedHeaders.trustedIPs=192.168.2.1/32
# - --entrypoints.appwrite_websecure.proxyProtocol.trustedIPs=192.168.2.1/32
- --accesslog=true
restart: unless-stopped
ports:
- 8080:80
- 8443:443
security_opt:
- label=disable
volumes:
- /run/user/1000/podman/podman.sock:/var/run/docker.sock:z
- appwrite-config:/storage/config:ro
- appwrite-certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- gateway
- appwrite
appwrite:
image: docker.io/appwrite/appwrite:1.4.13
container_name: appwrite
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_api.loadbalancer.server.port=80
#http
- traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_http.service=appwrite_api
# https
- traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
depends_on:
- mariadb
- redis
# - clamav
- influxdb
# entrypoint:
# - php
# - -e
# - app/http.php
# - -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_LOCALE=${_APP_LOCALE}
- _APP_CONSOLE_WHITELIST_ROOT=${_APP_CONSOLE_WHITELIST_ROOT}
- _APP_CONSOLE_WHITELIST_EMAILS=${_APP_CONSOLE_WHITELIST_EMAILS}
- _APP_CONSOLE_WHITELIST_IPS=${_APP_CONSOLE_WHITELIST_IPS}
- _APP_SYSTEM_EMAIL_NAME=${_APP_SYSTEM_EMAIL_NAME}
- _APP_SYSTEM_EMAIL_ADDRESS=${_APP_SYSTEM_EMAIL_ADDRESS}
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${_APP_SYSTEM_SECURITY_EMAIL_ADDRESS}
- _APP_SYSTEM_RESPONSE_FORMAT=${_APP_SYSTEM_RESPONSE_FORMAT}
- _APP_OPTIONS_ABUSE=${_APP_OPTIONS_ABUSE}
- _APP_OPTIONS_ROUTER_PROTECTION=${_APP_OPTIONS_ROUTER_PROTECTION}
- _APP_OPTIONS_FORCE_HTTPS=${_APP_OPTIONS_FORCE_HTTPS}
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=${_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_DOMAIN=${_APP_DOMAIN}
- _APP_DOMAIN_TARGET=${_APP_DOMAIN_TARGET}
- _APP_DOMAIN_FUNCTIONS=${_APP_DOMAIN_FUNCTIONS}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_SMTP_HOST=${_APP_SMTP_HOST}
- _APP_SMTP_PORT=${_APP_SMTP_PORT}
- _APP_SMTP_SECURE=${_APP_SMTP_SECURE}
- _APP_SMTP_USERNAME=${_APP_SMTP_USERNAME}
- _APP_SMTP_PASSWORD=${_APP_SMTP_PASSWORD}
- _APP_USAGE_STATS=${_APP_USAGE_STATS}
- _APP_INFLUXDB_HOST=${_APP_INFLUXDB_HOST}
- _APP_INFLUXDB_PORT=${_APP_INFLUXDB_PORT}
- _APP_STORAGE_LIMIT=${_APP_STORAGE_LIMIT}
- _APP_STORAGE_PREVIEW_LIMIT=${_APP_STORAGE_PREVIEW_LIMIT}
- _APP_STORAGE_ANTIVIRUS=${_APP_STORAGE_ANTIVIRUS}
- _APP_STORAGE_ANTIVIRUS_HOST=${_APP_STORAGE_ANTIVIRUS_HOST}
- _APP_STORAGE_ANTIVIRUS_PORT=${_APP_STORAGE_ANTIVIRUS_PORT}
- _APP_STORAGE_DEVICE=${_APP_STORAGE_DEVICE}
- _APP_STORAGE_S=${_APP_STORAGE_S3_ACCESS_KEY}
- _APP_STORAGE_S=${_APP_STORAGE_S3_SECRET}
- _APP_STORAGE_S=${_APP_STORAGE_S3_REGION}
- _APP_STORAGE_S=${_APP_STORAGE_S3_BUCKET}
- _APP_STORAGE_DO_SPACES_ACCESS_KEY=${_APP_STORAGE_DO_SPACES_ACCESS_KEY}
- _APP_STORAGE_DO_SPACES_SECRET=${_APP_STORAGE_DO_SPACES_SECRET}
- _APP_STORAGE_DO_SPACES_REGION=${_APP_STORAGE_DO_SPACES_REGION}
- _APP_STORAGE_DO_SPACES_BUCKET=${_APP_STORAGE_DO_SPACES_BUCKET}
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY=${_APP_STORAGE_BACKBLAZE_ACCESS_KEY}
- _APP_STORAGE_BACKBLAZE_SECRET=${_APP_STORAGE_BACKBLAZE_SECRET}
- _APP_STORAGE_BACKBLAZE_REGION=${_APP_STORAGE_BACKBLAZE_REGION}
- _APP_STORAGE_BACKBLAZE_BUCKET=${_APP_STORAGE_BACKBLAZE_BUCKET}
- _APP_STORAGE_LINODE_ACCESS_KEY=${_APP_STORAGE_LINODE_ACCESS_KEY}
- _APP_STORAGE_LINODE_SECRET=${_APP_STORAGE_LINODE_SECRET}
- _APP_STORAGE_LINODE_REGION=${_APP_STORAGE_LINODE_REGION}
- _APP_STORAGE_LINODE_BUCKET=${_APP_STORAGE_LINODE_BUCKET}
- _APP_STORAGE_WASABI_ACCESS_KEY=${_APP_STORAGE_WASABI_ACCESS_KEY}
- _APP_STORAGE_WASABI_SECRET=${_APP_STORAGE_WASABI_SECRET}
- _APP_STORAGE_WASABI_REGION=${_APP_STORAGE_WASABI_REGION}
- _APP_STORAGE_WASABI_BUCKET=${_APP_STORAGE_WASABI_BUCKET}
- _APP_FUNCTIONS_SIZE_LIMIT=${_APP_FUNCTIONS_SIZE_LIMIT}
- _APP_FUNCTIONS_TIMEOUT=${_APP_FUNCTIONS_TIMEOUT}
- _APP_FUNCTIONS_BUILD_TIMEOUT=${_APP_FUNCTIONS_BUILD_TIMEOUT}
- _APP_FUNCTIONS_CPUS=${_APP_FUNCTIONS_CPUS}
- _APP_FUNCTIONS_MEMORY=${_APP_FUNCTIONS_MEMORY}
- _APP_FUNCTIONS_RUNTIMES=${_APP_FUNCTIONS_RUNTIMES}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_EXECUTOR_HOST=${_APP_EXECUTOR_HOST}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
- _APP_STATSD_HOST=${_APP_STATSD_HOST}
- _APP_STATSD_PORT=${_APP_STATSD_PORT}
- _APP_MAINTENANCE_INTERVAL=${_APP_MAINTENANCE_INTERVAL}
- _APP_MAINTENANCE_RETENTION_EXECUTION=${_APP_MAINTENANCE_RETENTION_EXECUTION}
- _APP_MAINTENANCE_RETENTION_CACHE=${_APP_MAINTENANCE_RETENTION_CACHE}
- _APP_MAINTENANCE_RETENTION_ABUSE=${_APP_MAINTENANCE_RETENTION_ABUSE}
- _APP_MAINTENANCE_RETENTION_AUDIT=${_APP_MAINTENANCE_RETENTION_AUDIT}
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY=${_APP_MAINTENANCE_RETENTION_USAGE_HOURLY}
- _APP_MAINTENANCE_RETENTION_SCHEDULES=${_APP_MAINTENANCE_RETENTION_SCHEDULES}
- _APP_SMS_PROVIDER=${_APP_SMS_PROVIDER}
- _APP_SMS_FROM=${_APP_SMS_FROM}
- _APP_GRAPHQL_MAX_BATCH_SIZE=${_APP_GRAPHQL_MAX_BATCH_SIZE}
- _APP_GRAPHQL_MAX_COMPLEXITY=${_APP_GRAPHQL_MAX_COMPLEXITY}
- _APP_GRAPHQL_MAX_DEPTH=${_APP_GRAPHQL_MAX_DEPTH}
- _APP_VCS_GITHUB_APP_NAME=${_APP_VCS_GITHUB_APP_NAME}
- _APP_VCS_GITHUB_PRIVATE_KEY=${_APP_VCS_GITHUB_PRIVATE_KEY}
- _APP_VCS_GITHUB_APP_ID=${_APP_VCS_GITHUB_APP_ID}
- _APP_VCS_GITHUB_WEBHOOK_SECRET=${_APP_VCS_GITHUB_WEBHOOK_SECRET}
- _APP_VCS_GITHUB_CLIENT_SECRET=${_APP_VCS_GITHUB_CLIENT_SECRET}
- _APP_VCS_GITHUB_CLIENT_ID=${_APP_VCS_GITHUB_CLIENT_ID}
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID=${_APP_MIGRATIONS_FIREBASE_CLIENT_ID}
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=${_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET}
- _APP_ASSISTANT_OPENAI_API_KEY=${_APP_ASSISTANT_OPENAI_API_KEY}
appwrite-realtime:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_realtime.loadbalancer.server.port=80"
#ws
- traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web
- traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime
# wss
- traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.tls=true
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPTIONS_ABUSE=${_APP_OPTIONS_ABUSE}
- _APP_OPTIONS_ROUTER_PROTECTION=${_APP_OPTIONS_ROUTER_PROTECTION}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_USAGE_STATS=${_APP_USAGE_STATS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-audits:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-audits
<<: *x-logging
container_name: appwrite-worker-audits
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-webhooks:
entrypoint: worker-webhooks
<<: *x-logging
container_name: appwrite-worker-webhooks
image: docker.io/appwrite/appwrite:1.4.13
networks:
- appwrite
# volumes:
# - ./app:/usr/src/code/app
# - ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${_APP_SYSTEM_SECURITY_EMAIL_ADDRESS}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-deletes:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-deletes
<<: *x-logging
container_name: appwrite-worker-deletes
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_STORAGE_DEVICE=${_APP_STORAGE_DEVICE}
- _APP_STORAGE_S=${_APP_STORAGE_S3_ACCESS_KEY}
- _APP_STORAGE_S=${_APP_STORAGE_S3_SECRET}
- _APP_STORAGE_S=${_APP_STORAGE_S3_REGION}
- _APP_STORAGE_S=${_APP_STORAGE_S3_BUCKET}
- _APP_STORAGE_DO_SPACES_ACCESS_KEY=${_APP_STORAGE_DO_SPACES_ACCESS_KEY}
- _APP_STORAGE_DO_SPACES_SECRET=${_APP_STORAGE_DO_SPACES_SECRET}
- _APP_STORAGE_DO_SPACES_REGION=${_APP_STORAGE_DO_SPACES_REGION}
- _APP_STORAGE_DO_SPACES_BUCKET=${_APP_STORAGE_DO_SPACES_BUCKET}
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY=${_APP_STORAGE_BACKBLAZE_ACCESS_KEY}
- _APP_STORAGE_BACKBLAZE_SECRET=${_APP_STORAGE_BACKBLAZE_SECRET}
- _APP_STORAGE_BACKBLAZE_REGION=${_APP_STORAGE_BACKBLAZE_REGION}
- _APP_STORAGE_BACKBLAZE_BUCKET=${_APP_STORAGE_BACKBLAZE_BUCKET}
- _APP_STORAGE_LINODE_ACCESS_KEY=${_APP_STORAGE_LINODE_ACCESS_KEY}
- _APP_STORAGE_LINODE_SECRET=${_APP_STORAGE_LINODE_SECRET}
- _APP_STORAGE_LINODE_REGION=${_APP_STORAGE_LINODE_REGION}
- _APP_STORAGE_LINODE_BUCKET=${_APP_STORAGE_LINODE_BUCKET}
- _APP_STORAGE_WASABI_ACCESS_KEY=${_APP_STORAGE_WASABI_ACCESS_KEY}
- _APP_STORAGE_WASABI_SECRET=${_APP_STORAGE_WASABI_SECRET}
- _APP_STORAGE_WASABI_REGION=${_APP_STORAGE_WASABI_REGION}
- _APP_STORAGE_WASABI_BUCKET=${_APP_STORAGE_WASABI_BUCKET}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_EXECUTOR_HOST=${_APP_EXECUTOR_HOST}
appwrite-worker-databases:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-databases
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-builds:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-builds
<<: *x-logging
container_name: appwrite-worker-builds
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_EXECUTOR_HOST=${_APP_EXECUTOR_HOST}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
- _APP_VCS_GITHUB_APP_NAME=${_APP_VCS_GITHUB_APP_NAME}
- _APP_VCS_GITHUB_PRIVATE_KEY=${_APP_VCS_GITHUB_PRIVATE_KEY}
- _APP_VCS_GITHUB_APP_ID=${_APP_VCS_GITHUB_APP_ID}
- _APP_FUNCTIONS_TIMEOUT=${_APP_FUNCTIONS_TIMEOUT}
- _APP_FUNCTIONS_BUILD_TIMEOUT=${_APP_FUNCTIONS_BUILD_TIMEOUT}
- _APP_FUNCTIONS_CPUS=${_APP_FUNCTIONS_CPUS}
- _APP_FUNCTIONS_MEMORY=${_APP_FUNCTIONS_MEMORY}
- _APP_FUNCTIONS_SIZE_LIMIT=${_APP_FUNCTIONS_SIZE_LIMIT}
- _APP_OPTIONS_FORCE_HTTPS=${_APP_OPTIONS_FORCE_HTTPS}
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=${_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS}
- _APP_DOMAIN=${_APP_DOMAIN}
- _APP_STORAGE_DEVICE=${_APP_STORAGE_DEVICE}
- _APP_STORAGE_S=${_APP_STORAGE_S3_ACCESS_KEY}
- _APP_STORAGE_S=${_APP_STORAGE_S3_SECRET}
- _APP_STORAGE_S=${_APP_STORAGE_S3_REGION}
- _APP_STORAGE_S=${_APP_STORAGE_S3_BUCKET}
- _APP_STORAGE_DO_SPACES_ACCESS_KEY=${_APP_STORAGE_DO_SPACES_ACCESS_KEY}
- _APP_STORAGE_DO_SPACES_SECRET=${_APP_STORAGE_DO_SPACES_SECRET}
- _APP_STORAGE_DO_SPACES_REGION=${_APP_STORAGE_DO_SPACES_REGION}
- _APP_STORAGE_DO_SPACES_BUCKET=${_APP_STORAGE_DO_SPACES_BUCKET}
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY=${_APP_STORAGE_BACKBLAZE_ACCESS_KEY}
- _APP_STORAGE_BACKBLAZE_SECRET=${_APP_STORAGE_BACKBLAZE_SECRET}
- _APP_STORAGE_BACKBLAZE_REGION=${_APP_STORAGE_BACKBLAZE_REGION}
- _APP_STORAGE_BACKBLAZE_BUCKET=${_APP_STORAGE_BACKBLAZE_BUCKET}
- _APP_STORAGE_LINODE_ACCESS_KEY=${_APP_STORAGE_LINODE_ACCESS_KEY}
- _APP_STORAGE_LINODE_SECRET=${_APP_STORAGE_LINODE_SECRET}
- _APP_STORAGE_LINODE_REGION=${_APP_STORAGE_LINODE_REGION}
- _APP_STORAGE_LINODE_BUCKET=${_APP_STORAGE_LINODE_BUCKET}
- _APP_STORAGE_WASABI_ACCESS_KEY=${_APP_STORAGE_WASABI_ACCESS_KEY}
- _APP_STORAGE_WASABI_SECRET=${_APP_STORAGE_WASABI_SECRET}
- _APP_STORAGE_WASABI_REGION=${_APP_STORAGE_WASABI_REGION}
- _APP_STORAGE_WASABI_BUCKET=${_APP_STORAGE_WASABI_BUCKET}
appwrite-worker-certificates:
entrypoint: worker-certificates
<<: *x-logging
container_name: appwrite-worker-certificates
image: docker.io/appwrite/appwrite:1.4.13
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_DOMAIN=${_APP_DOMAIN}
- _APP_DOMAIN_TARGET=${_APP_DOMAIN_TARGET}
- _APP_DOMAIN_FUNCTIONS=${_APP_DOMAIN_FUNCTIONS}
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${_APP_SYSTEM_SECURITY_EMAIL_ADDRESS}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-functions:
entrypoint: worker-functions
<<: *x-logging
container_name: appwrite-worker-functions
image: docker.io/appwrite/appwrite:1.4.13
networks:
- appwrite
depends_on:
- redis
- mariadb
- openruntimes-executor
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_FUNCTIONS_TIMEOUT=${_APP_FUNCTIONS_TIMEOUT}
- _APP_FUNCTIONS_BUILD_TIMEOUT=${_APP_FUNCTIONS_BUILD_TIMEOUT}
- _APP_FUNCTIONS_CPUS=${_APP_FUNCTIONS_CPUS}
- _APP_FUNCTIONS_MEMORY=${_APP_FUNCTIONS_MEMORY}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_EXECUTOR_HOST=${_APP_EXECUTOR_HOST}
- _APP_USAGE_STATS=${_APP_USAGE_STATS}
- _APP_DOCKER_HUB_USERNAME=${_APP_DOCKER_HUB_USERNAME}
- _APP_DOCKER_HUB_PASSWORD=${_APP_DOCKER_HUB_PASSWORD}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
appwrite-worker-mails:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-mails
<<: *x-logging
container_name: appwrite-worker-mails
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_SYSTEM_EMAIL_NAME=${_APP_SYSTEM_EMAIL_NAME}
- _APP_SYSTEM_EMAIL_ADDRESS=${_APP_SYSTEM_EMAIL_ADDRESS}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_SMTP_HOST=${_APP_SMTP_HOST}
- _APP_SMTP_PORT=${_APP_SMTP_PORT}
- _APP_SMTP_SECURE=${_APP_SMTP_SECURE}
- _APP_SMTP_USERNAME=${_APP_SMTP_USERNAME}
- _APP_SMTP_PASSWORD=${_APP_SMTP_PASSWORD}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-messaging:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_SMS_PROVIDER=${_APP_SMS_PROVIDER}
- _APP_SMS_FROM=${_APP_SMS_FROM}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-worker-migrations:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: worker-migrations
<<: *x-logging
container_name: appwrite-worker-migrations
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_DOMAIN=${_APP_DOMAIN}
- _APP_DOMAIN_TARGET=${_APP_DOMAIN_TARGET}
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${_APP_SYSTEM_SECURITY_EMAIL_ADDRESS}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID=${_APP_MIGRATIONS_FIREBASE_CLIENT_ID}
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=${_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET}
appwrite-maintenance:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_DOMAIN=${_APP_DOMAIN}
- _APP_DOMAIN_TARGET=${_APP_DOMAIN_TARGET}
- _APP_DOMAIN_FUNCTIONS=${_APP_DOMAIN_FUNCTIONS}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_MAINTENANCE_INTERVAL=${_APP_MAINTENANCE_INTERVAL}
- _APP_MAINTENANCE_RETENTION_EXECUTION=${_APP_MAINTENANCE_RETENTION_EXECUTION}
- _APP_MAINTENANCE_RETENTION_CACHE=${_APP_MAINTENANCE_RETENTION_CACHE}
- _APP_MAINTENANCE_RETENTION_ABUSE=${_APP_MAINTENANCE_RETENTION_ABUSE}
- _APP_MAINTENANCE_RETENTION_AUDIT=${_APP_MAINTENANCE_RETENTION_AUDIT}
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY=${_APP_MAINTENANCE_RETENTION_USAGE_HOURLY}
- _APP_MAINTENANCE_RETENTION_SCHEDULES=${_APP_MAINTENANCE_RETENTION_SCHEDULES}
appwrite-usage:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: usage
<<: *x-logging
container_name: appwrite-usage
restart: unless-stopped
networks:
- appwrite
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
- _APP_INFLUXDB_HOST=${_APP_INFLUXDB_HOST}
- _APP_INFLUXDB_PORT=${_APP_INFLUXDB_PORT}
- _APP_USAGE_AGGREGATION_INTERVAL=${_APP_USAGE_AGGREGATION_INTERVAL}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_USAGE_STATS=${_APP_USAGE_STATS}
- _APP_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- _APP_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
appwrite-schedule:
image: docker.io/appwrite/appwrite:1.4.13
entrypoint: schedule
<<: *x-logging
container_name: appwrite-schedule
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV=${_APP_ENV}
- _APP_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=${_APP_REDIS_HOST}
- _APP_REDIS_PORT=${_APP_REDIS_PORT}
- _APP_REDIS_USER=${_APP_REDIS_USER}
- _APP_REDIS_PASS=${_APP_REDIS_PASS}
- _APP_DB_HOST=${_APP_DB_HOST}
- _APP_DB_PORT=${_APP_DB_PORT}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA}
- _APP_DB_USER=${_APP_DB_USER}
- _APP_DB_PASS=${_APP_DB_PASS}
appwrite-assistant:
image: docker.io/appwrite/assistant:0.2.2
container_name: appwrite-assistant
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY=${_APP_ASSISTANT_OPENAI_API_KEY}
openruntimes-executor:
container_name: openruntimes-executor
hostname: appwrite-executor
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: docker.io/openruntimes/executor:0.4.5
networks:
- appwrite
- runtimes
security_opt:
- label=disable
volumes:
- /run/user/1000/podman/podman.sock:/var/run/docker.sock:z
- appwrite-builds:/storage/builds:rw
- appwrite-functions:/storage/functions:rw
# Host mount nessessary to share files between executor and runtimes.
# It's not possible to share mount file between 2 containers without host mount (copying is too slow)
- /home/ptoal/appwrite/tmp:/tmp:z
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=${_APP_FUNCTIONS_INACTIVE_THRESHOLD}}
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=${_APP_FUNCTIONS_MAINTENANCE_INTERVAL}
- OPR_EXECUTOR_NETWORK=${_APP_FUNCTIONS_RUNTIMES_NETWORK}
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=${_APP_DOCKER_HUB_USERNAME}
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=${_APP_DOCKER_HUB_PASSWORD}
- OPR_EXECUTOR_ENV=${_APP_ENV}
- OPR_EXECUTOR_RUNTIMES=${_APP_FUNCTIONS_RUNTIMES}
- OPR_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v3
- OPR_EXECUTOR_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
- OPR_EXECUTOR_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
- OPR_EXECUTOR_STORAGE_DEVICE=${_APP_STORAGE_DEVICE}
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=${_APP_STORAGE_S3_ACCESS_KEY}
- OPR_EXECUTOR_STORAGE_S3_SECRET=${_APP_STORAGE_S3_SECRET}
- OPR_EXECUTOR_STORAGE_S3_REGION=${_APP_STORAGE_S3_REGION}
- OPR_EXECUTOR_STORAGE_S3_BUCKET=${_APP_STORAGE_S3_BUCKET}
- OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=${_APP_STORAGE_DO_SPACES_ACCESS_KEY}
- OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=${_APP_STORAGE_DO_SPACES_SECRET}
- OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=${_APP_STORAGE_DO_SPACES_REGION}
- OPR_EXECUTOR_STORAGE_DO_SPACES_BUCKET=${_APP_STORAGE_DO_SPACES_BUCKET}
- OPR_EXECUTOR_STORAGE_BACKBLAZE_ACCESS_KEY=${_APP_STORAGE_BACKBLAZE_ACCESS_KEY}
- OPR_EXECUTOR_STORAGE_BACKBLAZE_SECRET=${_APP_STORAGE_BACKBLAZE_SECRET}
- OPR_EXECUTOR_STORAGE_BACKBLAZE_REGION=${_APP_STORAGE_BACKBLAZE_REGION}
- OPR_EXECUTOR_STORAGE_BACKBLAZE_BUCKET=${_APP_STORAGE_BACKBLAZE_BUCKET}
- OPR_EXECUTOR_STORAGE_LINODE_ACCESS_KEY=${_APP_STORAGE_LINODE_ACCESS_KEY}
- OPR_EXECUTOR_STORAGE_LINODE_SECRET=${_APP_STORAGE_LINODE_SECRET}
- OPR_EXECUTOR_STORAGE_LINODE_REGION=${_APP_STORAGE_LINODE_REGION}
- OPR_EXECUTOR_STORAGE_LINODE_BUCKET=${_APP_STORAGE_LINODE_BUCKET}
- OPR_EXECUTOR_STORAGE_WASABI_ACCESS_KEY=${_APP_STORAGE_WASABI_ACCESS_KEY}
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=${_APP_STORAGE_WASABI_SECRET}
- OPR_EXECUTOR_STORAGE_WASABI_REGION=${_APP_STORAGE_WASABI_REGION}
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=${_APP_STORAGE_WASABI_BUCKET}
# openruntimes-proxy:
# container_name: openruntimes-proxy
# hostname: proxy
# <<: *x-logging
# stop_signal: SIGINT
# image: docker.io/openruntimes/proxy:0.3.1
# networks:
# - appwrite
# - runtimes
# environment:
# - OPR_PROXY_WORKER_PER_CORE=${_APP_WORKER_PER_CORE}
# - OPR_PROXY_ENV=${_APP_ENV}
# - OPR_PROXY_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
# - OPR_PROXY_SECRET=${_APP_EXECUTOR_SECRET}
# - OPR_PROXY_LOGGING_PROVIDER=${_APP_LOGGING_PROVIDER}
# - OPR_PROXY_LOGGING_CONFIG=${_APP_LOGGING_CONFIG}
# - OPR_PROXY_ALGORITHM=random
# - OPR_PROXY_EXECUTORS=appwrite-executor
# - OPR_PROXY_HEALTHCHECK_INTERVAL=10000
# - OPR_PROXY_MAX_TIMEOUT=600
# - OPR_PROXY_HEALTHCHECK=enabled
mariadb:
image: docker.io/mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync'
# smtp:
# image: appwrite/smtp:1.2.0
# container_name: appwrite-smtp
# restart: unless-stopped
# networks:
# - appwrite
# environment:
# - LOCAL_DOMAINS=@
# - RELAY_FROM_HOSTS=192.168.0.0/16 ; *.yourdomain.com
# - SMARTHOST_HOST=smtp
# - SMARTHOST_PORT=587
redis:
image: docker.io/redis:7.0.4-alpine
<<: *x-logging
container_name: appwrite-redis
restart: unless-stopped
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
networks:
- appwrite
volumes:
- appwrite-redis:/data:rw
# clamav:
# image: docker.io/appwrite/clamav:1.2.0
# container_name: appwrite-clamav
# networks:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
influxdb:
image: docker.io/appwrite/influxdb:1.5.0
container_name: appwrite-influxdb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-influxdb:/var/lib/influxdb:rw
telegraf:
image: docker.io/appwrite/telegraf:1.4.0
container_name: appwrite-telegraf
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_INFLUXDB_HOST=${_APP_INFLUXDB_HOST}
- _APP_INFLUXDB_PORT=${_APP_INFLUXDB_PORT}
networks:
gateway:
name: gateway
appwrite:
name: appwrite
runtimes:
name: runtimes
volumes:
appwrite-mariadb:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-influxdb:
appwrite-config:
# appwrite-chronograf:

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
@@ -21,36 +22,112 @@
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:
- podman
- 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 podman-compose installed
ansible.builtin.dnf:
name:
- podman-compose
- name: Userspace setup
hosts: bab1.mgmt.toal.ca
tasks:
- name: Ensure podman socket enabled
- name: Ensure Docker service is enabled and started
ansible.builtin.systemd:
name: podman.socket
scope: user
name: docker
enabled: true
state: started
- name: Ensure appwrite image pulled from docker hub
containers.podman.podman_image:
name: docker.io/appwrite/appwrite
tag: 1.7.4
- name: Ensure ansible user is in docker group
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: true
- name: Ensure podman-compose.yml deployed
ansible.builtin.copy:
src: files/container-compose.yml
dest: /home/ptoal/appwrite
- 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

@@ -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

@@ -43,4 +43,4 @@
return_content: true
register: appwrite_api_result
loop: '{{ interval_template_docs.documents }}'
delegate_to: localhost
delegate_to: localhost

View File

@@ -0,0 +1,85 @@
---
# 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

@@ -0,0 +1,79 @@
---
# 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

@@ -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

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

View File

@@ -1,3 +1,4 @@
collections:
- name: nginxinc.nginx_core
version: 0.8.0
version: 0.8.0
- name: community.hashi_vault

View File

@@ -4,8 +4,6 @@
sources:
- name: Ansible webhook listener
ansible.eda.webhook:
port: 5000
host: 0.0.0.0
rules:
- name: Run Template
condition:

View File

@@ -0,0 +1,490 @@
# Claude Templates — On-Demand Reference
> **Do NOT read this file at session start.** Read it only when you need to write a summary, handoff, decision record, or subagent output. This file is referenced from CLAUDE.md.
---
## Template 1: Source Document Summary
**Use when:** Processing any input document (client brief, research report, requirements doc, existing proposal)
**Write to:** `./docs/summaries/source-[filename].md`
```markdown
# Source Summary: [Original Document Name]
**Processed:** [YYYY-MM-DD]
**Source Path:** [exact file path]
**Archived From:** [original path, if moved to docs/archive/]
**Document Type:** [brief / requirements / research / proposal / transcript / other]
**Confidence:** [high = I understood everything / medium = some interpretation needed / low = significant gaps]
## Exact Numbers & Metrics
<!-- List EVERY specific number, dollar amount, percentage, date, count, measurement.
Do NOT round. Do NOT paraphrase. Copy exactly as stated in source. -->
- [metric]: [exact value] (page/section reference if available)
- [metric]: [exact value]
## Key Facts (Confirmed)
<!-- Only include facts explicitly stated in the document. Tag source. -->
- [fact] — stated in [section/page]
- [fact] — stated in [section/page]
## Requirements & Constraints
<!-- Use IF/THEN/BUT/EXCEPT format to preserve conditional logic -->
- REQUIREMENT: [what is needed]
- CONDITION: [when/if this applies]
- CONSTRAINT: [limitation or exception]
- PRIORITY: [must-have / should-have / nice-to-have / stated by whom]
## Decisions Referenced
<!-- Any decisions mentioned in the document -->
- DECISION: [what was decided]
- RATIONALE: [why, as stated in document]
- ALTERNATIVES MENTIONED: [what else was considered]
- DECIDED BY: [who, if stated]
## Relationships to Other Documents
<!-- How this document connects to other known project documents -->
- SUPPORTS: [other document/decision it reinforces]
- CONTRADICTS: [other document/decision it conflicts with]
- DEPENDS ON: [other document/decision it requires]
- UPDATES: [other document/decision it supersedes]
## Open Questions & Ambiguities
<!-- Things that are NOT resolved in this document -->
- UNCLEAR: [what is ambiguous] — needs clarification from [whom]
- ASSUMED: [interpretation made] — verify with [whom]
- MISSING: [information referenced but not provided]
## Verbatim Quotes Worth Preserving
<!-- 2-5 direct quotes that capture stakeholder language, priorities, or constraints
These are critical for proposals — use the client's own words back to them -->
- "[exact quote]" — [speaker/author], [context]
```
---
## Template 2: Analysis / Research Summary
**Use when:** Completing competitive analysis, market research, technical evaluation
**Write to:** `./docs/summaries/analysis-[topic].md`
```markdown
# Analysis Summary: [Topic]
**Completed:** [YYYY-MM-DD]
**Analysis Type:** [competitive / market / technical / financial / feasibility]
**Sources Used:** [list source paths or URLs]
**Confidence:** [high / medium / low — and WHY this confidence level]
## Core Finding (One Sentence)
[Single sentence: the most important conclusion]
## Evidence Base
<!-- Specific data points supporting the finding. Exact numbers only. -->
| Data Point | Value | Source | Date of Data |
|-----------|-------|--------|-------------|
| [metric] | [exact value] | [source] | [date] |
## Detailed Findings
### Finding 1: [Name]
- WHAT: [the finding]
- SO WHAT: [why it matters for this project]
- EVIDENCE: [specific supporting data]
- CONFIDENCE: [high/medium/low]
### Finding 2: [Name]
[same structure]
## Conditional Conclusions
<!-- Use IF/THEN format -->
- IF [condition], THEN [conclusion], BECAUSE [evidence]
- IF [alternative condition], THEN [different conclusion]
## What This Analysis Does NOT Cover
<!-- Explicit scope boundaries to prevent future sessions from over-interpreting -->
- [topic not addressed and why]
- [data not available]
## Recommended Next Steps
1. [action] — priority [high/medium/low], depends on [what]
2. [action]
```
---
## Template 3: Decision Record
**Use when:** Any significant decision is made during a session
**Write to:** `./docs/summaries/decision-[number]-[topic].md`
```markdown
# Decision Record: [Short Title]
**Decision ID:** DR-[sequential number]
**Date:** [YYYY-MM-DD]
**Status:** CONFIRMED / PROVISIONAL / REQUIRES_VALIDATION
## Decision
[One clear sentence: what was decided]
## Context
[2-3 sentences: what situation prompted this decision]
## Rationale
- CHOSE [option] BECAUSE: [specific reasons with data]
- REJECTED [alternative 1] BECAUSE: [specific reasons]
- REJECTED [alternative 2] BECAUSE: [specific reasons]
## Quantified Impact
- [metric affected]: [expected change with numbers]
- [cost/time/resource implication]: [specific figures]
## Conditions & Constraints
- VALID IF: [conditions under which this decision holds]
- REVISIT IF: [triggers that should cause reconsideration]
- DEPENDS ON: [upstream decisions or facts this relies on]
## Stakeholder Input
- [name/role]: [their stated position, if known]
## Downstream Effects
- AFFECTS: [what other decisions, documents, or deliverables this impacts]
- REQUIRES UPDATE TO: [specific files or deliverables that need revision]
```
---
## Template 4: Session Handoff
**Use when:** A session is ending (context limit approaching OR phase complete)
**Write to:** `./docs/summaries/handoff-[YYYY-MM-DD]-[topic].md`
**LIFECYCLE**: After writing a new handoff, move the PREVIOUS handoff to `docs/archive/handoffs/`.
```markdown
# Session Handoff: [Topic]
**Date:** [YYYY-MM-DD]
**Session Duration:** [approximate]
**Session Focus:** [one sentence]
**Context Usage at Handoff:** [estimated percentage if known]
## What Was Accomplished
<!-- Be specific. Include file paths for every output. -->
1. [task completed] → output at `[file path]`
2. [task completed] → output at `[file path]`
## Exact State of Work in Progress
<!-- If anything is mid-stream, describe exactly where it stopped -->
- [work item]: completed through [specific point], next step is [specific action]
- [work item]: blocked on [specific issue]
## Decisions Made This Session
<!-- Reference decision records if created, otherwise summarize here -->
- DR-[number]: [decision] (see `./docs/summaries/decision-[file]`)
- [Ad-hoc decision]: [what] BECAUSE [why] — STATUS: [confirmed/provisional]
## Key Numbers Generated or Discovered This Session
<!-- Every metric, estimate, or figure produced. Exact values. -->
- [metric]: [value] — [context for where/how this was derived]
## Conditional Logic Established
<!-- Any IF/THEN/BUT/EXCEPT reasoning that future sessions must respect -->
- IF [condition] THEN [approach] BECAUSE [rationale]
## Files Created or Modified
| File Path | Action | Description |
|-----------|--------|-------------|
| `[path]` | Created | [what it contains] |
| `[path]` | Modified | [what changed and why] |
## What the NEXT Session Should Do
<!-- Ordered, specific instructions. The next session starts by reading this. -->
1. **First**: [specific action with file paths]
2. **Then**: [specific action]
3. **Then**: [specific action]
## Open Questions Requiring User Input
<!-- Do NOT proceed on these without explicit user confirmation -->
- [ ] [question] — impacts [what downstream deliverable]
- [ ] [question]
## Assumptions That Need Validation
<!-- Things treated as true this session but not confirmed -->
- ASSUMED: [assumption] — validate by [method/person]
## What NOT to Re-Read
<!-- Prevent the next session from wasting context on already-processed material -->
- `[file path]` — already summarized in `[summary file path]`
## Files to Load Next Session
<!-- Explicit index of what the next session should read. Acts as progressive disclosure index layer. -->
- `[file path]` — needed for [reason]
- `[file path]` — needed for [reason]
```
---
## Template 5: Project Brief (Initial Setup)
**Use when:** Creating the 00-project-brief.md at project start
**Write to:** `./docs/summaries/00-project-brief.md`
```markdown
# Project Brief: [Project Name]
**Created:** [YYYY-MM-DD]
**Last Updated:** [YYYY-MM-DD]
## Client
- **Name:** [client name]
- **Industry:** [industry]
- **Size:** [employee count / revenue if known]
- **Relationship:** [through AutomatonsX / Lagrange Data / direct / other]
- **Key Contacts:** [names and roles if known]
## Engagement
- **Type:** [proposal / workshop / competitive analysis / agent development / hybrid]
- **Scope:** [one paragraph description]
- **Target Deliverable:** [specific output expected]
- **Timeline:** [deadline if known]
- **Budget Context:** [if known — exact figures]
## Input Documents
| Document | Path | Processed? | Summary At |
|----------|------|-----------|------------|
| [name] | `[path]` | Yes/No | `[summary path]` |
## Success Criteria
- [criterion 1]
- [criterion 2]
## Known Constraints
- [constraint 1]
- [constraint 2]
## Project Phase Tracker
| Phase | Status | Summary File | Date |
|-------|--------|-------------|------|
| Discovery | Not Started / In Progress / Complete | `[path]` | |
| Strategy | Not Started / In Progress / Complete | `[path]` | |
| Deliverable Draft | Not Started / In Progress / Complete | `[path]` | |
| Review & Polish | Not Started / In Progress / Complete | `[path]` | |
```
---
## Template 6: Task Definition
**Use when:** Defining a discrete unit of work before starting execution
```markdown
## Task: [name]
**Date:** [YYYY-MM-DD]
**Client:** [if applicable]
**Work Type:** [proposal / workshop / analysis / content / agent development]
### Context Files to Load
- `[file path]` — [why needed]
### Action
[What to produce. Be specific about format, length, and scope.]
### Verify
- [ ] Numbers match source data exactly
- [ ] Open questions marked OPEN
- [ ] Output matches what was requested, not what was assumed
- [ ] Claims backed by specific data
- [ ] Consistent with stored decisions in docs/context/
### Done When
- [ ] Output file exists at `[specific path]`
- [ ] Summary written to `docs/summaries/[specific file]`
```
---
## Subagent Output Contracts
**CRITICAL: When subagents return results to the main agent, unstructured prose causes information loss. These output contracts define the EXACT format subagents must return.**
### Contract for Document Analysis Subagent
```
=== DOCUMENT ANALYSIS OUTPUT ===
SOURCE: [file path]
TYPE: [document type]
CONFIDENCE: [high/medium/low]
NUMBERS:
- [metric]: [exact value]
[repeat for all numbers found]
REQUIREMENTS:
- REQ: [requirement] | CONDITION: [if any] | PRIORITY: [level] | CONSTRAINT: [if any]
[repeat]
DECISIONS_REFERENCED:
- DEC: [what] | WHY: [rationale] | BY: [who]
[repeat]
CONTRADICTIONS:
- [this document says X] CONTRADICTS [other known fact Y]
[repeat or NONE]
OPEN:
- [unresolved item] | NEEDS: [who/what to resolve]
[repeat or NONE]
QUOTES:
- "[verbatim]" — [speaker], [context]
[repeat, max 5]
=== END OUTPUT ===
```
### Contract for Research/Analysis Subagent
```
=== RESEARCH OUTPUT ===
QUERY: [what was researched]
SOURCES: [list]
CONFIDENCE: [high/medium/low] BECAUSE [reason]
CORE_FINDING: [one sentence]
EVIDENCE:
- [data point]: [exact value] | SOURCE: [where] | DATE: [when]
[repeat]
CONCLUSIONS:
- IF [condition] THEN [conclusion] | EVIDENCE: [reference]
[repeat]
GAPS:
- [what was not found or not covered]
[repeat or NONE]
NEXT_STEPS:
- [recommended action] | PRIORITY: [level]
[repeat]
=== END OUTPUT ===
```
### Contract for Review/QA Subagent
```
=== REVIEW OUTPUT ===
REVIEWED: [file path or deliverable name]
AGAINST: [what standard — spec, requirements, style guide]
PASS: [yes/no/partial]
ISSUES:
- SEVERITY: [critical/major/minor] | ITEM: [description] | LOCATION: [where in document] | FIX: [suggested resolution]
[repeat]
MISSING:
- [expected content/section not found] | REQUIRED_BY: [which requirement]
[repeat or NONE]
INCONSISTENCIES:
- [item A says X] BUT [item B says Y] | RESOLUTION: [suggested]
[repeat or NONE]
STRENGTHS:
- [what works well — for positive reinforcement in iteration]
[max 3]
=== END OUTPUT ===
```
---
## Phase-Based Workflow Templates
### Template A: Enterprise Sales Deliverable
```
Phase 1: Discovery & Input Processing
├── Process all client documents → Source Document Summaries
├── Identify gaps in information → flag as OPEN items
├── Create Decision Records for any choices made
├── Write: ./docs/summaries/01-discovery-complete.md (Handoff Template)
├── → Suggest new session for Phase 2
Phase 2: Strategy & Positioning
├── Read summaries only (NOT source documents)
├── Competitive positioning analysis → Analysis Summary
├── Value proposition development
├── ROI framework construction with EXACT numbers
├── Write: ./docs/summaries/02-strategy-complete.md (Handoff Template)
├── → Suggest new session for Phase 3
Phase 3: Deliverable Creation
├── Read strategy summary + project brief only
├── Draft deliverable (proposal / deck / workshop plan)
├── Output to: ./output/deliverables/
├── Write: ./docs/summaries/03-deliverable-draft.md (Handoff Template)
├── → Suggest new session for Phase 4
Phase 4: Review & Polish
├── Read draft deliverable + strategy summary
├── Quality review using Review/QA Output Contract
├── Final edits and formatting
├── Output final version to: ./output/deliverables/
```
### Template B: Agent/Application Development
```
Phase 1: Requirements → Spec
├── Process all input documents → Source Document Summaries
├── Generate structured specification
├── Output: ./output/SPEC.md
├── Write: ./docs/summaries/01-spec-complete.md (Handoff Template)
├── → Suggest new session for Phase 2
Phase 2: Architecture → Schema
├── Read SPEC.md + summaries only
├── Design data model
├── Define agent behaviors and workflows
├── Output: ./output/schemas/data-model.yaml
├── Output: ./output/schemas/agent-definitions.yaml
├── Write: ./docs/summaries/02-architecture-complete.md (Handoff Template)
├── → Suggest new session for Phase 3
Phase 3: Prompts → Integration
├── Read schemas + spec only
├── Write system prompts for each agent
├── Map API integrations and data flows
├── Output: ./output/prompts/[agent-name].md (one per agent)
├── Output: ./output/schemas/integration-map.yaml
├── Write: ./docs/summaries/03-prompts-complete.md (Handoff Template)
├── → Suggest new session for Phase 4
Phase 4: Assembly → Package
├── Read all output files
├── Assemble complete application package
├── Generate deployment/setup instructions
├── Output: ./output/deliverables/[project]-complete-package/
├── QA check against original spec using Review/QA Output Contract
```
### Template C: Hybrid (Sales + Agent Development)
```
Phase 1: Client Discovery → Summaries
Phase 2: Solution Design → Architecture + Schema
Phase 3a: Client-Facing Deliverable (proposal/deck)
Phase 3b: Internal Technical Package (schemas/prompts)
Phase 4: Review both tracks against each other for consistency
```
---
## End of Templates
**Return to your task after reading the template(s) you need. Do not keep this file in active context.**