Files
book-comic-library/AGENTS.md
T
XingfenD 95317a0108 docs(readme): rewrite run/dev/migrate instructions for dev/release deploy spec
- scripts/smoke.sh: idempotent re-run (reuse existing member/library on 409)
- web client.ts: probe localStorage methods, not typeof (node 22+ stub regression)
2026-09-07 21:09:11 +08:00

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: 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:

.
├── 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.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)

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.
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)

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

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.