mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
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:
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
FROM node:25-alpine
|
||||||
|
RUN npm install -g pnpm@10
|
||||||
|
WORKDIR /workspace
|
||||||
@@ -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
|
||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
@@ -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}"
|
||||||
|
|||||||
Reference in New Issue
Block a user