Files
pj0235-eai_agentplatform/eai_agentplatform/backend-go/internal/config/config.go
T
eaiadminandClaude Code 20df2ff06e feat: SQLite 定期备份内置进二进制
SQLite 是单文件、没有外部主从,坏一份就是全丢,而 data/ 一直被 gitignore、
此前没有任何备份。备份做进二进制,不依赖外部 cron / systemd timer。

- 快照用 VACUUM INTO,不用 cp 主库:服务边跑边写时 cp 可能拷到写了一半的页
- 启动时补一次,之后每小时醒一次看是否到期;默认间隔 24h、保留最近 7 份
- 产出自检 SQLite 文件头,不合格即删并报错,不留「看着像备份的废物」
- 新增 -backup 子命令,供升级 / 迁移 / 交付前手工备一份
- 落 data/backups/ —— systemd 单元是 ProtectSystem=strict,只放开了 data/ 可写
- 边界写进文档:只防误删 / 误改 / 写坏,不防整盘损坏,跨机保存仍需人工

配套:
- deploy/DELIVERY.md 新增第 7 节「数据备份与恢复」+ 红线条目
- clonezilla-cleanup.sh 提示克隆前清空 data/backups/,避免原型机测试数据被带走
- deploy/eaisalestrain.env 补 4 个备份环境变量
- PROJECT_STATE.md 记 D24

另:清理 worker_task 中 17 条没聊过天的历史空任务(id 1-11、13-18),
删前先备份、清单经确认,删除程序带「命中数与白名单不一致即中止」保险。
该项为一次性数据操作,不产生代码改动。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-14 16:33:05 +08:00

143 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"os"
"path/filepath"
"strconv"
"strings"
)
// Config 全局配置,从环境变量读取(带默认值)。
// system_config 表中的值在运行时覆盖对应字段(见 service)。
type Config struct {
Port string
DBPath string // SQLite 文件路径
JWTSecret string
JWTExpireMin int
LLMBaseURL string
LLMAPIKey string
LLMModel string
EmbedModel string
KnowledgeServiceURL string
KnowledgeIndexDir string
KBDataDir string
KnowledgeSourceDir string
// 定期备份。SQLite 是单文件、又没有任何外部主从,坏一份就是全丢,
// 所以默认开着:启动补一次,之后每 BackupIntervalHours 小时一次,保留最近 N 份。
BackupEnabled bool
BackupDir string
BackupKeep int
BackupIntervalHours int
FileMaxDoc int64
FileMaxVideo int64
ChunkThreshold int64
LibreOfficeBin string
PdftotextBin string
KingdeeBaseURL string
KingdeeAccountID string
KingdeeUsername string
KingdeePassword string
KingdeeLCID int
KingdeeTimeoutSec int
}
// Load 读取环境变量构造配置。缺失的用默认值(部署时通过 .env / systemd 注入)。
func Load() *Config {
baseDir := backendBaseDir()
return &Config{
Port: getenv("PORT", "8080"),
DBPath: getenv("DB_PATH", filepath.Join(baseDir, "data", "eai_agentplatform.db")),
JWTSecret: getenv("JWT_SECRET", "change-this-to-a-strong-secret-in-production"),
JWTExpireMin: getenvInt("JWT_EXPIRE_MINUTES", 480),
LLMBaseURL: getenv("LLM_BASE_URL", "http://127.0.0.1:11434/v1"),
LLMAPIKey: getenv("LLM_API_KEY", "ollama"),
LLMModel: getenv("LLM_MODEL", "qwen2.5:7b"),
EmbedModel: getenv("EMBED_MODEL", "bge-m3"),
KnowledgeServiceURL: getenv("KNOWLEDGE_SERVICE_URL", "http://127.0.0.1:10233"),
KnowledgeIndexDir: getenv("KNOWLEDGE_INDEX_DIR", filepath.Join(baseDir, "data", "faiss")),
KBDataDir: getenv("KB_DATA_DIR", filepath.Join(baseDir, "data", "kb_data")),
KnowledgeSourceDir: getenv("KNOWLEDGE_SOURCE_DIR", filepath.Join(baseDir, "knowledge_source")),
// 备份目录必须落在 data/ 下:systemd 单元是 ProtectSystem=strict,
// 只有 data/ 可写(ReadWritePaths),换到别处会静默失败。
BackupEnabled: getenvBool("BACKUP_ENABLED", true),
BackupDir: getenv("BACKUP_DIR", filepath.Join(baseDir, "data", "backups")),
BackupKeep: getenvInt("BACKUP_KEEP", 7),
BackupIntervalHours: getenvInt("BACKUP_INTERVAL_HOURS", 24),
FileMaxDoc: getenvInt64("FILE_MAX_SIZE_DOC", 209715200),
FileMaxVideo: getenvInt64("FILE_MAX_SIZE_VIDEO", 2147483648),
ChunkThreshold: getenvInt64("CHUNK_THRESHOLD", 104857600),
LibreOfficeBin: getenv("LIBREOFFICE_BIN", "libreoffice"),
PdftotextBin: getenv("PDFTOTEXT_BIN", "pdftotext"),
KingdeeBaseURL: getenv("KINGDEE_BASE_URL", ""),
KingdeeAccountID: getenv("KINGDEE_ACCOUNT_ID", ""),
KingdeeUsername: getenv("KINGDEE_USERNAME", ""),
KingdeePassword: getenv("KINGDEE_PASSWORD", ""),
KingdeeLCID: getenvInt("KINGDEE_LCID", 2052),
KingdeeTimeoutSec: getenvInt("KINGDEE_TIMEOUT_SEC", 15),
}
}
func backendBaseDir() string {
if exe, err := os.Executable(); err == nil && exe != "" {
return filepath.Clean(filepath.Join(filepath.Dir(exe), ".."))
}
if wd, err := os.Getwd(); err == nil && wd != "" {
return wd
}
return "."
}
func getenv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
// getenvBool 只认显式的假值,其余(含没设)都算真 —— 默认开着的东西,
// 不该因为写了个看不懂的值就悄悄关掉。
func getenvBool(key string, def bool) bool {
v := strings.ToLower(strings.TrimSpace(os.Getenv(key)))
switch v {
case "":
return def
case "0", "false", "no", "off":
return false
default:
return true
}
}
func getenvInt(key string, def int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return def
}
func getenvInt64(key string, def int64) int64 {
if v := os.Getenv(key); v != "" {
if n, err := strconv.ParseInt(v, 10, 64); err == nil {
return n
}
}
return def
}