Got things working. Added CLAUDE instructions
This commit is contained in:
8
.claude/commands/handoff.md
Normal file
8
.claude/commands/handoff.md
Normal 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.
|
||||||
13
.claude/commands/process-doc.md
Normal file
13
.claude/commands/process-doc.md
Normal 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.
|
||||||
13
.claude/commands/status.md
Normal file
13
.claude/commands/status.md
Normal 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)
|
||||||
97
CLAUDE.md
97
CLAUDE.md
@@ -1,79 +1,52 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
## Session Start
|
||||||
|
|
||||||
## What This Repo Does
|
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.
|
||||||
|
|
||||||
Ansible automation for the BAB (Borrow a Boat) project backend. Manages the full lifecycle of an Appwrite-based backend running on a single RHEL 9 host (`bab1.mgmt.toal.ca`) via Ansible Automation Platform. Do not refer to AWX. Also contains Ansible EDA rulebooks for event-driven automation triggered by Gitea webhooks and Alertmanager alerts.
|
Before starting work, state: what you understand the project state to be, what you plan to do this session, and any open questions.
|
||||||
|
|
||||||
## Common Commands
|
## Identity
|
||||||
|
|
||||||
Run a playbook via ansible-navigator (preferred — uses execution environment):
|
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.
|
||||||
```bash
|
|
||||||
ansible-navigator run playbooks/<name>.yml --mode stdout
|
|
||||||
```
|
|
||||||
|
|
||||||
Run with extra vars (required by several playbooks):
|
## Project
|
||||||
```bash
|
|
||||||
ansible-navigator run playbooks/deploy_application.yml --mode stdout -e artifact_url=<url>
|
|
||||||
```
|
|
||||||
|
|
||||||
Lint all playbooks:
|
**Repo:** Ansible playbooks and EDA rulebooks managing a full Appwrite backend lifecycle on RHEL 9.
|
||||||
```bash
|
**Host:** `bab1.mgmt.toal.ca`
|
||||||
ansible-navigator lint playbooks/ --mode stdout
|
**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`
|
||||||
|
|
||||||
Install required collections:
|
Load `docs/context/architecture.md` when working on playbooks, EDA rulebooks, or Appwrite API tasks.
|
||||||
```bash
|
|
||||||
ansible-galaxy collection install -r requirements.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
## Rules
|
||||||
|
|
||||||
### Inventory and Credentials
|
1. Do not mix unrelated project contexts in one session.
|
||||||
There is no inventory file in this repo. For development, a static inventory is located in ~/Dev/inventories/bab-inventory/. In production, inventory and credentials (Appwrite API keys, ServiceNow instance, vault password) are managed externally in AAP.
|
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.
|
||||||
|
|
||||||
Sensitive files are gitignored: `ansible.cfg`, `secrets.yml`, `.vault_password`.
|
## Where Things Live
|
||||||
|
|
||||||
### Playbook Roles
|
- `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
|
||||||
|
|
||||||
| Playbook | Purpose | Notable vars |
|
## Error Recovery
|
||||||
|---|---|---|
|
|
||||||
| `install_appwrite.yml` | Full RHEL 9 host setup: Docker, Appwrite container via compose | — |
|
|
||||||
| `install_nginx.yml` | Nginx via `nginxinc.nginx_core` role + firewalld | — |
|
|
||||||
| `configure_act_runner.yml` | Gitea Act Runner as systemd user unit (rootless) | `runner_user` |
|
|
||||||
| `deploy_application.yml` | Downloads a release tarball and extracts to nginx webroot | `artifact_url` |
|
|
||||||
| `provision_database.yml` | Creates Appwrite database, collections, and attributes via REST API | Appwrite vars |
|
|
||||||
| `provision_users.yml` | Creates users in Appwrite via REST API (Argon2 hashed) | `bab_users` list |
|
|
||||||
| `load_data.yml` | Loads seed data from `playbooks/files/database/*.json` into Appwrite | Appwrite vars |
|
|
||||||
| `read_database.yml` | Dumps Appwrite collection data to `playbooks/files/database/` | Appwrite vars, targets `appwrite:&dev` |
|
|
||||||
| `clean_logs.yml` | Removes large test log files from `/var/log` | — |
|
|
||||||
| `investigate_high_cpu.yml` | Captures process data and opens ServiceNow incident + problem | `snow_instance`, EDA event vars |
|
|
||||||
| `update_certificates.yml` | Requests and installs TLS certs from Red Hat IdM (IPA) | `ipa_admin_password`, cert path vars |
|
|
||||||
|
|
||||||
### Event-Driven Ansible (EDA) Rulebooks
|
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.
|
||||||
|
|
||||||
`rulebooks/gitea_webhook.yml` — listens for Gitea push webhooks carrying an `artifact_url` and triggers the `bab-deploy-application` AAP job template.
|
## Before Delivering Output
|
||||||
|
|
||||||
`rulebooks/alertmanager_listener.yml` — listens on port 9101 for Prometheus Alertmanager events; routes to AAP job templates for log cleanup (`root filesystem over 80% full`) and CPU investigation (`ProcessCPUHog`). Scoped to `org == "OYS"`.
|
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.
|
||||||
|
|
||||||
### Appwrite API Pattern
|
|
||||||
|
|
||||||
Playbooks that talk to Appwrite use `ansible.builtin.uri` delegated to `localhost`, with `module_defaults` at the play level to set shared headers (`X-Appwrite-Project`, `X-Appwrite-Key`, `X-Appwrite-Response-Format`). All mutating calls use `status_code: [201, 409]` to tolerate re-runs — but true idempotency is incomplete (noted in `provision_database.yml`).
|
|
||||||
|
|
||||||
### Templates and Static Files
|
|
||||||
|
|
||||||
- `playbooks/templates/` — Jinja2 templates: `act_runner.service` (systemd unit for Gitea runner), `appwrite_attribute_template.json.j2` (Appwrite schema helper), `cpuhog_ticket.j2` (ServiceNow ticket body)
|
|
||||||
- `playbooks/files/database/` — JSON seed data files used by `load_data.yml` and written by `read_database.yml`
|
|
||||||
|
|
||||||
### Collections Used
|
|
||||||
|
|
||||||
- `community.docker` — Docker image pulls and container management
|
|
||||||
- `community.general` — RHSM repo management
|
|
||||||
- `ansible.posix` — firewalld
|
|
||||||
- `nginxinc.nginx_core` — Nginx install and config roles
|
|
||||||
- `servicenow.itsm` — Incident and problem creation
|
|
||||||
- `redhat.rhel_idm` — IPA certificate requests
|
|
||||||
- `community.crypto` — TLS key/CSR generation
|
|
||||||
- `ansible.utils` — `remove_keys` filter used in `load_data.yml`
|
|
||||||
- `ansible.eda` — webhook and alertmanager sources
|
|
||||||
|
|||||||
3
TODO.txt
3
TODO.txt
@@ -1,3 +0,0 @@
|
|||||||
- Build template for ENV file with secrets management
|
|
||||||
- Build systemd auto-startup
|
|
||||||
- podman-compose startup.
|
|
||||||
|
|||||||
122
docs/archive/handoffs/handoff-2026-03-14-appwrite-setup.md
Normal file
122
docs/archive/handoffs/handoff-2026-03-14-appwrite-setup.md
Normal 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)
|
||||||
29
docs/summaries/00-project-brief.md
Normal file
29
docs/summaries/00-project-brief.md
Normal 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 |
|
||||||
43
docs/summaries/decisions-2026-03-14-domain-target-fix.md
Normal file
43
docs/summaries/decisions-2026-03-14-domain-target-fix.md
Normal 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 ✅
|
||||||
84
docs/summaries/handoff-2026-03-14-appwrite-setup-final.md
Normal file
84
docs/summaries/handoff-2026-03-14-appwrite-setup-final.md
Normal 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
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
- name: Prepare Backend Host for BAB
|
- name: Prepare Backend Host for BAB
|
||||||
hosts: bab1.mgmt.toal.ca
|
hosts: bab1.mgmt.toal.ca
|
||||||
become: true
|
become: true
|
||||||
|
tags: deps
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Update all packages to latest
|
- name: Update all packages to latest
|
||||||
@@ -55,41 +56,78 @@
|
|||||||
- name: Userspace setup
|
- name: Userspace setup
|
||||||
hosts: bab1.mgmt.toal.ca
|
hosts: bab1.mgmt.toal.ca
|
||||||
vars:
|
vars:
|
||||||
appwrite_version: "1.7.4"
|
appwrite_version: "1.8.1"
|
||||||
appwrite_dir: /home/ptoal/appwrite
|
appwrite_dir: /home/ptoal/appwrite
|
||||||
appwrite_socket: /var/run/docker.sock
|
appwrite_socket: /var/run/docker.sock
|
||||||
appwrite_web_port: 8080
|
appwrite_web_port: 8080
|
||||||
appwrite_websecure_port: 8443
|
appwrite_websecure_port: 8443
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: Restart appwrite service
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: appwrite
|
||||||
|
state: restarted
|
||||||
|
become: true
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Ensure appwrite image pulled from docker hub
|
- name: Ensure appwrite image pulled from docker hub
|
||||||
community.docker.docker_image:
|
community.docker.docker_image:
|
||||||
name: appwrite/appwrite
|
name: appwrite/appwrite
|
||||||
tag: "{{ appwrite_version }}"
|
tag: "{{ appwrite_version }}"
|
||||||
source: pull
|
source: pull
|
||||||
|
tags: image
|
||||||
|
|
||||||
- name: Ensure appwrite directory exists
|
- name: Ensure appwrite directory exists
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ appwrite_dir }}"
|
path: "{{ appwrite_dir }}"
|
||||||
state: directory
|
state: directory
|
||||||
mode: '0755'
|
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:
|
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"
|
dest: "{{ appwrite_dir }}/docker-compose.yml"
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
force: true
|
notify: Restart appwrite service
|
||||||
|
tags: configure
|
||||||
|
|
||||||
- name: Apply site-specific customizations to docker-compose.yml
|
- 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
|
- name: Deploy appwrite systemd unit
|
||||||
ansible.builtin.command:
|
ansible.builtin.template:
|
||||||
argv:
|
src: appwrite.service.j2
|
||||||
- docker
|
dest: /etc/systemd/system/appwrite.service
|
||||||
- compose
|
mode: '0644'
|
||||||
- up
|
become: true
|
||||||
- -d
|
notify: Restart appwrite service
|
||||||
chdir: "{{ appwrite_dir }}"
|
tags: configure
|
||||||
changed_when: true
|
|
||||||
|
- 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
|
||||||
|
|||||||
43
playbooks/install_node_exporter.yml
Normal file
43
playbooks/install_node_exporter.yml
Normal 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
|
||||||
@@ -7,18 +7,72 @@
|
|||||||
# appwrite_socket - host path to the container socket
|
# appwrite_socket - host path to the container socket
|
||||||
# appwrite_web_port - host port to map to container port 80 (default 8080)
|
# 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_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
|
- name: Remap traefik HTTP port
|
||||||
ansible.builtin.replace:
|
ansible.builtin.replace:
|
||||||
path: "{{ appwrite_dir }}/docker-compose.yml"
|
path: "{{ appwrite_dir }}/docker-compose.yml"
|
||||||
regexp: '- "?80:80"?'
|
regexp: '- "?80:80"?'
|
||||||
replace: "- {{ appwrite_web_port }}:80"
|
replace: "- {{ appwrite_web_port }}:80"
|
||||||
|
notify: Restart appwrite service
|
||||||
|
|
||||||
- name: Remap traefik HTTPS port
|
- name: Remap traefik HTTPS port
|
||||||
ansible.builtin.replace:
|
ansible.builtin.replace:
|
||||||
path: "{{ appwrite_dir }}/docker-compose.yml"
|
path: "{{ appwrite_dir }}/docker-compose.yml"
|
||||||
regexp: '- "?443:443"?'
|
regexp: '- "?443:443"?'
|
||||||
replace: "- {{ appwrite_websecure_port }}: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
|
- name: Add host tmp mount to openruntimes-executor for docker file sharing
|
||||||
# Inserts after the last occurrence of appwrite-builds:/storage/builds:rw,
|
# Inserts after the last occurrence of appwrite-builds:/storage/builds:rw,
|
||||||
@@ -28,3 +82,4 @@
|
|||||||
line: " - {{ appwrite_dir }}/tmp:/tmp:z"
|
line: " - {{ appwrite_dir }}/tmp:/tmp:z"
|
||||||
insertafter: "appwrite-builds:/storage/builds:rw"
|
insertafter: "appwrite-builds:/storage/builds:rw"
|
||||||
state: present
|
state: present
|
||||||
|
notify: Restart appwrite service
|
||||||
179
playbooks/templates/appwrite.env.j2
Normal file
179
playbooks/templates/appwrite.env.j2
Normal 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('') }}
|
||||||
16
playbooks/templates/appwrite.service.j2
Normal file
16
playbooks/templates/appwrite.service.j2
Normal 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
|
||||||
@@ -52,3 +52,9 @@
|
|||||||
loop_control:
|
loop_control:
|
||||||
loop_var: appwrite_target_version
|
loop_var: appwrite_target_version
|
||||||
when: appwrite_target_version is version(current_appwrite_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
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
collections:
|
collections:
|
||||||
- name: nginxinc.nginx_core
|
- name: nginxinc.nginx_core
|
||||||
version: 0.8.0
|
version: 0.8.0
|
||||||
|
- name: community.hashi_vault
|
||||||
490
templates/claude-templates.md
Normal file
490
templates/claude-templates.md
Normal 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.**
|
||||||
Reference in New Issue
Block a user