From d6aebf654f1af3237ebd7a7a52773836cdd0e422 Mon Sep 17 00:00:00 2001 From: BBQ <35603386+HoneyBBQ@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:32:19 +0800 Subject: [PATCH] feat(devenv): add containerized development environment (#116) * feat(devenv): add containerized development environment Replace local-process dev workflow with a fully containerized stack using docker compose. This enables consistent development across machines without requiring local Go/Node toolchains or containerd. - Add Dockerfile.server.dev with containerd + CNI networking support - Add Dockerfile.web.dev for frontend dev server - Add server-dev-entrypoint.sh for containerd lifecycle management - Expand devenv/docker-compose.yml with server, agent, web, migrate and deps services with proper health checks and dependency ordering - Update app.dev.toml to use container service names instead of localhost - Refactor mise.toml dev tasks to drive docker compose workflow - Support agent_gateway.server_addr in config package for inter-container communication * feat(devenv): add hot-reload and registry mirror support - Add air for Go server hot-reload in dev containers - Fix agent_gateway host in dev config (0.0.0.0 -> agent) - Add configurable registry mirror for China mainland users - Unify MCP image refs via MCPConfig.ImageRef() * feat(scripts): add China mainland mirror option to install script Prompt users to opt-in to memoh.cn mirror during installation, which applies docker-compose.cn.yml overlay and sets registry in config.toml for MCP image pulls. --- .air.toml | 22 ++++++ .gitignore | 1 + AGENTS.md | 10 +-- CONTRIBUTING.md | 46 ++++++------- conf/app.dev.toml | 29 ++++---- conf/app.docker.toml | 3 +- conf/app.example.toml | 3 +- conf/app.windows.toml | 3 +- devenv/docker-compose.yml | 117 ++++++++++++++++++++++++++++++++ docker/Dockerfile.server.dev | 60 ++++++++++++++++ docker/Dockerfile.web.dev | 3 + docker/server-dev-entrypoint.sh | 45 ++++++++++++ internal/config/config.go | 16 ++++- internal/handlers/containerd.go | 5 +- internal/mcp/manager.go | 5 +- mise.toml | 59 ++++++---------- packages/config/src/index.ts | 2 +- packages/config/src/types.ts | 1 + scripts/install.sh | 27 ++++++-- 19 files changed, 354 insertions(+), 103 deletions(-) create mode 100644 .air.toml create mode 100644 docker/Dockerfile.server.dev create mode 100644 docker/Dockerfile.web.dev create mode 100644 docker/server-dev-entrypoint.sh diff --git a/.air.toml b/.air.toml new file mode 100644 index 00000000..8df22911 --- /dev/null +++ b/.air.toml @@ -0,0 +1,22 @@ +root = "." +tmp_dir = "tmp" + +[build] +cmd = "go build -o ./tmp/memoh-server ./cmd/agent/main.go" +bin = "./tmp/memoh-server" +args_bin = ["serve"] +include_ext = ["go", "toml"] +include_dir = ["cmd", "internal", "conf"] +exclude_dir = ["tmp", "vendor", "node_modules", "packages", "agent", "docs", "devenv", "docker", "scripts", "db/migrations"] +exclude_regex = ["_test\\.go$"] +delay = 1000 +stop_on_error = true +send_interrupt = true +kill_delay = 500 + +[log] +time = false +main_only = true + +[misc] +clean_on_exit = true diff --git a/.gitignore b/.gitignore index 33664c70..377bf581 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ Thumbs.db *.tmp *.temp .cache/ +tmp/ docs/docs/.vitepress/cache .pnpm-store diff --git a/AGENTS.md b/AGENTS.md index 045bd361..687b3c68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,16 +97,16 @@ Memoh/ | Command | Description | |---------|-------------| -| `mise run dev` | Start the full dev environment (backend + agent gateway + frontend) | -| `mise run setup` | Initialize dev environment (sqlc gen + DB migration + dependency install) | +| `mise run dev` | Start the containerized dev environment (all services) | +| `mise run dev:down` | Stop the dev environment | +| `mise run dev:logs` | View dev environment logs | +| `mise run dev:restart` | Restart a service (e.g. `-- server`) | +| `mise run setup` | Copy config + install dependencies | | `mise run sqlc-generate` | Regenerate Go code after modifying SQL files | | `mise run swagger-generate` | Generate Swagger documentation | | `mise run sdk-generate` | Generate TypeScript SDK (depends on swagger-generate) | | `mise run db-up` | Initialize and migrate the database | | `mise run db-down` | Drop the database | -| `mise run //agent:dev` | Start Agent Gateway only | -| `mise run //cmd/agent:start` | Start the backend server only | -| `mise run //packages/web:dev` | Start the frontend dev server only | ### Docker Deployment diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbbe8383..7eb9dd08 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Prerequisites -- [Docker](https://docs.docker.com/get-docker/) (for dev infrastructure) +- [Docker](https://docs.docker.com/get-docker/) (for containerized dev environment) - [mise](https://mise.jdx.dev/) (task runner & toolchain manager) ### Install mise @@ -21,53 +21,47 @@ winget install jdx.mise ```bash mise install # Install toolchains (Go, Node, Bun, pnpm, sqlc) -mise run setup # Start infra + copy config + migrate DB + install deps -mise run dev # Start server + agent + web (all hot-reload) +mise run setup # Copy config + install deps +mise run dev # Start full containerized dev environment ``` -That's it. `setup` handles everything automatically: -1. Copies `conf/app.dev.toml` → `config.toml` (if not exists) -2. Starts PostgreSQL + Qdrant via `devenv/docker-compose.yml` -3. Runs database migrations -4. Installs Go/Node/pnpm dependencies +That's it. `dev` launches everything in Docker containers: +1. PostgreSQL + Qdrant (infrastructure) +2. Database migrations (auto-run on startup) +3. Go server with containerd (hot-reload via `go run`) +4. Agent Gateway (Bun, hot-reload) +5. Web frontend (Vite, hot-reload) ## Daily Development ```bash -mise run dev # Start all services with hot-reload -``` - -## Infrastructure - -```bash -mise run infra # Start dev postgres + qdrant -mise run infra-down # Stop dev infrastructure -mise run infra-logs # View infrastructure logs +mise run dev # Start all services +mise run dev:down # Stop all services +mise run dev:logs # View logs +mise run dev:restart -- server # Restart a specific service ``` ## More Commands | Command | Description | | ------- | ----------- | -| `mise run dev` | Start development environment | -| `mise run setup` | Full setup (infra + config + migrate + deps) | -| `mise run infra` | Start dev infrastructure only | -| `mise run infra-down` | Stop dev infrastructure | +| `mise run dev` | Start containerized dev environment | +| `mise run dev:down` | Stop dev environment | +| `mise run dev:logs` | View dev logs | +| `mise run dev:restart` | Restart a service (e.g. `-- server`) | +| `mise run setup` | Copy config + install deps | | `mise run db-up` | Run database migrations | | `mise run db-down` | Roll back database migrations | | `mise run swagger-generate` | Generate Swagger documentation | | `mise run sdk-generate` | Generate TypeScript SDK | | `mise run sqlc-generate` | Generate SQL code | -| `mise run //agent:dev` | Start agent gateway only | -| `mise run //cmd/agent:start` | Start main server only | -| `mise run //packages/web:dev` | Start web dev server only | ## Project Layout ``` conf/ — Configuration templates (app.example.toml, app.dev.toml, app.docker.toml) -devenv/ — Development infrastructure (docker-compose for postgres + qdrant) -docker/ — Production Docker build & runtime (Dockerfiles, entrypoints) +devenv/ — Containerized development environment (docker-compose) +docker/ — Docker build & runtime (Dockerfiles, entrypoints) cmd/ — Go application entry points internal/ — Go backend core code agent/ — Agent Gateway (Bun/Elysia) diff --git a/conf/app.dev.toml b/conf/app.dev.toml index e1ab91ca..0bb1483f 100644 --- a/conf/app.dev.toml +++ b/conf/app.dev.toml @@ -17,19 +17,20 @@ email = "dev@memoh.local" jwt_secret = "memoh-dev-secret-do-not-use-in-production" jwt_expires_in = "168h" -# Containerd is typically not available in local development. -# Uncomment and configure if you have containerd running locally. -# [containerd] -# socket_path = "/run/containerd/containerd.sock" -# namespace = "default" +[containerd] +socket_path = "/run/containerd/containerd.sock" +namespace = "default" -# [mcp] -# image = "docker.io/library/memoh-mcp:dev" -# snapshotter = "overlayfs" -# data_root = "data" +[mcp] +# registry = "memoh.cn" # Uncomment for China mainland mirror +image = "memohai/mcp:latest" +snapshotter = "overlayfs" +data_root = "/opt/memoh/data" +cni_bin_dir = "/opt/cni/bin" +cni_conf_dir = "/etc/cni/net.d" [postgres] -host = "127.0.0.1" +host = "postgres" port = 5432 user = "memoh" password = "memoh123" @@ -37,16 +38,16 @@ database = "memoh" sslmode = "disable" [qdrant] -base_url = "http://127.0.0.1:6334" +base_url = "http://qdrant:6334" api_key = "" collection = "memory" timeout_seconds = 10 [agent_gateway] -host = "127.0.0.1" +host = "agent" port = 8081 -server_addr = ":8080" +server_addr = "server:8080" [web] -host = "127.0.0.1" +host = "0.0.0.0" port = 8082 diff --git a/conf/app.docker.toml b/conf/app.docker.toml index c6dfaf71..a92c51fd 100644 --- a/conf/app.docker.toml +++ b/conf/app.docker.toml @@ -23,7 +23,8 @@ socket_path = "/run/containerd/containerd.sock" namespace = "default" [mcp] -image = "docker.io/library/memoh-mcp:latest" +# registry = "memoh.cn" # Uncomment for China mainland mirror +image = "memohai/mcp:latest" snapshotter = "overlayfs" data_root = "/opt/memoh/data" diff --git a/conf/app.example.toml b/conf/app.example.toml index 3645deb8..f579024f 100644 --- a/conf/app.example.toml +++ b/conf/app.example.toml @@ -23,7 +23,8 @@ socket_path = "/run/containerd/containerd.sock" namespace = "default" [mcp] -image = "docker.io/library/memoh-mcp:dev" +# registry = "memoh.cn" # Uncomment for China mainland mirror +image = "memohai/mcp:latest" snapshotter = "overlayfs" data_root = "data" cni_bin_dir = "/opt/cni/bin" diff --git a/conf/app.windows.toml b/conf/app.windows.toml index bafa1792..19441e6f 100644 --- a/conf/app.windows.toml +++ b/conf/app.windows.toml @@ -24,7 +24,8 @@ socket_path = "npipe:////./pipe/containerd-containerd" namespace = "default" [mcp] -image = "docker.io/library/memoh-mcp:dev" +# registry = "memoh.cn" # Uncomment for China mainland mirror +image = "memohai/mcp:latest" snapshotter = "overlayfs" data_root = "data" diff --git a/devenv/docker-compose.yml b/devenv/docker-compose.yml index 166de4f8..2451406a 100644 --- a/devenv/docker-compose.yml +++ b/devenv/docker-compose.yml @@ -34,8 +34,125 @@ services: retries: 5 restart: unless-stopped + deps: + build: + context: .. + dockerfile: docker/Dockerfile.web.dev + container_name: memoh-dev-deps + working_dir: /workspace + command: ["pnpm", "install"] + volumes: + - ..:/workspace + - node_modules:/workspace/node_modules + - pnpm_store:/root/.local/share/pnpm/store + restart: "no" + + migrate: + build: + context: .. + dockerfile: docker/Dockerfile.server.dev + container_name: memoh-dev-migrate + working_dir: /workspace + entrypoint: [] + command: ["go", "run", "./cmd/agent/main.go", "migrate", "up"] + environment: + CONFIG_PATH: /workspace/conf/app.dev.toml + volumes: + - ..:/workspace + - go_mod_cache:/go/pkg/mod + - go_build_cache:/root/.cache/go-build + depends_on: + postgres: + condition: service_healthy + qdrant: + condition: service_healthy + restart: "no" + + server: + build: + context: .. + dockerfile: docker/Dockerfile.server.dev + container_name: memoh-dev-server + working_dir: /workspace + privileged: true + pid: host + command: ["air", "-c", ".air.toml"] + environment: + CONFIG_PATH: /workspace/conf/app.dev.toml + volumes: + - ..:/workspace + - go_mod_cache:/go/pkg/mod + - go_build_cache:/root/.cache/go-build + - containerd_data:/var/lib/containerd + - server_cni_state:/var/lib/cni + - memoh_data:/opt/memoh/data + - /etc/localtime:/etc/localtime:ro + ports: + - "8080:8080" + healthcheck: + test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/health || exit 1"] + interval: 5s + timeout: 3s + retries: 20 + depends_on: + migrate: + condition: service_completed_successfully + qdrant: + condition: service_healthy + restart: unless-stopped + + agent: + image: oven/bun:1-alpine + container_name: memoh-dev-agent + working_dir: /workspace/agent + command: ["bun", "run", "--watch", "src/index.ts"] + volumes: + - ..:/workspace + - node_modules:/workspace/node_modules + ports: + - "8081:8081" + depends_on: + deps: + condition: service_completed_successfully + server: + condition: service_healthy + restart: unless-stopped + + web: + build: + context: .. + dockerfile: docker/Dockerfile.web.dev + container_name: memoh-dev-web + working_dir: /workspace/packages/web + command: ["pnpm", "dev"] + volumes: + - ..:/workspace + - node_modules:/workspace/node_modules + ports: + - "8082:8082" + depends_on: + deps: + condition: service_completed_successfully + server: + condition: service_healthy + restart: unless-stopped + volumes: postgres_data: driver: local qdrant_data: driver: local + go_mod_cache: + driver: local + go_build_cache: + driver: local + containerd_data: + driver: local + memoh_data: + driver: local + server_cni_state: + driver: local + node_modules: + driver: local + pnpm_store: + driver: local diff --git a/docker/Dockerfile.server.dev b/docker/Dockerfile.server.dev new file mode 100644 index 00000000..9c3a2185 --- /dev/null +++ b/docker/Dockerfile.server.dev @@ -0,0 +1,60 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.25-alpine + +WORKDIR /workspace + +RUN go install github.com/air-verse/air@latest + +RUN apk add --no-cache \ + bash \ + ca-certificates \ + cni-plugins \ + containerd \ + containerd-ctr \ + git \ + iptables \ + make \ + tzdata \ + wget \ + && mkdir -p /opt/cni/bin \ + && (cp -a /usr/lib/cni/. /opt/cni/bin/ 2>/dev/null || true) \ + && (cp -a /usr/libexec/cni/. /opt/cni/bin/ 2>/dev/null || true) \ + && mkdir -p /etc/cni/net.d /var/lib/cni /run/containerd /var/lib/containerd /opt/memoh/data + +RUN printf '%s\n' \ + '{' \ + ' "cniVersion": "1.0.0",' \ + ' "name": "memoh-cni",' \ + ' "plugins": [' \ + ' {' \ + ' "type": "bridge",' \ + ' "bridge": "cni0",' \ + ' "isGateway": true,' \ + ' "ipMasq": true,' \ + ' "hairpinMode": true,' \ + ' "ipam": {' \ + ' "type": "host-local",' \ + ' "ranges": [[' \ + ' { "subnet": "10.88.0.0/16" }' \ + ' ]],' \ + ' "routes": [' \ + ' { "dst": "0.0.0.0/0" }' \ + ' ]' \ + ' }' \ + ' },' \ + ' {' \ + ' "type": "portmap",' \ + ' "capabilities": { "portMappings": true }' \ + ' }' \ + ' ]' \ + '}' > /etc/cni/net.d/10-memoh.conflist + +COPY docker/server-dev-entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +VOLUME ["/var/lib/containerd", "/opt/memoh/data"] + +EXPOSE 8080 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/Dockerfile.web.dev b/docker/Dockerfile.web.dev new file mode 100644 index 00000000..218564bc --- /dev/null +++ b/docker/Dockerfile.web.dev @@ -0,0 +1,3 @@ +FROM node:25-alpine +RUN npm install -g pnpm@10 +WORKDIR /workspace diff --git a/docker/server-dev-entrypoint.sh b/docker/server-dev-entrypoint.sh new file mode 100644 index 00000000..14e7fa35 --- /dev/null +++ b/docker/server-dev-entrypoint.sh @@ -0,0 +1,45 @@ +#!/bin/sh +set -e + +# Setup cgroup v2 delegation for nested containerd. +if [ -f /sys/fs/cgroup/cgroup.controllers ]; then + mkdir -p /sys/fs/cgroup/init + while read -r pid; do + echo "$pid" > /sys/fs/cgroup/init/cgroup.procs 2>/dev/null || true + done < /sys/fs/cgroup/cgroup.procs + + sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ + > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true +fi + +mkdir -p /run/containerd +containerd & +CONTAINERD_PID=$! + +echo "Waiting for containerd..." +for i in $(seq 1 30); do + if ctr version >/dev/null 2>&1; then + break + fi + sleep 1 +done + +if ! ctr version >/dev/null 2>&1; then + echo "ERROR: containerd not responsive after 30s" + exit 1 +fi + +echo "containerd is ready, starting server command..." + +trap 'kill ${SERVER_PID:-0} 2>/dev/null || true; kill ${CONTAINERD_PID:-0} 2>/dev/null || true; wait' TERM INT + +"$@" & +SERVER_PID=$! + +wait $SERVER_PID +EXIT_CODE=$? + +kill $CONTAINERD_PID 2>/dev/null || true +wait $CONTAINERD_PID 2>/dev/null || true + +exit $EXIT_CODE diff --git a/internal/config/config.go b/internal/config/config.go index 83e4c4c1..1bb0796a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,7 +12,7 @@ const ( DefaultHTTPAddr = ":8080" DefaultNamespace = "default" DefaultSocketPath = "/run/containerd/containerd.sock" - DefaultMCPImage = "docker.io/library/memoh-mcp:latest" + DefaultMCPImage = "memohai/mcp:latest" DefaultDataRoot = "data" DefaultDataMount = "/data" DefaultCNIBinaryDir = "/opt/cni/bin" @@ -71,6 +71,7 @@ type SocktainerConfig struct { } type MCPConfig struct { + Registry string `toml:"registry"` Image string `toml:"image"` Snapshotter string `toml:"snapshotter"` DataRoot string `toml:"data_root"` @@ -78,6 +79,19 @@ type MCPConfig struct { CNIConfigDir string `toml:"cni_conf_dir"` } +// ImageRef returns the fully qualified image reference, prepending the +// registry mirror when configured (e.g. "memoh.cn/memohai/mcp:latest"). +func (c MCPConfig) ImageRef() string { + img := c.Image + if img == "" { + img = DefaultMCPImage + } + if c.Registry != "" { + return c.Registry + "/" + img + } + return img +} + type PostgresConfig struct { Host string `toml:"host"` Port int `toml:"port"` diff --git a/internal/handlers/containerd.go b/internal/handlers/containerd.go index 0383f681..152f0838 100644 --- a/internal/handlers/containerd.go +++ b/internal/handlers/containerd.go @@ -749,10 +749,7 @@ func snapshotLineage(root string, all []ctr.SnapshotInfo) ([]ctr.SnapshotInfo, b // ---------- auth helpers ---------- func (h *ContainerdHandler) mcpImageRef() string { - if h.cfg.Image != "" { - return h.cfg.Image - } - return config.DefaultMCPImage + return h.cfg.ImageRef() } // requireBotAccess extracts bot_id from path, validates user auth, and authorizes bot access. diff --git a/internal/mcp/manager.go b/internal/mcp/manager.go index cb8b1330..fb225480 100644 --- a/internal/mcp/manager.go +++ b/internal/mcp/manager.go @@ -338,10 +338,7 @@ func (m *Manager) dataMount() string { } func (m *Manager) imageRef() string { - if m.cfg.Image != "" { - return m.cfg.Image - } - return config.DefaultMCPImage + return m.cfg.ImageRef() } func validateBotID(botID string) error { diff --git a/mise.toml b/mise.toml index 9068b584..a1d19ff6 100644 --- a/mise.toml +++ b/mise.toml @@ -48,18 +48,27 @@ depends = [ description = "Generate SQL code" run = "sqlc generate" -[tasks.infra] -description = "Start dev infrastructure (postgres + qdrant)" -run = "docker compose -f devenv/docker-compose.yml up -d" +[tasks.dev] +description = "Start development environment" +run = """ +#!/bin/bash +set -e +cp conf/app.dev.toml config.toml +docker compose -f devenv/docker-compose.yml up --build +""" -[tasks.infra-down] -description = "Stop dev infrastructure" -run = "docker compose -f devenv/docker-compose.yml down" +[tasks."dev:down"] +description = "Stop development environment" +run = "docker compose -f devenv/docker-compose.yml down --remove-orphans" -[tasks.infra-logs] -description = "View dev infrastructure logs" +[tasks."dev:logs"] +description = "View development logs" run = "docker compose -f devenv/docker-compose.yml logs -f" +[tasks."dev:restart"] +description = "Restart a service (usage: mise run dev:restart -- server)" +run = "docker compose -f devenv/docker-compose.yml restart $@" + [tasks.db-up] description = "Initialize and Migrate Database" run = "scripts/db-up.sh" @@ -92,20 +101,10 @@ description = "Install CLI" depends = ["//:pnpm-install"] run = "cd packages/cli && npm install -g" -[task.install-socktainer] +[tasks.install-socktainer] description = "Install socktainer" run = "brew tap socktainer/tap && brew install socktainer" -[tasks.dev] -description = "Start development environment" -depends = [ - "//:swagger-generate", - "//:sdk-generate", - "//agent:dev", - "//cmd/agent:start", - "//packages/web:dev", -] - [tasks.setup] description = "Setup development environment" depends = [ @@ -117,26 +116,6 @@ depends = [ run = """ #!/bin/bash set -e - -# Auto-copy dev config if config.toml doesn't exist -if [ ! -f config.toml ]; then - cp conf/app.dev.toml config.toml - echo '✓ Copied conf/app.dev.toml → config.toml' -fi - -# Start dev infrastructure -docker compose -f devenv/docker-compose.yml up -d - -# Wait for postgres to be healthy -echo 'Waiting for postgres...' -until docker compose -f devenv/docker-compose.yml exec -T postgres pg_isready -U memoh >/dev/null 2>&1; do - sleep 1 -done -echo '✓ Postgres ready' - -# Run migrations -scripts/db-up.sh - +cp conf/app.dev.toml config.toml echo '✓ Setup complete! Run: mise run dev' """ - diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 47b5ac84..32ee2f98 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -8,7 +8,7 @@ export const loadConfig = (path: string = './config.toml'): Config => { } export const getBaseUrl = (config: Config) => { - const rawAddr = typeof config.server.addr === 'string' ? config.server.addr.trim() : '' + const rawAddr = (config.agent_gateway?.server_addr || config.server?.addr || '').trim() if (!rawAddr) { return 'http://127.0.0.1' diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 57355ee7..aaaefbbf 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -61,6 +61,7 @@ export interface QdrantConfig { export interface AgentGatewayConfig { host: string; port: number; + server_addr?: string; } export interface WebConfig { diff --git a/scripts/install.sh b/scripts/install.sh index d9e2a43a..df261269 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -72,6 +72,7 @@ JWT_SECRET="$(gen_secret)" PG_PASS="memoh123" WORKSPACE="$WORKSPACE_DEFAULT" MEMOH_DATA_DIR="$MEMOH_DATA_DIR_DEFAULT" +USE_CN_MIRROR=false if [ "$SILENT" = false ]; then echo "Configure Memoh (press Enter to use defaults):" > /dev/tty @@ -115,6 +116,12 @@ if [ "$SILENT" = false ]; then read -r input < /dev/tty || true [ -n "$input" ] && PG_PASS="$input" + printf " Use China mainland mirror? (y/N): " > /dev/tty + read -r input < /dev/tty || true + case "$input" in + [Yy]|[Yy][Ee][Ss]) USE_CN_MIRROR=true ;; + esac + echo "" > /dev/tty fi @@ -144,6 +151,9 @@ sed -i.bak "s|password = \"admin123\"|password = \"${ADMIN_PASS}\"|" config.toml sed -i.bak "s|jwt_secret = \".*\"|jwt_secret = \"${JWT_SECRET}\"|" config.toml sed -i.bak "s|password = \"memoh123\"|password = \"${PG_PASS}\"|" config.toml export POSTGRES_PASSWORD="${PG_PASS}" +if [ "$USE_CN_MIRROR" = true ]; then + sed -i.bak 's|# registry = "memoh.cn"|registry = "memoh.cn"|' config.toml +fi rm -f config.toml.bak # Use generated config and data dir @@ -152,13 +162,19 @@ export MEMOH_CONFIG=./config.toml export MEMOH_DATA_DIR mkdir -p "$MEMOH_DATA_DIR" +COMPOSE_FILES="-f docker-compose.yml" +if [ "$USE_CN_MIRROR" = true ]; then + COMPOSE_FILES="$COMPOSE_FILES -f docker/docker-compose.cn.yml" + echo "${GREEN}✓ Using China mainland mirror (memoh.cn)${NC}" +fi + echo "" echo "${GREEN}Pulling latest Docker images...${NC}" -$DOCKER compose pull +$DOCKER compose $COMPOSE_FILES pull echo "" echo "${GREEN}Starting services (first startup may take a few minutes)...${NC}" -$DOCKER compose up -d +$DOCKER compose $COMPOSE_FILES up -d echo "" echo "${GREEN}========================================${NC}" @@ -171,9 +187,10 @@ echo " Agent Gateway: http://localhost:8081" echo "" echo " Admin login: ${ADMIN_USER} / ${ADMIN_PASS}" echo "" +COMPOSE_CMD="$DOCKER compose $COMPOSE_FILES" echo "Commands:" -echo " cd ${INSTALL_DIR} && $DOCKER compose ps # Status" -echo " cd ${INSTALL_DIR} && $DOCKER compose logs -f # Logs" -echo " cd ${INSTALL_DIR} && $DOCKER compose down # Stop" +echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} ps # Status" +echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} logs -f # Logs" +echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} down # Stop" echo "" echo "${YELLOW}First startup may take 1-2 minutes, please be patient.${NC}"