mirror of
https://github.com/cole-maxwell1/todo-hetzner.git
synced 2026-10-27 19:02:49 -05:00
chore: align repository and Ansible configuration
Relocate group variable examples beside the inventory for automatic discovery. Refresh repository guidance, ignore local tooling artifacts, and disable Ansible cows.
This commit is contained in:
@@ -1,114 +1,34 @@
|
||||
# AGENTS.md / OpenCode Execution Plan
|
||||
# Repository Guide
|
||||
|
||||
## 1. Executive Summary & Objective
|
||||
Build a complete, production-ready, highly maintainable infrastructure and web application stack:
|
||||
1. **Infrastructure:** OpenTofu configs deploying Rocky Linux 10 on Hetzner Cloud, isolated via private networking, configured with S3-compatible Hetzner Object Storage.
|
||||
2. **App:** Lightweight Go To-Do web app using native `html/template`, xorm,and TailwindCSS, running via rootless Podman / systemd containers.
|
||||
3. **Database & Backups:** PostgreSQL running on host/container, using `WAL-G` for continuous archiving and hourly base backups to Hetzner Object Storage.
|
||||
4. **Staging Environment:** Automated daily database sync (6:00 AM and 12:00 PM `America/Chicago`) restoring production S3 state onto a separate Staging instance.
|
||||
5. **Scale Path:** Pre-wired for future multi-instance app scaling behind a Hetzner Cloud Load Balancer.
|
||||
## Boundaries
|
||||
|
||||
---
|
||||
- `app/` is the Go 1.22 module. The server entrypoint is `app/cmd/server/main.go`; handlers own HTTP behavior, `internal/db` owns xorm persistence, and `app/web` embeds templates plus compiled CSS into the binary.
|
||||
- `infra/` is one OpenTofu root module and one shared state for both production and staging. `infra/environments/*` contain input files, not independent stacks.
|
||||
- `ansible/playbooks/setup-node.yml` configures both hosts; `deploy-app.yml` pulls and restarts the rootless Podman/Quadlet app. Environment-specific behavior is selected by generated inventory groups and `deployment_environment`.
|
||||
- Staging restores unchanged production data. Treat staging, backups, logs, real tfvars, Vault files, and state as production-sensitive; inspect examples instead of secret-bearing files.
|
||||
|
||||
## 2. Project Directory Structure
|
||||
Generate the codebase using this strict layout:
|
||||
## Commands
|
||||
|
||||
```text
|
||||
.
|
||||
├── infra/
|
||||
│ ├── modules/
|
||||
│ │ ├── compute/ # OpenTofu module for Rocky Linux instances
|
||||
│ │ └── storage/ # OpenTofu module for Hetzner S3 bucket
|
||||
│ ├── environments/
|
||||
│ │ ├── prod/ # Production HCL configs
|
||||
│ │ └── staging/ # Staging HCL configs
|
||||
│ └── main.tf
|
||||
├── ansible/
|
||||
│ ├── playbooks/
|
||||
│ │ ├── setup-node.yml # System setup, Podman, PostgreSQL, WAL-G installation
|
||||
│ │ └── deploy-app.yml # Pull container & reload systemd unit
|
||||
│ ├── roles/
|
||||
│ └── templates/
|
||||
│ ├── wal-g.env.j2 # Hetzner S3 credentials for WAL-G
|
||||
│ ├── db-sync.service.j2
|
||||
│ └── db-sync.timer.j2 # Systemd timer set for America/Chicago
|
||||
├── app/
|
||||
│ ├── cmd/
|
||||
│ │ └── server/
|
||||
│ │ └── main.go
|
||||
│ ├── internal/
|
||||
│ │ ├── db/ # Postgres connections & queries
|
||||
│ │ ├── handler/ # HTTP handlers using html/template
|
||||
│ │ └── model/ # Todo struct definitions
|
||||
│ ├── web/
|
||||
│ │ ├── templates/ # Go HTML templates
|
||||
│ │ │ ├── layouts/
|
||||
│ │ │ └── pages/
|
||||
│ │ └── static/ # Compiled Tailwind CSS output
|
||||
│ ├── Dockerfile # Multi-stage build for Go app + Tailwind CLI
|
||||
│ ├── tailwind.config.js
|
||||
│ ├── go.mod
|
||||
│ └── Makefile
|
||||
└── README.md
|
||||
- Run repository checks with `make validate`: Go tests, `go vet`, OpenTofu validation, then both Ansible syntax checks.
|
||||
- Run one Go test from `app/`: `go test ./internal/handler -run '^TestName$'` or `go test ./internal/db -run '^TestStoreLifecycle$'`.
|
||||
- `internal/db` integration tests silently skip unless `TEST_DATABASE_URL` points to PostgreSQL. Normal `make test` needs no database.
|
||||
- Format Go and OpenTofu files with `make format`; it runs `gofmt` and recursive `tofu fmt` (including real ignored tfvars), but does not format YAML or templates.
|
||||
- Build the embedded app with `make build-app`. This first runs Tailwind and writes `app/web/static/app.css`; `make css` performs only that asset step.
|
||||
- Build the deployment image with `docker build -t todo:local app`. Its CSS stage is self-contained and does not use the local `app/tailwindcss` binary.
|
||||
|
||||
3. Component & Technical Specifications
|
||||
A. Infrastructure (OpenTofu)
|
||||
Use the official hetznercloud/hcloud provider.
|
||||
## Generated And Runtime Behavior
|
||||
|
||||
Provision an hcloud_network (private subnet 10.0.1.0/24) and attach both Production and Staging instances.
|
||||
- Edit Tailwind source in `app/web/input.css` and templates under `app/web/templates/`; regenerate the tracked `app/web/static/app.css` with `make css` after class or style changes.
|
||||
- The standalone Tailwind binary is downloaded at pinned version `v3.4.17` to ignored `app/tailwindcss`.
|
||||
- The server requires `DATABASE_URL`, defaults `APP_ADDR` to `:8080`, and runs xorm `Sync2` for the `todos` table during startup. Keep `xorm.io/xorm` paired with `github.com/lib/pq` in this stack.
|
||||
- `make generate-inventory` reads applied OpenTofu output and overwrites ignored `ansible/inventory/hosts.yml`; rerun it after server replacement or address changes.
|
||||
|
||||
Configure hcloud_primary_ip for static public IPv4/IPv6 assignment.
|
||||
## Infrastructure And Deployment
|
||||
|
||||
Include structural outputs for an hcloud_load_balancer to easily attach future app instances.
|
||||
|
||||
B. Application (Golang + TailwindCSS)
|
||||
Standard library net/http router (or lightweight chi).
|
||||
|
||||
UI: Native html/template rendering Todo List operations (Create, Read, Toggle Complete, Delete).
|
||||
|
||||
Styling: Standalone TailwindCSS CLI integrated into a multi-stage Dockerfile (build CSS step -> Go build step -> minimal Scratch/Distroless image).
|
||||
|
||||
Database Driver: jackc/pgx/v5 targeting local PostgreSQL.
|
||||
|
||||
Service Management: App runs containerized via Podman and configured as a systemd user service (app-todo.service).
|
||||
|
||||
C. Database & Continuous Backups (Production)
|
||||
Install PostgreSQL on the Rocky Linux instance.
|
||||
|
||||
Configure WAL streaming and continuous archiving using WAL-G.
|
||||
|
||||
Systemd service executes wal-g backup-push hourly, pushing binary base backups and WAL logs to Hetzner Object Storage (S3 API).
|
||||
|
||||
D. Automated Staging Restore Pipeline
|
||||
Create a systemd timer on the Staging server configured explicitly for America/Chicago timezone:
|
||||
|
||||
db-sync.timer:
|
||||
|
||||
Ini, TOML
|
||||
[Unit]
|
||||
Description=Run DB Restore from S3 Hourly/Daily Snapshot
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 06,12:00:00 America/Chicago
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
db-sync.service Execution Steps:
|
||||
|
||||
systemctl stop app-todo
|
||||
|
||||
Execute wal-g backup-fetch LATEST onto PostgreSQL staging instance
|
||||
|
||||
Apply any necessary staging sanitization scripts
|
||||
|
||||
systemctl start app-todo
|
||||
|
||||
4. Execution Rules for OpenCode
|
||||
Idiomatic Code: Write standard, strictly formatted Go code (gofmt).
|
||||
|
||||
Full Implementation: Do not use placeholder code (// TODO: implement later) inside Go handlers or Ansible templates. Generate complete, runnable files.
|
||||
|
||||
No External Framework Bloat: Keep dependencies minimal (xorm for database, standard library for templates/routing).
|
||||
|
||||
Shell Script Verification: Provide a Makefile in the root directory automation tasks: make build-app, make infra-plan, make deploy-ansible.
|
||||
- OpenTofu commands must use both var files against the root stack, for example `tofu -chdir=infra plan -var-file=environments/prod/prod.tfvars -var-file=environments/staging/staging.tfvars`. The staging file only supplies staging-specific overrides.
|
||||
- Prefer `TF_VAR_hcloud_token`, `TF_VAR_s3_access_key`, and `TF_VAR_s3_secret_key`; values in `*.tfvars` override those environment variables.
|
||||
- Keep the AWS provider on `~> 5.69.0`: newer releases wait for an AWS-only response header absent from Hetzner Object Storage.
|
||||
- Run Ansible from `ansible/`, as the Make targets do, or set `ANSIBLE_CONFIG=ansible/ansible.cfg`; otherwise the repository role path is not loaded.
|
||||
- `make deploy-ansible` is an operational deployment, not a verification command. It regenerates inventory, runs host setup before app deployment, requires decryptable Vault files, and acts on both production and staging.
|
||||
- The Quadlet uses `Pull=never`; `deploy-app.yml` intentionally pulls `app_image` before restarting `app-todo.service`.
|
||||
- Production WAL-G archives under a prefix ending in `/prod`. Staging's scheduled `db-sync` destructively replaces `/var/lib/pgsql/data` from `LATEST` and leaves the app stopped on failure; do not weaken its path/prefix guards or use it as a routine test.
|
||||
|
||||
Reference in New Issue
Block a user