- scripts/smoke.sh: idempotent re-run (reuse existing member/library on 409) - web client.ts: probe localStorage methods, not typeof (node 22+ stub regression)
7.8 KiB
7.8 KiB
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: devimages, workers rungo 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
*.prodDockerfiles (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}_datapersist infra state to Docker volumes — identical names across the two compose files, so dev and release see the same data. Onlydown -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) plusdeploy/logs/{nginx,rabbitmq}are mounted from the host — these come fromprepare.sh, so stale files in the container mean you forgot to rerun it. - Source mounts (dev only):
../backend:/app(which rungo mod download && go run ./cmd/...),../frontend:/appwith an anonymous volume on/app/node_modules(so the image's deps survive the mount — delete containers withdown, notrm, 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}/storageas the storage path into the container.
Project structure
Basically, the project consists of the directories docs, backend, frontend, deploy, plus optional collab* services:
.
├── 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:
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.mdandREADME_zh.mdstay content-equivalent; update them in the same change.- Documentation lives only under
docs/— no stray*.mdat the repo root besidesAGENTS.mdandLICENSE.
backend (golang)
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 onemainpackage;main.goonly bootstraps — load config, wire dependencies, start the server/CLI, handle graceful shutdown. No business logic here, and nothing outsidecmdimportscmd.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 inpkg.pkg: opt-in public API surface — keep it small and stable, and never leakinternaltypes through its APIs.
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 behindinternalboundaries. apiis the single source of truth for the endpoint contract; the frontend aligns with it.- Code shared between
cliandwebuibelongs ininternal(orpkg), never imported from one command by the other.
frontend (node)
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) withnode_modulesprovided by the image's anonymous volume — install new deps inside the container and commit the lockfile.
deploy
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 byprepare.sh) and log files are git-ignored. - Everything under
deploy/that containers mount (*.confandprepare.shoutputs) is config, not app code: edit it here, rerunprepare.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.