From 628d416dcd8fee1a09f4f0c4f92798acb827bbba Mon Sep 17 00:00:00 2001 From: Aska Date: Wed, 12 Aug 2026 21:26:58 +0800 Subject: [PATCH] feat(admin): add authenticated restart endpoint + fix admin username - POST /api/v1/admin/restart: admin-only restart (forks new process) - Fix: MemUserStore now uses cfg.AdminUsername instead of hardcoded 'admin' - RestartFunc injected by main.go for clean process fork + exit --- .env.bak | 74 ++++++++++++++++++++++++++ backend/cmd/cyrene/main.go | 20 ++++++- backend/services/gateway/api/auth.go | 12 +++-- backend/services/gateway/api/router.go | 15 ++++++ 4 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 .env.bak diff --git a/.env.bak b/.env.bak new file mode 100644 index 0000000..4255edc --- /dev/null +++ b/.env.bak @@ -0,0 +1,74 @@ +# ============================================ +# Cyrene v2 配置文件 (.env) +# 复制为 .env 后修改 +# ============================================ + +# ---- 运行环境 ---- +ENV=development +LOG_LEVEL=debug + +# ---- 服务器 ---- +PORT=8080 +HOST=0.0.0.0 + +# ---- 数据库 (PostgreSQL + pgvector) ---- +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_USER=cyrene +POSTGRES_PASSWORD=cyrene_pass +POSTGRES_DB=cyrene_ai + +# ---- LLM API ---- +LLM_API_URL=https://api.deepseek.com/v1 +LLM_API_KEY=sk-6086952ab537454ab3bc0cf9b0c92343 +LLM_MODEL=deepseek-v4-flash +LLM_FALLBACK_MODEL=deepseek-v4-flash + +# ---- 记忆嵌入 (可选) ---- +# 不配置时使用本地 n-gram 文本相似度 +# EMBEDDING_API_URL=http://localhost:11434/v1 +# EMBEDDING_MODEL=bge-m3 + +# ---- 认证 ---- +JWT_SECRET=change-me-to-a-random-string-at-least-32-characters-long +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin12345 +REGISTRATION_ENABLED=true + +# ---- IoT 设备提供者 ---- +# mock = 8 个模拟智能家居设备 (开发测试用) +# none = 禁用 IoT +# 未来: homeassistant / mqtt +IOT_PROVIDER=mock + +# ---- 语音服务 ---- +# DASHSCOPE_API_KEY=sk-xxxxx +TTS_PROVIDER=edge-tts +ASR_PROVIDER=dashscope + +# ---- 后台思考 ---- +ENABLE_BACKGROUND_THINKING=true +THINK_INTERVAL_SEC=300 +THINK_SILENCE_TIMEOUT_SEC=120 + +# ---- 平台桥接 ---- +# 桥接配置不在 .env 中管理。 +# 通过 Admin 面板 → 平台桥接 页面进行配置。 +# 配置将保存在 configs/bridge.json,支持热重载。 + +# ---- 搜索 (SearXNG) ---- +# SEARXNG_URL=http://localhost:8088 + +# ---- 数据目录 ---- +DATA_DIR=./data +PERSONA_DIR=./internal/persona + +# ---- 跨域 ---- +ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000 + +# ---- WebSocket ---- +WS_MAX_CONNECTIONS=1000 +SESSION_IDLE_TIMEOUT_MIN=30 + +# ---- 时区 ---- +TZ=Asia/Shanghai diff --git a/backend/cmd/cyrene/main.go b/backend/cmd/cyrene/main.go index 442e958..4ebe6a4 100644 --- a/backend/cmd/cyrene/main.go +++ b/backend/cmd/cyrene/main.go @@ -212,7 +212,7 @@ func main() { log.Printf("[bridge] config store ready (path=%s, %d adapters)", bridgeCfgPath, bridgeStore.Count()) // === 12. Gateway === - userStore := api.NewMemUserStore(cfg.AdminPassword) + userStore := api.NewMemUserStore(cfg.AdminUsername, cfg.AdminPassword) authMgr := api.NewRealAuthManager(cfg.JWTSecret, cfg.JWTExpiryHours, userStore) // Build dependencies for gateway handlers @@ -254,6 +254,24 @@ func main() { srv := &http.Server{Addr: addr, Handler: mainMux} // === 13. Start === + // Register restart handler — forks a new process and exits + api.RestartFunc = func() { + log.Printf("[gateway] restarting...") + reg.Shutdown() + srv.Close() + // Re-exec self with same args and env + exe, _ := os.Executable() + proc, err := os.StartProcess(exe, os.Args, &os.ProcAttr{ + Env: os.Environ(), + Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, + }) + if err != nil { + log.Fatalf("[gateway] restart failed: %v", err) + } + log.Printf("[gateway] new process PID=%d, exiting...", proc.Pid) + os.Exit(0) + } + go func() { log.Printf("[gateway] listening on %s", addr) log.Printf("[gateway] plugins=%d tools=%d", reg.Count(), len(reg.GetAllToolDefinitions())) diff --git a/backend/services/gateway/api/auth.go b/backend/services/gateway/api/auth.go index d77c036..297df82 100644 --- a/backend/services/gateway/api/auth.go +++ b/backend/services/gateway/api/auth.go @@ -28,14 +28,16 @@ type MemUserStore struct { users map[string]*UserRecord } -// NewMemUserStore creates an in-memory user store with a default admin. -func NewMemUserStore(adminPassword string) *MemUserStore { +// NewMemUserStore creates an in-memory user store with the configured admin account. +func NewMemUserStore(adminUsername, adminPassword string) *MemUserStore { s := &MemUserStore{users: make(map[string]*UserRecord)} hash, _ := auth.HashPassword(adminPassword) - s.users["admin"] = &UserRecord{ - UserID: "admin", Username: "admin", - PasswordHash: hash, Role: "admin", + s.users[adminUsername] = &UserRecord{ + UserID: adminUsername, + Username: adminUsername, + PasswordHash: hash, + Role: "admin", } return s } diff --git a/backend/services/gateway/api/router.go b/backend/services/gateway/api/router.go index 8507745..6799156 100644 --- a/backend/services/gateway/api/router.go +++ b/backend/services/gateway/api/router.go @@ -136,6 +136,7 @@ func (r *Router) setupRoutes() { // Admin r.mux.HandleFunc("/api/v1/admin/dashboard", r.authMW(r.adminMW(r.adminDashboardHandler))) + r.mux.HandleFunc("/api/v1/admin/restart", r.authMW(r.adminMW(r.adminRestartHandler))) r.mux.HandleFunc("/api/v1/admin/llm-calls", r.authMW(r.adminMW(r.llmCallsHandler))) r.mux.HandleFunc("/api/v1/admin/system", r.authMW(r.adminMW(r.systemInfoHandler))) r.mux.HandleFunc("/api/v1/admin/plugins", r.authMW(r.adminMW(r.pluginListHandler))) @@ -417,6 +418,19 @@ func (r *Router) adminDashboardHandler(w http.ResponseWriter, _ *http.Request) { }) } +// RestartFunc is injected by main.go — called to restart the process. +var RestartFunc func() + +func (r *Router) adminRestartHandler(w http.ResponseWriter, _ *http.Request) { + writeJSON(w, 200, M{"restart": "ok", "message": "Cyrene is restarting..."}) + go func() { + time.Sleep(200 * time.Millisecond) // Let the response flush + if RestartFunc != nil { + RestartFunc() + } + }() +} + // --- Admin extra handlers --- func (r *Router) llmCallsHandler(w http.ResponseWriter, req *http.Request) { @@ -638,3 +652,4 @@ func (r *Router) userID(req *http.Request) string { return req.Header.Get("X-Use // Ensure unused imports don't cause issues +// Debug: this will be overwritten