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.
This commit is contained in:
BBQ
2026-02-26 17:32:19 +08:00
committed by GitHub
parent 19ab2fef3a
commit d6aebf654f
19 changed files with 354 additions and 103 deletions
+22
View File
@@ -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
+1
View File
@@ -88,6 +88,7 @@ Thumbs.db
*.tmp *.tmp
*.temp *.temp
.cache/ .cache/
tmp/
docs/docs/.vitepress/cache docs/docs/.vitepress/cache
.pnpm-store .pnpm-store
+5 -5
View File
@@ -97,16 +97,16 @@ Memoh/
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `mise run dev` | Start the full dev environment (backend + agent gateway + frontend) | | `mise run dev` | Start the containerized dev environment (all services) |
| `mise run setup` | Initialize dev environment (sqlc gen + DB migration + dependency install) | | `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 sqlc-generate` | Regenerate Go code after modifying SQL files |
| `mise run swagger-generate` | Generate Swagger documentation | | `mise run swagger-generate` | Generate Swagger documentation |
| `mise run sdk-generate` | Generate TypeScript SDK (depends on swagger-generate) | | `mise run sdk-generate` | Generate TypeScript SDK (depends on swagger-generate) |
| `mise run db-up` | Initialize and migrate the database | | `mise run db-up` | Initialize and migrate the database |
| `mise run db-down` | Drop 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 ### Docker Deployment
+20 -26
View File
@@ -2,7 +2,7 @@
## Prerequisites ## 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) - [mise](https://mise.jdx.dev/) (task runner & toolchain manager)
### Install mise ### Install mise
@@ -21,53 +21,47 @@ winget install jdx.mise
```bash ```bash
mise install # Install toolchains (Go, Node, Bun, pnpm, sqlc) mise install # Install toolchains (Go, Node, Bun, pnpm, sqlc)
mise run setup # Start infra + copy config + migrate DB + install deps mise run setup # Copy config + install deps
mise run dev # Start server + agent + web (all hot-reload) mise run dev # Start full containerized dev environment
``` ```
That's it. `setup` handles everything automatically: That's it. `dev` launches everything in Docker containers:
1. Copies `conf/app.dev.toml``config.toml` (if not exists) 1. PostgreSQL + Qdrant (infrastructure)
2. Starts PostgreSQL + Qdrant via `devenv/docker-compose.yml` 2. Database migrations (auto-run on startup)
3. Runs database migrations 3. Go server with containerd (hot-reload via `go run`)
4. Installs Go/Node/pnpm dependencies 4. Agent Gateway (Bun, hot-reload)
5. Web frontend (Vite, hot-reload)
## Daily Development ## Daily Development
```bash ```bash
mise run dev # Start all services with hot-reload mise run dev # Start all services
``` mise run dev:down # Stop all services
mise run dev:logs # View logs
## Infrastructure mise run dev:restart -- server # Restart a specific service
```bash
mise run infra # Start dev postgres + qdrant
mise run infra-down # Stop dev infrastructure
mise run infra-logs # View infrastructure logs
``` ```
## More Commands ## More Commands
| Command | Description | | Command | Description |
| ------- | ----------- | | ------- | ----------- |
| `mise run dev` | Start development environment | | `mise run dev` | Start containerized dev environment |
| `mise run setup` | Full setup (infra + config + migrate + deps) | | `mise run dev:down` | Stop dev environment |
| `mise run infra` | Start dev infrastructure only | | `mise run dev:logs` | View dev logs |
| `mise run infra-down` | Stop dev infrastructure | | `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-up` | Run database migrations |
| `mise run db-down` | Roll back database migrations | | `mise run db-down` | Roll back database migrations |
| `mise run swagger-generate` | Generate Swagger documentation | | `mise run swagger-generate` | Generate Swagger documentation |
| `mise run sdk-generate` | Generate TypeScript SDK | | `mise run sdk-generate` | Generate TypeScript SDK |
| `mise run sqlc-generate` | Generate SQL code | | `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 ## Project Layout
``` ```
conf/ — Configuration templates (app.example.toml, app.dev.toml, app.docker.toml) conf/ — Configuration templates (app.example.toml, app.dev.toml, app.docker.toml)
devenv/ — Development infrastructure (docker-compose for postgres + qdrant) devenv/ — Containerized development environment (docker-compose)
docker/ — Production Docker build & runtime (Dockerfiles, entrypoints) docker/ — Docker build & runtime (Dockerfiles, entrypoints)
cmd/ — Go application entry points cmd/ — Go application entry points
internal/ — Go backend core code internal/ — Go backend core code
agent/ — Agent Gateway (Bun/Elysia) agent/ — Agent Gateway (Bun/Elysia)
+15 -14
View File
@@ -17,19 +17,20 @@ email = "dev@memoh.local"
jwt_secret = "memoh-dev-secret-do-not-use-in-production" jwt_secret = "memoh-dev-secret-do-not-use-in-production"
jwt_expires_in = "168h" jwt_expires_in = "168h"
# Containerd is typically not available in local development. [containerd]
# Uncomment and configure if you have containerd running locally. socket_path = "/run/containerd/containerd.sock"
# [containerd] namespace = "default"
# socket_path = "/run/containerd/containerd.sock"
# namespace = "default"
# [mcp] [mcp]
# image = "docker.io/library/memoh-mcp:dev" # registry = "memoh.cn" # Uncomment for China mainland mirror
# snapshotter = "overlayfs" image = "memohai/mcp:latest"
# data_root = "data" snapshotter = "overlayfs"
data_root = "/opt/memoh/data"
cni_bin_dir = "/opt/cni/bin"
cni_conf_dir = "/etc/cni/net.d"
[postgres] [postgres]
host = "127.0.0.1" host = "postgres"
port = 5432 port = 5432
user = "memoh" user = "memoh"
password = "memoh123" password = "memoh123"
@@ -37,16 +38,16 @@ database = "memoh"
sslmode = "disable" sslmode = "disable"
[qdrant] [qdrant]
base_url = "http://127.0.0.1:6334" base_url = "http://qdrant:6334"
api_key = "" api_key = ""
collection = "memory" collection = "memory"
timeout_seconds = 10 timeout_seconds = 10
[agent_gateway] [agent_gateway]
host = "127.0.0.1" host = "agent"
port = 8081 port = 8081
server_addr = ":8080" server_addr = "server:8080"
[web] [web]
host = "127.0.0.1" host = "0.0.0.0"
port = 8082 port = 8082
+2 -1
View File
@@ -23,7 +23,8 @@ socket_path = "/run/containerd/containerd.sock"
namespace = "default" namespace = "default"
[mcp] [mcp]
image = "docker.io/library/memoh-mcp:latest" # registry = "memoh.cn" # Uncomment for China mainland mirror
image = "memohai/mcp:latest"
snapshotter = "overlayfs" snapshotter = "overlayfs"
data_root = "/opt/memoh/data" data_root = "/opt/memoh/data"
+2 -1
View File
@@ -23,7 +23,8 @@ socket_path = "/run/containerd/containerd.sock"
namespace = "default" namespace = "default"
[mcp] [mcp]
image = "docker.io/library/memoh-mcp:dev" # registry = "memoh.cn" # Uncomment for China mainland mirror
image = "memohai/mcp:latest"
snapshotter = "overlayfs" snapshotter = "overlayfs"
data_root = "data" data_root = "data"
cni_bin_dir = "/opt/cni/bin" cni_bin_dir = "/opt/cni/bin"
+2 -1
View File
@@ -24,7 +24,8 @@ socket_path = "npipe:////./pipe/containerd-containerd"
namespace = "default" namespace = "default"
[mcp] [mcp]
image = "docker.io/library/memoh-mcp:dev" # registry = "memoh.cn" # Uncomment for China mainland mirror
image = "memohai/mcp:latest"
snapshotter = "overlayfs" snapshotter = "overlayfs"
data_root = "data" data_root = "data"
+117
View File
@@ -34,8 +34,125 @@ services:
retries: 5 retries: 5
restart: unless-stopped 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: volumes:
postgres_data: postgres_data:
driver: local driver: local
qdrant_data: qdrant_data:
driver: local 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
+60
View File
@@ -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"]
+3
View File
@@ -0,0 +1,3 @@
FROM node:25-alpine
RUN npm install -g pnpm@10
WORKDIR /workspace
+45
View File
@@ -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
+15 -1
View File
@@ -12,7 +12,7 @@ const (
DefaultHTTPAddr = ":8080" DefaultHTTPAddr = ":8080"
DefaultNamespace = "default" DefaultNamespace = "default"
DefaultSocketPath = "/run/containerd/containerd.sock" DefaultSocketPath = "/run/containerd/containerd.sock"
DefaultMCPImage = "docker.io/library/memoh-mcp:latest" DefaultMCPImage = "memohai/mcp:latest"
DefaultDataRoot = "data" DefaultDataRoot = "data"
DefaultDataMount = "/data" DefaultDataMount = "/data"
DefaultCNIBinaryDir = "/opt/cni/bin" DefaultCNIBinaryDir = "/opt/cni/bin"
@@ -71,6 +71,7 @@ type SocktainerConfig struct {
} }
type MCPConfig struct { type MCPConfig struct {
Registry string `toml:"registry"`
Image string `toml:"image"` Image string `toml:"image"`
Snapshotter string `toml:"snapshotter"` Snapshotter string `toml:"snapshotter"`
DataRoot string `toml:"data_root"` DataRoot string `toml:"data_root"`
@@ -78,6 +79,19 @@ type MCPConfig struct {
CNIConfigDir string `toml:"cni_conf_dir"` 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 { type PostgresConfig struct {
Host string `toml:"host"` Host string `toml:"host"`
Port int `toml:"port"` Port int `toml:"port"`
+1 -4
View File
@@ -749,10 +749,7 @@ func snapshotLineage(root string, all []ctr.SnapshotInfo) ([]ctr.SnapshotInfo, b
// ---------- auth helpers ---------- // ---------- auth helpers ----------
func (h *ContainerdHandler) mcpImageRef() string { func (h *ContainerdHandler) mcpImageRef() string {
if h.cfg.Image != "" { return h.cfg.ImageRef()
return h.cfg.Image
}
return config.DefaultMCPImage
} }
// requireBotAccess extracts bot_id from path, validates user auth, and authorizes bot access. // requireBotAccess extracts bot_id from path, validates user auth, and authorizes bot access.
+1 -4
View File
@@ -338,10 +338,7 @@ func (m *Manager) dataMount() string {
} }
func (m *Manager) imageRef() string { func (m *Manager) imageRef() string {
if m.cfg.Image != "" { return m.cfg.ImageRef()
return m.cfg.Image
}
return config.DefaultMCPImage
} }
func validateBotID(botID string) error { func validateBotID(botID string) error {
+19 -40
View File
@@ -48,18 +48,27 @@ depends = [
description = "Generate SQL code" description = "Generate SQL code"
run = "sqlc generate" run = "sqlc generate"
[tasks.infra] [tasks.dev]
description = "Start dev infrastructure (postgres + qdrant)" description = "Start development environment"
run = "docker compose -f devenv/docker-compose.yml up -d" run = """
#!/bin/bash
set -e
cp conf/app.dev.toml config.toml
docker compose -f devenv/docker-compose.yml up --build
"""
[tasks.infra-down] [tasks."dev:down"]
description = "Stop dev infrastructure" description = "Stop development environment"
run = "docker compose -f devenv/docker-compose.yml down" run = "docker compose -f devenv/docker-compose.yml down --remove-orphans"
[tasks.infra-logs] [tasks."dev:logs"]
description = "View dev infrastructure logs" description = "View development logs"
run = "docker compose -f devenv/docker-compose.yml logs -f" 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] [tasks.db-up]
description = "Initialize and Migrate Database" description = "Initialize and Migrate Database"
run = "scripts/db-up.sh" run = "scripts/db-up.sh"
@@ -92,20 +101,10 @@ description = "Install CLI"
depends = ["//:pnpm-install"] depends = ["//:pnpm-install"]
run = "cd packages/cli && npm install -g" run = "cd packages/cli && npm install -g"
[task.install-socktainer] [tasks.install-socktainer]
description = "Install socktainer" description = "Install socktainer"
run = "brew tap socktainer/tap && brew 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] [tasks.setup]
description = "Setup development environment" description = "Setup development environment"
depends = [ depends = [
@@ -117,26 +116,6 @@ depends = [
run = """ run = """
#!/bin/bash #!/bin/bash
set -e set -e
cp conf/app.dev.toml config.toml
# 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
echo '✓ Setup complete! Run: mise run dev' echo '✓ Setup complete! Run: mise run dev'
""" """
+1 -1
View File
@@ -8,7 +8,7 @@ export const loadConfig = (path: string = './config.toml'): Config => {
} }
export const getBaseUrl = (config: 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) { if (!rawAddr) {
return 'http://127.0.0.1' return 'http://127.0.0.1'
+1
View File
@@ -61,6 +61,7 @@ export interface QdrantConfig {
export interface AgentGatewayConfig { export interface AgentGatewayConfig {
host: string; host: string;
port: number; port: number;
server_addr?: string;
} }
export interface WebConfig { export interface WebConfig {
+22 -5
View File
@@ -72,6 +72,7 @@ JWT_SECRET="$(gen_secret)"
PG_PASS="memoh123" PG_PASS="memoh123"
WORKSPACE="$WORKSPACE_DEFAULT" WORKSPACE="$WORKSPACE_DEFAULT"
MEMOH_DATA_DIR="$MEMOH_DATA_DIR_DEFAULT" MEMOH_DATA_DIR="$MEMOH_DATA_DIR_DEFAULT"
USE_CN_MIRROR=false
if [ "$SILENT" = false ]; then if [ "$SILENT" = false ]; then
echo "Configure Memoh (press Enter to use defaults):" > /dev/tty echo "Configure Memoh (press Enter to use defaults):" > /dev/tty
@@ -115,6 +116,12 @@ if [ "$SILENT" = false ]; then
read -r input < /dev/tty || true read -r input < /dev/tty || true
[ -n "$input" ] && PG_PASS="$input" [ -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 echo "" > /dev/tty
fi 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|jwt_secret = \".*\"|jwt_secret = \"${JWT_SECRET}\"|" config.toml
sed -i.bak "s|password = \"memoh123\"|password = \"${PG_PASS}\"|" config.toml sed -i.bak "s|password = \"memoh123\"|password = \"${PG_PASS}\"|" config.toml
export POSTGRES_PASSWORD="${PG_PASS}" 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 rm -f config.toml.bak
# Use generated config and data dir # Use generated config and data dir
@@ -152,13 +162,19 @@ export MEMOH_CONFIG=./config.toml
export MEMOH_DATA_DIR export MEMOH_DATA_DIR
mkdir -p "$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 ""
echo "${GREEN}Pulling latest Docker images...${NC}" echo "${GREEN}Pulling latest Docker images...${NC}"
$DOCKER compose pull $DOCKER compose $COMPOSE_FILES pull
echo "" echo ""
echo "${GREEN}Starting services (first startup may take a few minutes)...${NC}" echo "${GREEN}Starting services (first startup may take a few minutes)...${NC}"
$DOCKER compose up -d $DOCKER compose $COMPOSE_FILES up -d
echo "" echo ""
echo "${GREEN}========================================${NC}" echo "${GREEN}========================================${NC}"
@@ -171,9 +187,10 @@ echo " Agent Gateway: http://localhost:8081"
echo "" echo ""
echo " Admin login: ${ADMIN_USER} / ${ADMIN_PASS}" echo " Admin login: ${ADMIN_USER} / ${ADMIN_PASS}"
echo "" echo ""
COMPOSE_CMD="$DOCKER compose $COMPOSE_FILES"
echo "Commands:" echo "Commands:"
echo " cd ${INSTALL_DIR} && $DOCKER compose ps # Status" echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} ps # Status"
echo " cd ${INSTALL_DIR} && $DOCKER compose logs -f # Logs" echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} logs -f # Logs"
echo " cd ${INSTALL_DIR} && $DOCKER compose down # Stop" echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} down # Stop"
echo "" echo ""
echo "${YELLOW}First startup may take 1-2 minutes, please be patient.${NC}" echo "${YELLOW}First startup may take 1-2 minutes, please be patient.${NC}"