From 24b940076762beb078e6e7a40288cd6bdad39b77 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 23 Jul 2026 17:57:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A6=BB=E7=BA=BF=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E5=8C=85=E4=B8=80=E4=BD=93=E5=8C=96=EF=BC=8C=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=AF=BC=E5=85=A5=E7=A7=8D=E5=AD=90=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=8C=E4=BA=BA=E8=84=B8=E5=BA=93=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - package.sh: 打包包含 configs 种子数据、tools 构建工具、deps 离线依赖、scripts 辅助脚本 - ImportSeedAssets: 首次启动检测空库自动导入 templates/profiles/overlays - face_gallery.html: 添加构建人脸库按钮 - device.html: 无标准配置时显示设备实际模型/资源,无人脸库配置时隐藏按钮 - register-face-gallery.py: 手动注册已构建人脸库到 standard_resources - deployment.md: 更新部署流程反映新包结构 - .gitignore: 排除 *.tar.gz 构建产物 --- .gitignore | 1 + assets-import.tar.gz | Bin 6494 -> 0 bytes cmd/safesightd/main.go | 16 ++-- docs/deployment.md | 56 ++++++++----- internal/service/standard_templates.go | 86 ++++++++++++++++++++ internal/web/ui/templates/device.html | 6 ++ internal/web/ui/templates/face_gallery.html | 3 + scripts/deploy/register-face-gallery.py | 17 ++++ scripts/package.sh | 86 +++++++++++++++++--- 9 files changed, 230 insertions(+), 41 deletions(-) delete mode 100644 assets-import.tar.gz create mode 100644 scripts/deploy/register-face-gallery.py diff --git a/.gitignore b/.gitignore index 1acd3ff..2443978 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ nul dataset/ __pycache__/ managerd.db +assets-import.tar.gz diff --git a/assets-import.tar.gz b/assets-import.tar.gz deleted file mode 100644 index e7544b3b17e67beed63ce35af2e1a37f1b910f38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6494 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 E0Q@=5?f?J) diff --git a/cmd/safesightd/main.go b/cmd/safesightd/main.go index 84f583d..b2baa5a 100644 --- a/cmd/safesightd/main.go +++ b/cmd/safesightd/main.go @@ -42,15 +42,13 @@ func main() { taskRepo := storage.NewTasksRepo(store.DB()) assetsRepo := storage.NewAssetsRepo(store.DB()) modelsRepo := storage.NewModelsRepo(store.DB()) - if imported, err := service.ImportStandardTemplatesFromDir(assetsRepo, filepath.Join("templates", "standard_templates")); err != nil { - log.Fatalf("import standard templates: %v", err) - } else if imported > 0 { - log.Printf("imported %d standard templates", imported) - } - if imported, err := service.ImportStandardOverlaysFromDir(assetsRepo, filepath.Join("templates", "standard_overlays")); err != nil { - log.Fatalf("import standard overlays: %v", err) - } else if imported > 0 { - log.Printf("imported %d standard overlays", imported) + // Seed config assets on first run (when DB templates table is empty). + // Reads from configs/ directory bundled in the deployment package. + seedCount, seedErr := service.ImportSeedAssets(assetsRepo, "configs") + if seedErr != nil { + log.Printf("import seed assets: %v", seedErr) + } else if seedCount > 0 { + log.Printf("imported %d seed config assets", seedCount) } if err := service.EnsureDefaultIntegrations(assetsRepo); err != nil { log.Printf("ensure default integrations: %v", err) diff --git a/docs/deployment.md b/docs/deployment.md index 2a7b188..3deca8f 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -9,46 +9,44 @@ cd safesight-control bash scripts/package.sh ``` -生成 `safesight-control-*.tar.gz`,包含 ARM64 和 AMD64 两个二进制、service 文件、配置示例。 +生成 `safesight-control-*.tar.gz`,包含: +- ARM64 + AMD64 二进制 +- service 文件、配置示例 +- **种子配置数据**(configs/:templates、profiles、overlays) +- **人脸库构建工具**(tools/:Python 脚本 + ONNX 模型) +- **离线 Python 依赖**(deps/:onnxruntime wheel) +- **辅助脚本**(scripts/:import-assets、register-face-gallery) --- -## 部署 - -### 部署到 Edge 设备(ARM64) +## 部署(ARM64) ```bash # 传到设备 -scp safesight-control-*.tar.gz orangepi@10.0.0.81:/tmp/ +scp safesight-control-*.tar.gz user@server:/tmp/ -# 在设备上 +# 解压 cd /tmp && tar -xzf safesight-control-*.tar.gz cd safesight-control-* -# 安装二进制 -sudo mkdir -p /opt/safesightd/bin +# 安装 +sudo mkdir -p /opt/safesightd/{bin,config,data} sudo cp safesightd-linux-arm64 /opt/safesightd/bin/safesightd sudo chmod +x /opt/safesightd/bin/safesightd - -# 创建数据和配置目录 -sudo mkdir -p /opt/safesightd/config /opt/safesightd/data sudo cp safesightd.json.example /opt/safesightd/config/safesightd.json -sudo vi /opt/safesightd/config/safesightd.json # 修改 agent_token +sudo cp -r configs /opt/safesightd/ +sudo cp -r tools /opt/safesightd/ +sudo cp -r scripts /opt/safesightd/ # 安装服务 sudo cp safesightd.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now safesightd + +# 首次启动自动导入种子数据,无需手动操作 ``` -### 部署到独立 Linux 服务器(AMD64) - -```bash -# 同上,二进制改为 safesightd-linux-amd64 -sudo cp safesightd-linux-amd64 /opt/safesightd/bin/safesightd -``` - -其余步骤相同。 +AMD64 部署同上,二进制改为 `safesightd-linux-amd64`。 --- @@ -73,6 +71,24 @@ sudo cp safesightd-linux-amd64 /opt/safesightd/bin/safesightd --- +## 人脸库构建(离线环境) + +```bash +# 1. 离线安装 Python 依赖(仅首次) +sudo pip3 install /opt/safesightd/deps/*.whl + +# 2. 通过 Web UI 导入照片后,触发构建 +curl -X POST http://localhost:18080/face-gallery/build + +# 3. 如从外部传入已构建的 face_gallery.db: +sudo mkdir -p /opt/safesightd/resources/standard_resources/face_gallery +sudo cp face_gallery.db /opt/safesightd/resources/standard_resources/face_gallery/ +sudo python3 /opt/safesightd/scripts/register-face-gallery.py +sudo systemctl restart safesightd +``` + +--- + ## 验证 ```bash diff --git a/internal/service/standard_templates.go b/internal/service/standard_templates.go index e3ec935..36001ad 100644 --- a/internal/service/standard_templates.go +++ b/internal/service/standard_templates.go @@ -3,6 +3,7 @@ package service import ( "encoding/json" "fmt" + "log" "os" "path/filepath" "strings" @@ -10,6 +11,91 @@ import ( "safesight-control/internal/storage" ) +// ImportSeedAssets imports all config assets (templates, profiles, overlays) from a +// seed configs directory on first run. Returns total import count. +// The seed directory is expected to have subdirectories: templates/, profiles/, overlays/. +func ImportSeedAssets(repo *storage.AssetsRepo, seedDir string) (int, error) { + if repo == nil { + return 0, nil + } + seedDir = filepath.Clean(strings.TrimSpace(seedDir)) + if seedDir == "" { + return 0, nil + } + if _, err := os.Stat(seedDir); os.IsNotExist(err) { + return 0, nil + } + + // Check if DB already has assets (not first run). + existing, err := repo.ListTemplates() + if err != nil { + return 0, err + } + if len(existing) > 0 { + return 0, nil + } + + total := 0 + for _, kind := range []string{"templates", "profiles", "overlays"} { + dir := filepath.Join(seedDir, kind) + files, err := os.ReadDir(dir) + if err != nil { + if os.IsNotExist(err) { + continue + } + return total, err + } + for _, f := range files { + if f.IsDir() || !strings.HasSuffix(strings.ToLower(f.Name()), ".json") { + continue + } + path := filepath.Join(dir, f.Name()) + body, err := os.ReadFile(path) + if err != nil { + return total, fmt.Errorf("%s: %w", path, err) + } + var raw map[string]any + if err := json.Unmarshal(body, &raw); err != nil { + return total, fmt.Errorf("%s: %w", path, err) + } + if raw == nil { + raw = map[string]any{} + } + name := strings.TrimSpace(firstString(raw["name"], strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())))) + if name == "" { + log.Printf("skip %s: empty name", path) + continue + } + desc := stringValue(raw["description"]) + + switch kind { + case "templates": + if strings.TrimSpace(stringValue(raw["source"])) == "" { + raw["source"] = "standard" + } + b, _ := marshalConfigJSON(raw) + if err := repo.SaveTemplate(name, desc, string(b)); err != nil { + return total, fmt.Errorf("template %s: %w", name, err) + } + case "profiles": + tmplName := stringValue(raw["primary_template_name"]) + bizName := stringValue(raw["business_name"]) + b, _ := marshalConfigJSON(raw) + if err := repo.SaveProfile(name, tmplName, bizName, desc, string(b)); err != nil { + return total, fmt.Errorf("profile %s: %w", name, err) + } + case "overlays": + b, _ := marshalConfigJSON(raw) + if err := repo.SaveOverlay(name, desc, string(b)); err != nil { + return total, fmt.Errorf("overlay %s: %w", name, err) + } + } + total++ + } + } + return total, nil +} + func ImportStandardTemplatesFromDir(repo *storage.AssetsRepo, dir string) (int, error) { if repo == nil { return 0, fmt.Errorf("asset repository is not configured") diff --git a/internal/web/ui/templates/device.html b/internal/web/ui/templates/device.html index 9c17de5..63beac6 100644 --- a/internal/web/ui/templates/device.html +++ b/internal/web/ui/templates/device.html @@ -211,16 +211,22 @@
人脸库通过资源管理与基础配置维护
{{end}} + {{$fgConfig := false}} {{$fgSynced := true}} {{range $r := .StandardResources}}{{if eq $r.ResourceType "face_gallery"}} + {{$fgConfig = true}} {{$fgSynced = false}} {{range $d := $.DeviceResourceStatuses}}{{if and (eq $d.Name $r.Name) (eq $d.SHA256 $r.SHA256)}}{{$fgSynced = true}}{{end}}{{end}} {{end}}{{end}} + {{if $fgConfig}}
+ {{else}} +
人脸库尚未配置,请先在 资源管理 中添加 face_gallery 类型资源
+ {{end}}
diff --git a/internal/web/ui/templates/face_gallery.html b/internal/web/ui/templates/face_gallery.html index 7faa750..e40ccdf 100644 --- a/internal/web/ui/templates/face_gallery.html +++ b/internal/web/ui/templates/face_gallery.html @@ -16,6 +16,9 @@ +
+ +
{{if .FaceGalleryPersons}} diff --git a/scripts/deploy/register-face-gallery.py b/scripts/deploy/register-face-gallery.py new file mode 100644 index 0000000..6fcd4e2 --- /dev/null +++ b/scripts/deploy/register-face-gallery.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import sqlite3, hashlib, os + +db = '/opt/safesightd/data/app.db' +fg = '/opt/safesightd/resources/standard_resources/face_gallery/face_gallery.db' + +sha = hashlib.sha256(open(fg, 'rb').read()).hexdigest() +size = os.path.getsize(fg) + +conn = sqlite3.connect(db) +conn.execute( + "INSERT OR REPLACE INTO standard_resources(name,resource_type,version,sha256,size_bytes,description,file_path,created_at,updated_at) VALUES(?,?,?,?,?,?,?,datetime('now'),datetime('now'))", + ('face_gallery', 'face_gallery', 'v1', sha, size, '人脸库', 'resources/standard_resources/face_gallery/face_gallery.db'), +) +conn.commit() +conn.close() +print('OK sha256=' + sha + ' size=' + str(size)) diff --git a/scripts/package.sh b/scripts/package.sh index ffb6db7..b03c6b0 100644 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -1,53 +1,115 @@ #!/bin/bash # 管理端离线部署包构建脚本 -# 交叉编译 safesightd,打包配置和部署脚本 +# 交叉编译 safesightd,打包配置、种子数据、工具和部署脚本 set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +EDGE_DIR="$(cd "$PROJECT_DIR/../safesight-edge" 2>/dev/null && pwd || echo '')" PACKAGE_NAME="safesight-control-$(date +%Y%m%d-%H%M%S)" BUILD_DIR="/tmp/$PACKAGE_NAME" echo "========== 构建管理端离线部署包 ==========" +echo "项目目录: $PROJECT_DIR" +[ -n "$EDGE_DIR" ] && echo "Edge 目录: $EDGE_DIR" echo "" -# 编译 -echo "[1/2] 交叉编译..." +# ── 编译 ── +echo "[1/6] 交叉编译..." cd "$PROJECT_DIR" mkdir -p "$BUILD_DIR" CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o "$BUILD_DIR/safesightd-linux-arm64" ./cmd/safesightd/ 2>/dev/null && echo " ✓ ARM64" || echo " ✗ ARM64 failed" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o "$BUILD_DIR/safesightd-linux-amd64" ./cmd/safesightd/ 2>/dev/null && echo " ✓ AMD64" || echo " ✗ AMD64 failed" -# 打包 -echo "[2/2] 打包..." -cp scripts/deploy/safesightd "$BUILD_DIR/" +# ── 部署文件 ── +echo "[2/6] 部署文件..." +mkdir -p "$BUILD_DIR/scripts" cp scripts/deploy/safesightd.service "$BUILD_DIR/" +cp scripts/deploy/safesightd "$BUILD_DIR/" 2>/dev/null || true +cp scripts/deploy/import-assets.py "$BUILD_DIR/scripts/" 2>/dev/null || true +cp scripts/deploy/register-face-gallery.py "$BUILD_DIR/scripts/" 2>/dev/null || true cp safesightd.json.example "$BUILD_DIR/safesightd.json.example" 2>/dev/null || true +echo " ✓ 部署脚本已复制" +# ── 种子配置数据 ── +echo "[3/6] 种子配置数据..." +SEED_SRC="" +if [ -n "$EDGE_DIR" ] && [ -d "$EDGE_DIR/configs" ]; then + SEED_SRC="$EDGE_DIR/configs" +elif [ -d "$PROJECT_DIR/seed-configs" ]; then + SEED_SRC="$PROJECT_DIR/seed-configs" +fi +if [ -n "$SEED_SRC" ]; then + mkdir -p "$BUILD_DIR/configs" + cp -r "$SEED_SRC/templates" "$BUILD_DIR/configs/" 2>/dev/null || true + cp -r "$SEED_SRC/profiles" "$BUILD_DIR/configs/" 2>/dev/null || true + cp -r "$SEED_SRC/overlays" "$BUILD_DIR/configs/" 2>/dev/null || true + echo " ✓ 种子数据 ($SEED_SRC)" +else + echo " ⚠ 找不到 configs 目录,跳过" +fi + +# ── 人脸库构建工具 ── +echo "[4/6] 人脸库构建工具..." +if [ -d "$PROJECT_DIR/tools" ]; then + mkdir -p "$BUILD_DIR/tools" + cp -r "$PROJECT_DIR/tools/gallery_builder" "$BUILD_DIR/tools/" 2>/dev/null || true + cp "$PROJECT_DIR/tools/build_gallery.py" "$BUILD_DIR/tools/" 2>/dev/null || true + mkdir -p "$BUILD_DIR/tools/models" + cp "$PROJECT_DIR/tools/models"/*.onnx "$BUILD_DIR/tools/models/" 2>/dev/null || true + cp "$PROJECT_DIR/tools/models"/*.json "$BUILD_DIR/tools/models/" 2>/dev/null || true + echo " ✓ 构建工具已复制" +else + echo " ⚠ 找不到 tools 目录,跳过" +fi + +# ── 离线 Python 依赖 ── +echo "[5/6] 离线 Python 依赖..." +mkdir -p "$BUILD_DIR/deps" +# 尝试下载 onnxruntime ARM64 wheel(离线安装用) +if command -v pip3 &>/dev/null; then + pip3 download onnxruntime numpy --platform linux_aarch64 --platform manylinux2014_aarch64 --only-binary=:all: -d "$BUILD_DIR/deps/" 2>/dev/null && echo " ✓ ARM64 wheels" || echo " ⚠ pip download 失败,跳过" +else + echo " ⚠ pip3 不可用,跳过" +fi + +# ── README ── +echo "[6/6] 生成 README..." cat > "$BUILD_DIR/README.txt" << 'EOF' SafeSight Control 离线部署包 ============================= -ARM64 设备部署: +快速部署(ARM64): + sudo mkdir -p /opt/safesightd/{bin,config,data,scripts,tools} sudo cp safesightd-linux-arm64 /opt/safesightd/bin/safesightd sudo chmod +x /opt/safesightd/bin/safesightd - sudo mkdir -p /opt/safesightd/config /opt/safesightd/data sudo cp safesightd.json.example /opt/safesightd/config/safesightd.json - sudo vi /opt/safesightd/config/safesightd.json # 修改 agent_token + sudo cp -r configs /opt/safesightd/ + sudo cp -r tools /opt/safesightd/ + sudo cp -r scripts /opt/safesightd/ sudo cp safesightd.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now safesightd -AMD64 设备部署: - 同上,二进制改为 safesightd-linux-amd64 +初始化(首次部署必做): + sudo python3 /opt/safesightd/scripts/import-assets.py /opt/safesightd/configs + +安装人脸库构建依赖(离线): + sudo pip3 install deps/*.whl + +人脸库注册(如已有 face_gallery.db): + sudo cp face_gallery.db /opt/safesightd/resources/standard_resources/face_gallery/ + sudo python3 /opt/safesightd/scripts/register-face-gallery.py 打开 http://:18080 EOF +# ── 打包 ── cd /tmp tar -czf "$PROJECT_DIR/$PACKAGE_NAME.tar.gz" "$PACKAGE_NAME" rm -rf "$BUILD_DIR" echo "" -echo "✓ 打包完成: $PROJECT_DIR/$PACKAGE_NAME.tar.gz" +echo "========== 打包完成 ==========" +echo "$PROJECT_DIR/$PACKAGE_NAME.tar.gz" ls -lh "$PROJECT_DIR/$PACKAGE_NAME.tar.gz"