summaryrefslogtreecommitdiff
path: root/AGENTS.md
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2026-02-15 21:27:00 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2026-02-15 21:27:00 +0100
commitce0dbf6b249956700c6a1705bf4ad85a09d53e8c (patch)
treed7c3236807cfbf75d7f3a355eb5df5a5e2cc4ad7 /AGENTS.md
parent064a1d01c5c14f5ecc032fa9b8346a4a88b893f6 (diff)
feat: witryna 0.2.0HEADv0.2.0main
Switch, cleanup, and status CLI commands. Persistent build state via state.json. Post-deploy hooks on success and failure with WITRYNA_BUILD_STATUS. Dependency diet (axum→tiny_http, clap→argh, tracing→log). Drop built-in rate limiting. Nix flake with NixOS module. Arch Linux PKGBUILD. Centralized version management. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md53
1 files changed, 30 insertions, 23 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 97b3aab..62292a7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -22,9 +22,11 @@ This project follows a **minimal philosophy**: software should be simple, minima
witryna serve # Start the deployment server
witryna validate # Validate config and print summary
witryna run <site> [-v] # One-off build (synchronous)
-witryna status [-s <site>] [--json] # Deployment status
+witryna status [site] [--json] # Deployment status
+witryna switch <site> <build> # Switch active build (rollback)
+witryna cleanup [site] [--keep N] # Remove old builds/logs (manual)
# Config discovery: ./witryna.toml → $XDG_CONFIG_HOME/witryna/witryna.toml → /etc/witryna/witryna.toml
-# Override with: witryna --config /path/to/witryna.toml <command>
+# Override with: witryna <command> --config /path/to/witryna.toml
```
### Development
@@ -37,10 +39,8 @@ just test # Run unit tests
just test-integration # Run integration tests (Tier 1 + Tier 2)
just test-integration-serial # Integration tests with --test-threads=1 (for SIGHUP)
just test-all # All lints + unit tests + integration tests
+just lint-picky # Pedantic + nursery clippy lints (warnings only, not gated)
just pre-commit # Mirrors lefthook pre-commit checks
-just man-1 # View witryna(1) man page (needs cargo build first)
-just man-5 # View witryna.toml(5) man page
-
# Cargo (direct)
cargo build # Build the project
cargo run # Run the application
@@ -51,7 +51,7 @@ cargo check # Type-check without building
### Core Components
-1. **HTTP Server (axum)**: Listens on localhost, handles webhook POST requests
+1. **HTTP Server (tiny_http)**: Listens on localhost, handles webhook POST requests
2. **Site Manager**: Manages site configurations from `witryna.toml`
3. **Build Executor**: Runs containerized builds via Podman/Docker
4. **Asset Publisher**: Atomic symlink switching for zero-downtime deployments
@@ -68,7 +68,8 @@ cargo check # Type-check without building
├── clones/{site-name}/ # Git repository clones
├── builds/{site-name}/
│ ├── {timestamp}/ # Timestamped build outputs
-│ └── current -> {latest} # Symlink to current build
+│ ├── current -> {latest} # Symlink to current build
+│ └── state.json # Current build state (building/success/failed)
└── cache/{site-name}/ # Persistent build caches
/var/log/witryna/
@@ -83,7 +84,6 @@ cargo check # Type-check without building
- `202 Accepted` - Build triggered (immediate or queued)
- `401 Unauthorized` - Invalid token (only when `webhook_token` is configured; `{"error": "unauthorized"}`)
- `404 Not Found` - Unknown site (`{"error": "not_found"}`)
- - `429 Too Many Requests` - Rate limit exceeded (`{"error": "rate_limit_exceeded"}`)
### System Diagram
@@ -118,6 +118,7 @@ Upon receiving a valid webhook request, Witryna executes asynchronously:
1. **Acquire Lock / Queue:** Per-site non-blocking lock. If a build is in progress, the request is queued (depth-1, latest-wins). Queued rebuilds run after the current build completes.
2. **Determine Paths:** Construct clone/build paths from `base_dir` and `site_name`.
+2b. **Write Build State:** Write `state.json` with `"building"` status (enables `witryna status` to show in-progress builds).
3. **Fetch Source Code:** `git clone` if first time, `git pull` otherwise.
3b. **Initialize Submodules:** If `.gitmodules` exists, run `git submodule sync --recursive` (pull only) then `git submodule update --init --recursive [--depth N]`.
4. **Parse Repository Config:** Read build config (`.witryna.yaml` / `witryna.yaml` / custom `config_file`) or use `witryna.toml` overrides.
@@ -136,7 +137,8 @@ Upon receiving a valid webhook request, Witryna executes asynchronously:
node:20-alpine sh -c "npm install && npm run build"
```
6. **Publish Assets:** Copy built `public` dir to timestamped directory, atomically switch symlink via `ln -sfn`.
-6b. **Post-Deploy Hook (Optional):** Run `post_deploy` command with `WITRYNA_SITE`, `WITRYNA_BUILD_DIR`, `WITRYNA_BUILD_TIMESTAMP` env vars. 30s timeout, non-fatal on failure.
+6b. **Post-Deploy Hook (Optional):** Run `post_deploy` command with `WITRYNA_SITE`, `WITRYNA_BUILD_DIR`, `WITRYNA_PUBLIC_DIR`, `WITRYNA_BUILD_TIMESTAMP`, `WITRYNA_BUILD_STATUS` env vars. Hooks run on both success and failure. 30s timeout, non-fatal on failure.
+6c. **Update Build State:** Write `state.json` with final status (`"success"`, `"failed"`, or `"hook failed"`).
7. **Release Lock:** Release the per-site lock.
8. **Log Outcome:** Log success or failure.
@@ -186,7 +188,7 @@ cargo test --features integration overrides # Build config override tests
#### Test Tiers
-- **Tier 1 (no container runtime needed):** health, auth (401), 404, concurrent build (409), rate limit (429), edge cases, SIGHUP
+- **Tier 1 (no container runtime needed):** health, auth (401), 404, concurrent build (409), edge cases, SIGHUP
- **Tier 2 (requires podman or docker):** deploy, logs, cleanup, overrides, polling
Tests that require git or a container runtime automatically skip with an explicit message (e.g., `SKIPPED: no container runtime (podman/docker) found`) when the dependency is missing.
@@ -212,7 +214,6 @@ tests/integration/
not_found.rs # 404: unknown site
deploy.rs # Full build pipeline (Tier 2)
concurrent.rs # 409 via DashSet injection (Tier 1)
- rate_limit.rs # 429 with isolated server (Tier 1)
logs.rs # Build log verification (Tier 2)
cleanup.rs # Old build cleanup (Tier 2)
sighup.rs # SIGHUP reload (#[serial], Tier 1)
@@ -222,13 +223,16 @@ tests/integration/
cache.rs # Cache directory persistence (Tier 2)
env_vars.rs # Environment variable passing (Tier 2)
cli_run.rs # witryna run command (Tier 2)
- cli_status.rs # witryna status command (Tier 1)
+ cli_status.rs # witryna status command + state.json (Tier 1)
+ cli_switch.rs # witryna switch command (Tier 1)
+ cli_cleanup.rs # witryna cleanup command (Tier 1)
+ cli_validate.rs # witryna validate command (Tier 1)
hooks.rs # Post-deploy hooks (Tier 2)
```
#### Test Categories
-- **Core pipeline** — health, auth (401), 404, deployment (202), concurrent build rejection (409), rate limiting (429)
+- **Core pipeline** — health, auth (401), 404, deployment (202), concurrent build rejection (409)
- **FEAT-001** — SIGHUP config hot-reload
- **FEAT-002** — build config overrides from `witryna.toml` (complete and partial)
- **FEAT-003** — periodic repository polling, new commit detection
@@ -253,12 +257,11 @@ Follow OWASP best practices for all HTTP endpoints:
- Limit request body size to prevent DoS
3. **Rate Limiting**
- - Implement rate limiting per token/IP to prevent abuse
- - Return `429 Too Many Requests` when exceeded
+ - Delegated to the reverse proxy (Caddy, nginx). See `examples/caddy/Caddyfile` and `examples/nginx/witryna.conf` for configuration.
4. **Error Handling**
- Never expose internal error details in responses
- - Log detailed errors server-side with `tracing`
+ - Log detailed errors server-side with `log`
- Return generic error messages to clients
5. **Command Injection Prevention**
@@ -273,10 +276,14 @@ Follow OWASP best practices for all HTTP endpoints:
- Configurable working directory: `container_workdir` (relative path, no traversal)
- Podman: rootless via `--userns=keep-id`; Docker: `--cap-add=DAC_OVERRIDE` for workspace access
+## Backlog & Sprint Tracking
+
+`SPRINT.md` (gitignored) contains the project backlog, current sprint tasks, and sprint history. Always check `SPRINT.md` first when asked "what's next", what remains for a milestone, or what to work on.
+
## Conventions
- Use `anyhow` for error handling with context
-- Use `tracing` macros for logging (`info!`, `debug!`, `error!`)
+- Use `log` macros for logging (`info!`, `debug!`, `error!`)
- Async-first: prefer `tokio::fs` over `std::fs`
- Use `DashSet` for concurrent build tracking
- `SPRINT.md` is gitignored — update it after each task to track progress, but **never commit it**
@@ -285,15 +292,15 @@ Follow OWASP best practices for all HTTP endpoints:
## Branching
-- Implement each new feature or task on a **dedicated branch** named after the task ID (e.g., `cli-002-man-pages`, `pkg-001-cargo-deb`)
-- Branch from `main` before starting work: `git checkout -b <branch-name> main`
-- Keep the branch focused on a single task — do not mix unrelated changes
-- Merge back to `main` only after the task is complete and tests pass
-- Do not delete the branch until the merge is confirmed
+- **Only `main` is pushed to the remote.** `main` has a clean, linear history — one squash commit per feature/milestone.
+- Day-to-day work happens on a **`staging`** branch (or feature branches off staging). These are never pushed.
+- When work is complete and tests pass, squash-merge `staging` into `main` with a single descriptive commit.
+- Feature branches (if used) branch from `staging`, not `main`.
+- Do not push work-in-progress branches — Git is decentralized; only publish finished work.
## Commit Rules
-**IMPORTANT:** Before completing any task, run `just test-all` to verify everything passes, then run `/commit-smart` to commit changes.
+**IMPORTANT:** Before completing any task, run `just test-all` and `just lint-picky` to verify everything passes, then run `/commit-smart` to commit changes.
- Only commit files modified in the current session
- Use atomic commits with descriptive messages