Files
Memoh/internal/config/config.go
T
BBQ d6aebf654f 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.
2026-02-26 17:32:19 +08:00

189 lines
4.4 KiB
Go

package config
import (
"fmt"
"os"
"github.com/BurntSushi/toml"
)
const (
DefaultConfigPath = "config.toml"
DefaultHTTPAddr = ":8080"
DefaultNamespace = "default"
DefaultSocketPath = "/run/containerd/containerd.sock"
DefaultMCPImage = "memohai/mcp:latest"
DefaultDataRoot = "data"
DefaultDataMount = "/data"
DefaultCNIBinaryDir = "/opt/cni/bin"
DefaultCNIConfigDir = "/etc/cni/net.d"
DefaultJWTExpiresIn = "24h"
DefaultPGHost = "127.0.0.1"
DefaultPGPort = 5432
DefaultPGUser = "postgres"
DefaultPGDatabase = "memoh"
DefaultPGSSLMode = "disable"
DefaultQdrantURL = "http://127.0.0.1:6334"
DefaultQdrantCollection = "memory"
)
type Config struct {
Log LogConfig `toml:"log"`
Server ServerConfig `toml:"server"`
Admin AdminConfig `toml:"admin"`
Auth AuthConfig `toml:"auth"`
Containerd ContainerdConfig `toml:"containerd"`
MCP MCPConfig `toml:"mcp"`
Postgres PostgresConfig `toml:"postgres"`
Qdrant QdrantConfig `toml:"qdrant"`
AgentGateway AgentGatewayConfig `toml:"agent_gateway"`
}
type LogConfig struct {
Level string `toml:"level"`
Format string `toml:"format"`
}
type ServerConfig struct {
Addr string `toml:"addr"`
}
type AdminConfig struct {
Username string `toml:"username"`
Password string `toml:"password"`
Email string `toml:"email"`
}
type AuthConfig struct {
JWTSecret string `toml:"jwt_secret"`
JWTExpiresIn string `toml:"jwt_expires_in"`
}
type ContainerdConfig struct {
SocketPath string `toml:"socket_path"`
Namespace string `toml:"namespace"`
Socktainer SocktainerConfig `toml:"socktainer"`
}
type SocktainerConfig struct {
SocketPath string `toml:"socket_path"`
BinaryPath string `toml:"binary_path"`
}
type MCPConfig struct {
Registry string `toml:"registry"`
Image string `toml:"image"`
Snapshotter string `toml:"snapshotter"`
DataRoot string `toml:"data_root"`
CNIBinaryDir string `toml:"cni_bin_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 {
Host string `toml:"host"`
Port int `toml:"port"`
User string `toml:"user"`
Password string `toml:"password"`
Database string `toml:"database"`
SSLMode string `toml:"sslmode"`
}
type QdrantConfig struct {
BaseURL string `toml:"base_url"`
APIKey string `toml:"api_key"`
Collection string `toml:"collection"`
TimeoutSeconds int `toml:"timeout_seconds"`
}
type AgentGatewayConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
}
func (c AgentGatewayConfig) BaseURL() string {
host := c.Host
if host == "" {
host = "127.0.0.1"
}
port := c.Port
if port == 0 {
port = 8081
}
return "http://" + host + ":" + fmt.Sprint(port)
}
func Load(path string) (Config, error) {
cfg := Config{
Log: LogConfig{
Level: "info",
Format: "text",
},
Server: ServerConfig{
Addr: DefaultHTTPAddr,
},
Admin: AdminConfig{
Username: "admin",
Password: "change-your-password-here",
Email: "you@example.com",
},
Auth: AuthConfig{
JWTExpiresIn: DefaultJWTExpiresIn,
},
Containerd: ContainerdConfig{
SocketPath: DefaultSocketPath,
Namespace: DefaultNamespace,
},
MCP: MCPConfig{
Image: DefaultMCPImage,
DataRoot: DefaultDataRoot,
CNIBinaryDir: DefaultCNIBinaryDir,
CNIConfigDir: DefaultCNIConfigDir,
},
Postgres: PostgresConfig{
Host: DefaultPGHost,
Port: DefaultPGPort,
User: DefaultPGUser,
Database: DefaultPGDatabase,
SSLMode: DefaultPGSSLMode,
},
Qdrant: QdrantConfig{
BaseURL: DefaultQdrantURL,
Collection: DefaultQdrantCollection,
},
AgentGateway: AgentGatewayConfig{
Host: "127.0.0.1",
Port: 8081,
},
}
if path == "" {
path = DefaultConfigPath
}
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return cfg, nil
}
return cfg, err
}
if _, err := toml.DecodeFile(path, &cfg); err != nil {
return cfg, err
}
return cfg, nil
}