# AGENTS.md @github.com/XingfenD/AGENTS.md:content/basic_agents_md/AGENTS.md ## Docker deployment (dev & release) ### Deployment mode Mode differences (dev = `deploy/docker-compose.dev.yml`, release = `deploy/docker-compose.yml`): - dev: source dirs bind-mounted into containers (`../backend:/app`, `../frontend:/app`, `../collab*`), `target: dev` images, workers run `go mod download && go run ./cmd/...`, hot reload without rebuild; also exposes infra ports to the host (postgres 5432, redis 6379, ES 9200, rabbit 5672) and a delve debugger on 2345, and gates startup on healthchecks. - release: multi-stage `*.prod` Dockerfiles (`target: runner`) with compiled binaries, no source mounts — Go services only get config files mounted; infra ports stay on the internal network only. ### Volume mounts - Named volumes (both modes, shared): `{$project_name}_{postgres,redis,consul,rabbitmq,minio,elasticsearch}_data` persist infra state to Docker volumes — identical names across the two compose files, so dev and release see the same data. Only `down -v` (i.e. `rebuild`/`clear`) wipes them. - Config/log bind mounts (both modes): rendered outputs `deploy/nginx/nginx.conf`, `deploy/nginx/conf.d/default.conf`, `deploy/redis/redis.conf`, `deploy/rabbitmq/rabbitmq.conf` (`:ro`) plus `deploy/logs/{nginx,rabbitmq}` are mounted from the host — these come from `prepare.sh`, so stale files in the container mean you forgot to rerun it. - Source mounts (dev only): `../backend:/app` (which run `go mod download && go run ./cmd/...`), `../frontend:/app` with an anonymous volume on `/app/node_modules` (so the image's deps survive the mount — delete containers with `down`, not `rm`, to avoid orphaning it). - The File Storage mounts: if the backend stores uploaded files directly instead of in an object storage, mount `deploy/{$service_name}/storage` as the storage path into the container. ## Project structure Basically, the project consists of the directories `docs`, `backend`, `frontend`, `deploy`, plus optional `collab*` services: ```plaintext . ├── docs # project documentation (see Documents layout) ├── backend # golang services (see backend layout) ├── frontend # node web app, built and served via nginx (see frontend layout) ├── collab* # optional collaboration services, same layout as backend ├── deploy # compose stacks, rendered configs, host-side logs (see deploy layout) ├── .gitignore # repo-wide ignores only; module-specific ignores live inside each module ├── AGENTS.md # agent guidance for this repo └── LICENSE ``` ### Documents layout The documents' layout is expected to be like below: ```plaintext docs ├── CHANGELOG.md # general (non-WebUI) changes; highest version on top ├── CHANGELOG_web.md # WebUI changes only; highest version on top ├── README.md # primary (English) entry doc └── README_zh.md # Chinese mirror of README.md — keep in sync ``` Constraints: - Every user-visible change gets a changelog entry: WebUI changes → `CHANGELOG_web.md`, everything else → `CHANGELOG.md`; never duplicate an entry across both. - `README.md` and `README_zh.md` stay content-equivalent; update them in the same change. - Documentation lives only under `docs/` — no stray `*.md` at the repo root besides `AGENTS.md` and `LICENSE`. ### backend (golang) ```plaintext backend ├── cmd # executable entry points, one dir per binary ├── internal # private packages: importable only within this module ├── pkg # public packages: intentionally shared with other repos └── .gitignore # .gitignore inside backend ``` Constraints: - `cmd`: each subdirectory holds exactly one `main` package; `main.go` only bootstraps — load config, wire dependencies, start the server/CLI, handle graceful shutdown. No business logic here, and nothing outside `cmd` imports `cmd`. - `internal`: default home for all business code (domain, services, storage, config). If code is not meant to be imported by other repos, it goes here, not in `pkg`. - `pkg`: opt-in public API surface — keep it small and stable, and never leak `internal` types through its APIs. ```plaintext cmd ├── cli # command-line binary │ ├── commands # one file/package per subcommand: flag parsing + dispatch into internal │ └── main.go # arg parsing and subcommand dispatch only └── webui # HTTP server binary ├── api # route registration + request/response DTOs — the endpoint contract, no logic ├── constant # webui-only constants (routes, keys, limits) ├── handlers # thin HTTP handlers: bind/validate input, call internal services, map errors ├── main.go # boot the HTTP server ├── static # assets served as-is by webui └── templates # server-side HTML templates rendered by webui ``` Constraints: - Handlers never touch the database or implement domain rules — they delegate to `internal`; SQL and business rules live behind `internal` boundaries. - `api` is the single source of truth for the endpoint contract; the frontend aligns with it. - Code shared between `cli` and `webui` belongs in `internal` (or `pkg`), never imported from one command by the other. ### frontend (node) ```plaintext frontend ├── src # all hand-written app source: pages, components, state, API client ├── public # static assets copied into the build output as-is ├── package.json # dev/build/lint/test scripts, runnable unchanged inside the container └── .gitignore # node_modules/ and build output (e.g. dist/) never committed ``` Constraints (framework-agnostic — any stack that keeps the layout above): - The app talks to the backend only over the HTTP APIs defined in `backend/cmd/webui/api` — no direct access to infra (DB, ES, rabbit) from the frontend. - Build output is disposable and git-ignored; committed sources live only in `src`/`public`; nginx serves the built assets in release. - Dev runs from the source mount (`../frontend:/app`) with `node_modules` provided by the image's anonymous volume — install new deps inside the container and commit the lockfile. ### deploy ```plaintext deploy ├── docker-compose.yml # release stack: multi-stage *.prod images, infra internal-only ├── docker-compose.dev.yml # dev stack: source mounts, target: dev, exposed ports, delve 2345 ├── prepare.sh # renders the config files below; rerun after changing templates ├── nginx │ ├── nginx.conf # main config, mounted :ro │ └── conf.d/default.conf # site config, mounted :ro ├── redis/redis.conf # mounted :ro ├── rabbitmq/rabbitmq.conf # mounted :ro ├── logs # host-side dirs bind-mounted into containers ({nginx,rabbitmq}) ├── {$service_name}/storage # file-storage path for services not using object storage └── .gitignore # ignores actual/rendered configs and logs/; tracks only *.example files ``` Constraints: - Only config examples (e.g. `*.conf.example`) are tracked by git; actual configs (rendered by `prepare.sh`) and log files are git-ignored. - Everything under `deploy/` that containers mount (`*.conf` and `prepare.sh` outputs) is config, not app code: edit it here, rerun `prepare.sh`, restart — never edit configs inside a running container. - Infra ports are exposed to the host only in `docker-compose.dev.yml`; release keeps them internal-network-only. - Persisted state lives only in the named `{$project_name}_*` volumes; bind-mounts are reserved for source, config, logs, and file storage. - Service-specific host dirs (storage, config) go under `deploy/{$service_name}/`, never loose at the repo root.