1 Commits

Author SHA1 Message Date
warren ca24794853 Initial commit 2026-03-25 14:51:25 +08:00
127 changed files with 3 additions and 39159 deletions
-40
View File
@@ -1,40 +0,0 @@
# Database Configuration
DATABASE_URL=postgres://accusys@localhost:5432/momentry
# Redis
# Format: redis://[username][:password]@host:port
# Users: default (with password), accusys (custom user with password)
REDIS_URL=redis://accusys:accusys@localhost:6379
# MongoDB
MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
MONGODB_DATABASE=momentry
# Qdrant Vector Database
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=Test3200Test3200Test3200
QDRANT_COLLECTION=chunks_v3
# Gitea
GITEA_URL=http://localhost:3000
# API Server (Production)
MOMENTRY_SERVER_PORT=3002
MOMENTRY_REDIS_PREFIX=momentry:
API_HOST=127.0.0.1
API_PORT=3002
# Worker Configuration (Production)
MOMENTRY_WORKER_ENABLED=true
MOMENTRY_MAX_CONCURRENT=2
MOMENTRY_POLL_INTERVAL=5
# Watch Directories (comma separated)
WATCH_DIRECTORIES=~/Videos,~/momentry_core_project/test_video
# Ollama (for Mistral 7B LLM)
OLLAMA_HOST=http://localhost:11434
# Model Paths
# EMBEDDING_MODEL_PATH=./models/comic-embed-text
# LLM_MODEL_PATH=./models/mistral-7b
-60
View File
@@ -1,60 +0,0 @@
# Development Environment Configuration
# Used by: momentry_playground binary
#
# This file is loaded BEFORE the main .env file
# Settings here override defaults but can be overridden by CLI flags
# Server Configuration
MOMENTRY_SERVER_PORT=3003
MOMENTRY_REDIS_PREFIX=momentry_dev:
# Worker Configuration (disabled by default for development)
MOMENTRY_WORKER_ENABLED=false
MOMENTRY_MAX_CONCURRENT=1
MOMENTRY_POLL_INTERVAL=10
MOMENTRY_WORKER_BATCH_SIZE=5
# Database (same as production, but could use separate dev database)
DATABASE_URL=postgres://accusys@localhost:5432/momentry
# MongoDB
MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
MONGODB_DATABASE=momentry
# Redis
REDIS_URL=redis://:accusys@localhost:6379
REDIS_PASSWORD=accusys
# Qdrant Vector Database (same as production)
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=Test3200Test3200Test3200
QDRANT_COLLECTION=chunks_v3
# Paths
MOMENTRY_OUTPUT_DIR=/Users/accusys/momentry/output_dev
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry_dev
# Python (for processing scripts)
MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11
MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core_0.1/scripts
# Logging
RUST_LOG=debug
MOMENTRY_LOG_LEVEL=debug
# Media
MOMENTRY_MEDIA_BASE_URL=https://wp.momentry.ddns.net
# Processor Timeouts
MOMENTRY_ASR_TIMEOUT=3600
MOMENTRY_CUT_TIMEOUT=3600
MOMENTRY_DEFAULT_TIMEOUT=7200
# Cache Settings
MONGODB_CACHE_ENABLED=true
MONGODB_CACHE_TTL_VIDEOS=300
MONGODB_CACHE_TTL_SEARCH=300
MONGODB_CACHE_TTL_HYBRID_SEARCH=600
MONGODB_CACHE_TTL_VIDEO_META=3600
REDIS_CACHE_TTL_HEALTH=30
REDIS_CACHE_TTL_VIDEO_META=3600
-70
View File
@@ -1,70 +0,0 @@
# Momentry Core Configuration Template
# Copy this file to .env and customize for your environment
# DO NOT commit .env with real credentials to version control
# ===========================================
# Database Configuration
# ===========================================
DATABASE_URL=postgres://user:password@localhost:5432/momentry
# ===========================================
# Redis Configuration
# ===========================================
REDIS_URL=redis://user:password@localhost:6379
REDIS_PASSWORD=your_redis_password
# ===========================================
# MongoDB Configuration
# ===========================================
MONGODB_URL=mongodb://user:password@localhost:27017/admin
MONGODB_DATABASE=momentry
# ===========================================
# Qdrant Configuration
# ===========================================
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=your_qdrant_api_key
QDRANT_COLLECTION=chunks_v3
# ===========================================
# API Server Configuration
# ===========================================
API_HOST=127.0.0.1
API_PORT=3000
# ===========================================
# Directory Paths
# ===========================================
MOMENTRY_OUTPUT_DIR=/path/to/output
MOMENTRY_BACKUP_DIR=/path/to/backup
MOMENTRY_SCRIPTS_DIR=/path/to/momentry_core/scripts
MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11
# ===========================================
# Processor Timeouts (seconds)
# ===========================================
MOMENTRY_ASR_TIMEOUT=3600
MOMENTRY_CUT_TIMEOUT=3600
MOMENTRY_DEFAULT_TIMEOUT=7200
# ===========================================
# Watch Directories (comma separated)
# ===========================================
WATCH_DIRECTORIES=~/Videos,~/Downloads
# ===========================================
# Logging
# ===========================================
RUST_LOG=info
# Options: trace, debug, info, warn, error
# ===========================================
# Ollama (for LLM integration)
# ===========================================
OLLAMA_HOST=http://localhost:11434
# ===========================================
# Model Paths
# ===========================================
# EMBEDDING_MODEL_PATH=./models/embedding
# LLM_MODEL_PATH=./models/llm
-40
View File
@@ -1,40 +0,0 @@
# Environment - Local configs (NEVER commit these)
.env
.env.local
.env.*.local
# Build artifacts
target/
venv/
# Generated files
thumbnails/
*.asr.json
*.probe.json
test_asr.json
# Local output (machine learning results)
output/
*.pt
# Cache
.ruff_cache/
# OS files
.DS_Store
.Spotlight-V100
.Trashes
# Logs
*.log
# SSH keys (NEVER commit)
id_*
!id_*.pub
# IDE and editor
.vscode/
.idea/
*.swp
*.swo
*~
-413
View File
@@ -1,413 +0,0 @@
# AGENTS.md - Momentry Core
Rust-based digital asset management system with video analysis and RAG capabilities.
## Build & Run Commands
```bash
# Build project
cargo build
cargo build --release
cargo build --bin momentry
cargo build --bin momentry_playground
# Build all binaries
cargo build --bins
# Run CLI
cargo run -- --help
cargo run -- register /path/to/video.mp4
cargo run -- server --host 0.0.0.0 --port 3002
# Run playground (development binary)
cargo run --bin momentry_playground -- server
cargo run --bin momentry_playground -- --help
```
## Binaries
| Binary | Purpose | Port | Redis Prefix | Environment |
|--------|---------|------|--------------|-------------|
| `momentry` | Production | 3002 | `momentry:` | `.env` |
| `momentry_playground` | Development | 3003 | `momentry_dev:` | `.env.development` |
| `momentry_player` | Video player | - | - | - |
## Testing
```bash
# Run all tests
cargo test
# Run single test by name
cargo test test_name
# Run with output
cargo test -- --nocapture
# Doc tests
cargo test --doc
```
## Linting & Formatting
```bash
# Format code (edition=2021, max_width=100, tab_spaces=4)
cargo fmt
cargo fmt -- --check
# Lint
cargo clippy
cargo clippy --all-features
# Check for errors
cargo check
cargo check --all-features
```
## Code Style
### General
- Use Rust 2021 edition
- Use tracing for logging (not println!)
- Keep lines under 100 characters
### Imports (order: std → external → local)
```rust
use std::path::Path;
use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::core::chunk::Chunk;
```
### Error Handling
- Use `anyhow::Result<T>` for application code
- Use `thiserror` for library code
- Use `.context()` for error context
- Use `anyhow::bail!()` for early returns
```rust
fn example() -> Result<SomeType> {
let output = Command::new("ffprobe")
.args([...])
.output()
.context("Failed to run ffprobe")?;
if !output.status.success() {
anyhow::bail!("Command failed");
}
Ok(result)
}
```
### Naming
- Types/Enums: PascalCase (`VideoRecord`, `ChunkType`)
- Functions/Variables: snake_case (`get_video_by_uuid`)
- Traits: PascalCase with -er suffix (`Database`, `ChunkStore`)
- Files: snake_case (`postgres_db.rs`)
### Types
- Use `serde::{Deserialize, Serialize}` for serializable types
- Use `#[serde(rename_all = "snake_case")]` for enum variants
- Use explicit numeric types (i64, u32, f64)
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoRecord {
pub id: i64,
pub uuid: String,
pub duration: f64,
pub width: u32,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ChunkType {
TimeBased,
Sentence,
Cut,
}
```
### Async Programming
- Use `tokio` runtime with full features
- Use `#[async_trait]` for async trait methods
```rust
#[async_trait]
pub trait Database: Send + Sync {
async fn init() -> Result<Self>
where Self: Sized;
}
```
## Code Structure
```
src/
├── main.rs # CLI entry point
├── lib.rs # Library exports
├── core/
│ ├── api_key/ # API key management (anomaly, blacklist, encryption, etc.)
│ ├── chunk/ # Chunking logic
│ ├── config.rs # Centralized configuration (env vars)
│ ├── db/ # Database (PostgreSQL, MongoDB, Redis, Qdrant)
│ ├── embedding/ # Vector embeddings
│ ├── overlay/ # Video overlay
│ ├── probe/ # ffprobe integration
│ ├── processor/ # ASR, OCR, YOLO, Face, Pose, CUT, ASRX
│ │ └── executor.rs # Unified Python script executor
│ ├── storage/ # File management
│ └── thumbnail/ # Thumbnail extraction
├── api/ # HTTP API (axum)
├── player/ # Video player
├── ui/ # TUI components
└── watcher/ # File system watcher
```
## Key Dependencies
- **Error handling**: `anyhow`, `thiserror`
- **Async**: `tokio` (full features), `async-trait`
- **CLI**: `clap` (derive)
- **Serialization**: `serde`, `serde_json`, `chrono`
- **Database**: `sqlx`, `mongodb`, `redis` (1.0), `qdrant-client`
- **HTTP**: `axum`, `tower`
- **Logging**: `tracing`, `tracing-subscriber`
- **Config**: `once_cell` (lazy static config)
## Environment Variables
### Server
- `MOMENTRY_SERVER_PORT` - API server port (default: `3002` for production, `3003` for playground)
- `MOMENTRY_REDIS_PREFIX` - Redis key prefix (default: `momentry:` for production, `momentry_dev:` for playground)
### Database
- `DATABASE_URL` - PostgreSQL (default: `postgres://accusys@localhost:5432/momentry`)
### Redis
- `REDIS_URL` - Redis URL (default: `redis://:accusys@localhost:6379`)
- `REDIS_PASSWORD` - Redis password (default: `accusys`)
### Paths
- `MOMENTRY_OUTPUT_DIR` - Output directory (default: `/Users/accusys/momentry/output`)
- `MOMENTRY_BACKUP_DIR` - Backup directory
- `MOMENTRY_PYTHON_PATH` - Python path (default: `/opt/homebrew/bin/python3.11`)
- `MOMENTRY_SCRIPTS_DIR` - Scripts directory
### Processor Timeouts
- `MOMENTRY_ASR_TIMEOUT` - ASR timeout in seconds (default: 3600)
- `MOMENTRY_CUT_TIMEOUT` - CUT timeout in seconds (default: 3600)
- `MOMENTRY_DEFAULT_TIMEOUT` - Default timeout (default: 7200)
### Logging
- `RUST_LOG` or `MOMENTRY_LOG_LEVEL` - Log level (default: `info`)
## Notes
- Unit tests exist (86 library tests)
- Video processing uses external tools (ffprobe, Python scripts)
- Multi-database architecture (PostgreSQL, MongoDB, Redis, Qdrant)
- Monitor directory is a separate system (not Rust)
- PythonExecutor provides unified script execution with timeout support
- Redis 1.0.x for improved performance
## Task Management
### 使用 todowrite 追蹤任務
```bash
# 創建任務清單
/todo 建立配置模組 [in_progress]
/todo 添加單元測試 [pending]
# 更新狀態
/todo 完成標記 [completed]
```
### 任務批次建議
- 一次處理 1-2 個功能
- 每個功能完成後驗證 (clippy + test)
- 驗證通過後再繼續下一個
## Code Review Checklist
完成任務後檢查:
- [ ] `cargo clippy --lib` 通過
- [ ] `cargo test --lib` 通過
- [ ] `cargo fmt -- --check` 通過
- [ ] 文檔已更新 (如需要)
- [ ] 新功能有單元測試
## Commit Guidelines
```bash
# feat: 新功能
git commit -m "feat: add monitor_jobs table"
# fix: 錯誤修復
git commit -m "fix: resolve SQL injection in store_vector"
# refactor: 重構
git commit -m "refactor: use parameterized queries"
# docs: 文檔更新
git commit -m "docs: update AGENTS.md with new modules"
```
## Pre-commit Hook
專案已配置 `.git/hooks/pre-commit`,提交前自動檢查:
```bash
# 檢查內容
1. cargo fmt --check # Rust 格式化檢查
2. cargo clippy --lib # Rust Lint 檢查
3. cargo test --lib # Rust 單元測試
4. ruff check # Python Lint 檢查
5. ruff format --check # Python 格式化檢查
6. markdownlint # Markdown 格式檢查
7. shellcheck # Shell 腳本檢查
# 跳過檢查(不建議)
git commit --no-verify
# 跳過特定檢查
git commit --skip-checks
```
**注意**: Hook 僅檢查已暫存的 Rust/Python/Markdown 文件。
### Python 環境設置
```bash
# 安裝 ruff
pip install ruff==0.11.2
# 格式化 Python 文件
ruff format scripts/
# Lint Python 文件
ruff check scripts/
```
### Markdown 環境設置
```bash
# 安裝 markdownlint-cli (使用系統 Node.js)
npm install -g markdownlint-cli
# 檢查 Markdown 文件
markdownlint docs/
# 配置檔案
.markdownlint.json
```
### Shell 環境設置
```bash
# 安裝 shellcheck
brew install shellcheck
# 檢查 Shell 腳本
shellcheck scripts/*.sh monitor/**/*.sh
```
**注意**: Hook 只檢查 error 等級的 shellcheck 問題,style 警告會顯示但不阻擋提交。
## Reference Documents
| 文件 | 用途 |
|------|------|
| `docs/OPENCODE_GUIDE.md` | OpenCode 使用規範 |
| `docs/ARCHITECTURE_EVALUATION.md` | 架構優化待評估項目 (含 GraphRAG) |
| `docs/PENDING_ISSUES.md` | 待解決問題追蹤 |
| `docs/MOMENTRY_CORE_MONITORING.md` | 監控系統規範 |
| `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 |
| `docs/PYTHON.md` | Python 腳本規範 |
| `docs/FILE_CHANGE_MANAGEMENT.md` | 文件修改管理規範 |
| `docs/YOLO_RESUME_INTEGRATION.md` | YOLO Resume 功能整合記錄 |
| `docs/DOCUMENT_EMBEDDING_STRATEGY.md` | Parent-Child 嵌入策略 |
| `docs/PROCESSING_PIPELINE.md` | 處理流程文檔 |
| `docs/N8N_DEMO_WORKFLOW.md` | n8n 工作流文檔 |
| `docs/FRESH_MAC_INSTALLATION.md` | 全新 Mac 安裝指南 |
| `docs/SERVICES.md` | 服務總覽與管理 |
| `docs/SFTPGO_DEMO_USER.md` | SFTPGo 用戶指南 |
## Document Change Workflow
修改文件前請參考 `docs/FILE_CHANGE_MANAGEMENT.md`,確保:
1. **修改前**:完整閱讀文件、執行預檢清單
2. **修改中**:提供變更計畫、取得確認
3. **修改後**:展示 diff、更新版本歷史
4. **驗證**:執行 lint/test、提交前審查
### AI 工具修改規範
AI 工具修改文件時:
- 必須先完整閱讀文件(不可只讀取部分章節)
- 修改前先提出變更計畫供確認
- 修改後展示 diff 內容
- 更新版本歷史表
## PHP Development
WordPress 作為 Momentry Portal,負責 n8n 自動化與 sftpgo 檔案服務的頁面整合。
### 編輯器設定
| 編輯器 | LSP 方案 | 安裝方式 |
|--------|----------|----------|
| VS Code | Intelephense | Extension Marketplace (推薦) |
| Cursor | Intelephense | Extension Marketplace (推薦) |
| CLI | phpactor | `~/bin/phpactor` |
### Intelephense (VS Code/Cursor)
1. 安裝 Extension: 搜尋 "Intelephense"
2. 設定:
```json
{
"intelephense.stubs": ["wordpress"]
}
```
### phpactor (CLI)
```bash
# 安裝方式
brew install composer
curl -sSL https://github.com/phpactor/phpactor/releases/latest/download/phpactor.phar -o ~/bin/phpactor
chmod +x ~/bin/phpactor
# 安裝 WordPress Stubs
cd /Users/accusys/wordpress/web
composer require --dev php-stubs/wordpress-stubs
# 建立 WordPress 索引
cd /Users/accusys/wordpress/web
~/bin/phpactor index:build --reset
# 常用指令
~/bin/phpactor class:search "WP_User" # 搜尋類別
~/bin/phpactor index:query WP_User # 查看類別資訊
~/bin/phpactor navigate /path/to/file.php # 導航到定義
```
### WordPress 程式碼位置
| 類型 | 路徑 |
|------|------|
| 主題 | `/Users/accusys/wordpress/web/wp-content/themes/` |
| 插件 | `/Users/accusys/wordpress/web/wp-content/plugins/` |
### 與 marcom 團隊協作
| 角色 | 負責 |
|------|------|
| marcom 團隊 | Figma 設計 / Elementor 建構 |
| OpenCode | 程式碼實作 / 重構 |
### 開發時程
```
Phase 1: marcom 建構 (現在) → Elementor 頁面建構
Phase 2: 交付審視 (TBD) → 功能確認 / 重構評估
Phase 3: OpenCode 重構 → 純程式碼實作,交付無 Elementor 依賴版本
```
Generated
-5283
View File
File diff suppressed because it is too large Load Diff
-96
View File
@@ -1,96 +0,0 @@
[package]
name = "momentry_core"
version = "0.1.0"
edition = "2021"
authors = ["Momentry Team"]
description = "Digital asset management system with video analysis and RAG"
[dependencies]
# Core
anyhow = "1.0"
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
once_cell = "1.19"
dotenv = "0.15"
# CLI
clap = { version = "4", features = ["derive"] }
# Async
async-trait = "0.1"
futures-util = "0.3"
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
# UUID
sha2 = "0.10"
hex = "0.4"
uuid = { version = "1.0", features = ["v4"] }
# Security
subtle = "2.5"
aes-gcm = "0.10"
base64 = "0.22"
# Cache
moka = { version = "0.12", features = ["future"] }
# Database
redis = { version = "1.0", features = ["tokio-comp", "connection-manager"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "json", "chrono"] }
mongodb = { version = "2", features = ["tokio-runtime"] }
bson = { version = "2", features = ["chrono-0_4"] }
qdrant-client = "1.7"
reqwest = { version = "0.12", features = ["json"] }
# HTTP Server
axum = "0.7"
tower = "0.4"
# API Documentation
utoipa = { version = "4", features = ["axum_extras", "chrono", "uuid"] }
utoipa-swagger-ui = { version = "7", features = ["axum"] }
# File watching
notify = "6"
# Logging
env_logger = "0.11"
# Hash
md5 = "0.7"
# TUI
ratatui = "0.28"
crossterm = "0.28"
# Terminal
atty = "0.2"
# System
libc = "0.2"
[lib]
name = "momentry_core"
path = "src/lib.rs"
[features]
default = []
player = []
[[bin]]
name = "momentry"
path = "src/main.rs"
[[bin]]
name = "momentry_player"
path = "src/player/main.rs"
[[bin]]
name = "momentry_playground"
path = "src/playground.rs"
-443
View File
@@ -1,443 +0,0 @@
# Momentry 系統整合指南 v0.1
## 一、專案概述
Momentry 是一個數位資產管理系統,包含服務遷移、監控系統、Storage 架構和用戶管理。
**設計目標**
- 自動化優先:人類只需處理通知和判斷
- 分層監控:七層監控架構
- 數據納管:檔案由資料庫統一管理
---
## 二、系統架構
### 硬體
- **本地 NVMe**:熱數據 (/Users/accusys/momentry/)
- **外部 RAID**:溫數據 (/Volumes/RAID System/)
- **Object Storage**:冷數據歸檔
### 網路
- **DDNS**momentry.ddns.net
- **VPN**Shadowsocks (port 8388)
- **反向代理**Caddy
### 軟體服務 (15 個)
| 服務 | Port | 用途 | 狀態 |
|------|------|------|------|
| PostgreSQL | 5432 | 主資料庫 | ✅ 運行 |
| Redis | 6379 | 快取/會話 | ✅ 運行 |
| n8n Main | 5678 | 工作流 | ✅ 運行 |
| n8n Worker | 5690 | 工作流 | ✅ 運行 |
| Caddy | 443/2019 | 反向代理 | ✅ 運行 |
| Gitea | 3000 | Git 服務 | ✅ 運行 |
| SFTPGo | 2222 | SFTP | ✅ 運行 |
| Ollama | 11434 | LLM | ✅ 運行 |
| Qdrant | 6333 | 向量資料庫 | ✅ 運行 |
| MariaDB | 3306 | WordPress | ✅ 運行 |
| PHP-FPM | - | Web 後端 | ✅ 運行 |
| RustDesk hbbs | 21116 | 遠程桌面 | ✅ 運行 |
| RustDesk hbbr | 21117 | 遠程桌面 | ✅ 運行 |
| MongoDB | 27017 | 文檔資料庫 | ✅ 運行 |
| Agent | - | 代理服務 | ✅ 運行 |
---
## 三、監控系統 (七層架構)
```
Layer 1: External 監控 - DDNS、網關、互聯網
Layer 2: Service 監控 - 15 個服務健康檢查
Layer 3: Workflow 監控 - n8n Workflow 狀態/閒置分析
Layer 4: Portal 監控 - WordPress 頁面/帳號
Layer 5: Database 監控 - PostgreSQL/Redis/Qdrant/MariaDB
Layer 6: 使用者監控 - 連線/本機/異常檢測
Layer 7: Storage 架構 - 冷溫熱/歸檔/檔案註冊
```
### 3.1 Layer 1: External 監控
**監控項目**
- DDNS 域名解析 (momentry.ddns.net)
- 網關連通性 (192.168.110.1)
- 互聯網連接 (8.8.8.8)
**腳本**`monitor/service/external_monitor.sh`
### 3.2 Layer 2: Service 監控
**監控項目**
- 進程狀態
- 端口響應
- HTTP 狀態碼
**腳本**`monitor/service/health_check.sh`
### 3.3 Layer 3: n8n Workflow 監控
**監控項目**
- Workflow 數量與啟用狀態
- 執行次數與結果
- 閒置識別
**閒置定義**
```
閒置 = 無排程 AND 無 API 觸發 AND 超過 30 天未執行
```
**改善建議**
- 建議停用:閒置 > 30 天
- 建議刪除:閒置 > 90 天
**腳本**`monitor/workflow/n8n_workflow_monitor.sh`
### 3.4 Layer 4: WordPress Portal 監控
**變動監控**
- 版本變更 (Core/Theme/Plugin)
- 文件變更 (wp-content)
- 配置變更 (wp-config.php)
- 內容變更 (文章/頁面)
- 代碼變更
- 帳號創建/刪除/修改
**使用監控**
- 帳號登入/失敗
- 內容操作 (發布/編輯/刪除)
- 權限變更
- 評論/媒體操作
**腳本**`monitor/portal/page_monitor.sh`
### 3.5 Layer 5: Database 監控
**PostgreSQL**
- 表數量/行數/大小
- 死元組
- 慢查詢
**Redis**
- 連線數
- 內存使用
- 命中率
- 客戶端列表
**Qdrant**
- Collection 列表
- Points 數
- 向量維度
- 磁盤使用
**MariaDB**
- 連線數
- WordPress 表
**腳本**
- `monitor/database/postgres_monitor.sh`
- `monitor/database/redis_monitor.sh`
- `monitor/database/qdrant_monitor.sh`
### 3.6 Layer 6: 使用者監控
**連線使用者**
- SSH 登入與命令
- Web 服務 (n8n/Gitea/WP)
- 資料庫連線
- SFTP 傳輸
**本機使用者**
- 系統登入
- sudo 使用
- 服務帳戶
- 異常檢測
**異常檢測**
- 暴力破解 (> 5次/分鐘)
- 權限提升
- 異常時間登入
**腳本**`monitor/users/session_tracker.sh`
### 3.7 Layer 7: Storage 架構
**目錄結構**
```
/Users/accusys/momentry/
├── var/ # 服務數據 (熱)
├── etc/ # 配置 (溫)
├── log/ # 日誌 (溫)
├── data/ # 用戶數據
│ ├── family/ # 家庭集群
│ ├── work/ # 工作集群
│ ├── wordpress/ # WP 隔離
│ └── shared/ # 共享
├── backup/ # 備份
│ ├── daily/
│ ├── weekly/
│ ├── monthly/
│ └── archive/
└── tmp/ # 臨時
```
**分層標準**
| 等級 | 條件 | 存放 |
|------|------|------|
| 熱 | 7天內訪問 > 10次 | NVMe |
| 溫 | 30天內訪問 > 1次 | RAID |
| 冷 | 90天未訪問 | Object |
**歸檔策略**
- 手動歸檔
- 自動歸檔 (90天)
- 深度歸檔 (1年)
**腳本**`monitor/storage/storage_manager.sh`
---
## 四、數據表設計
### 監控相關
```sql
monitor_services -- 服務健康狀態
monitor_workflows -- n8n Workflow
monitor_databases -- 資料庫指標
monitor_portal_pages -- WP 頁面
monitor_portal_users -- WP 用戶
monitor_sessions -- 使用者會話
monitor_logins -- 登入歷史
monitor_sudo_history -- sudo 記錄
monitor_resource_usage -- 資源使用
monitor_anomalies -- 異常檢測
monitor_external -- 外部監控
```
### Storage 相關
```sql
file_registry -- 檔案註冊
storage_usage_stats -- 存儲統計
storage_access_logs -- 訪問日誌
file_lifecycle -- 生命週期
```
---
## 八、實現文件結構
```
momentry_core_0.1/
├── MOMENTRY_INTEGRATION_GUIDE.md # 本文件
├── monitor/
│ ├── MONITORING.md # 監控詳細文檔
│ ├── config/
│ │ └── monitor_config.yaml # 配置文件
│ ├── control/
│ │ └── monitor_control.sh # 控制腳本
│ ├── service/
│ │ ├── health_check.sh # 服務健康
│ │ └── external_monitor.sh # 外部監控
│ ├── workflow/
│ │ └── n8n_workflow_monitor.sh
│ ├── portal/
│ │ └── page_monitor.sh
│ ├── database/
│ │ ├── schema.sql
│ │ ├── postgres_monitor.sh
│ │ ├── redis_monitor.sh
│ │ └── qdrant_monitor.sh
│ ├── users/
│ │ └── session_tracker.sh
│ └── storage/
│ └── storage_manager.sh
├── momentry_runtime/
│ └── plist/
│ ├── template.service.plist # 用戶級模板
│ ├── template.root.plist # 系統級模板
│ └── *.plist # 服務配置文件
└── docs/
├── SERVICES.md # 服務安裝
└── SERVICE_ADDITION_GUIDE.md # 服務添加規範
```
---
## 六、快速使用
### 初始化
```bash
# 初始化監控資料庫
psql -U accusys -h localhost -d momentry -f monitor/database/schema.sql
# 初始化 Storage 目錄
./monitor/storage/storage_manager.sh init
```
### 監控命令
```bash
cd /Users/accusys/momentry_core_0.1/monitor
# 查看狀態
./control/monitor_control.sh status
# 執行檢查
./control/monitor_control.sh check all # 全部
./control/monitor_control.sh check service # 服務
./control/monitor_control.sh check workflow # Workflow
./control/monitor_control.sh check database # 資料庫
./control/monitor_control.sh check users # 使用者
./control/monitor_control.sh check storage # Storage
# 持續監控
./control/monitor_control.sh monitor
```
---
## 七、優先級
| 優先級 | 項目 |
|--------|------|
| P0 | Service 基礎監控 |
| P0 | Database 監控 |
| P1 | 使用者監控 |
| P1 | n8n Workflow 監控 |
| P2 | WordPress Portal 監控 |
| P2 | External 監控 |
| P3 | Storage 架構 |
---
## 九、服務添加規範
### 9.1 概述
添加新服務到 Momentry 系統的標準流程。
**重要原則**
- 使用 `launchctl` 管理服務,勿使用 `brew services`
- 所有服務使用 `com.momentry.*` 作為 plist Label
- 數據存放於 `/Users/accusys/momentry/` 目錄
- 每個服務需提供監控腳本
### 9.2 命名規範
**Plist 文件命名**
```
com.momentry.{service_name}.plist
```
**目錄結構**
```
/Users/accusys/momentry/
├── var/{service_name}/ # 服務數據
├── etc/{service_name}/ # 服務配置
└── log/{service_name}.log # 服務日誌
```
### 9.3 Plist 模板
**用戶級服務模板** (`plist/template.service.plist`)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.{service_name}</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/{service_name}</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/executable</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/{service_name}.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/{service_name}.error.log</string>
</dict>
</plist>
```
### 9.4 添加步驟
**步驟 1:創建目錄**
```bash
mkdir -p /Users/accusys/momentry/var/{service_name}
mkdir -p /Users/accusys/momentry/etc/{service_name}
```
**步驟 2:創建 Plist 文件**
```bash
cp plist/template.service.plist plist/com.momentry.{service_name}.plist
# 編輯 plist 文件
```
**步驟 3:複製到系統**
```bash
# 系統級服務
sudo cp plist/com.momentry.{service}.plist /Library/LaunchDaemons/
# 用戶級服務
cp plist/com.momentry.{service}.plist ~/Library/LaunchAgents/
```
**步驟 4:載入服務**
```bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
```
**步驟 5:添加監控**
`monitor/config/monitor_config.yaml` 中添加服務配置。
### 9.5 服務管理命令
```bash
# 啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 停止
sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
# 查看狀態
launchctl list | grep momentry
# 查看日誌
tail -f /Users/accusys/momentry/log/{service}.log
```
### 9.6 服務分類
| 類型 | Plist 位置 | 示例 |
|------|------------|------|
| 系統級 | `/Library/LaunchDaemons/` | PostgreSQL, Caddy |
| 用戶級 | `~/Library/LaunchAgents/` | Redis, n8n, Ollama |
### 9.7 模板文件
| 文件 | 說明 |
|------|------|
| `plist/template.service.plist` | 用戶級服務模板 |
| `plist/template.root.plist` | 系統級服務模板 |
| `docs/SERVICE_ADDITION_GUIDE.md` | 完整規範文檔 |
---
## 十、版本歷史
| 版本 | 日期 | 內容 |
|------|------|------|
| 0.1 | 2026-03-15 | 初始版本:七層監控架構、15 個服務、Storage 設計 |
| 0.2 | 2026-03-15 | 新增服務添加規範 (第九章)、模板文件 |
+3
View File
@@ -0,0 +1,3 @@
# momentry_core
Digital asset management system with video analysis and RAG - Production version with API Key authentication
-105
View File
@@ -1,105 +0,0 @@
# Momentry Core 配置管理
## 目錄結構
```
momentry_core_0.1/
├── .env.example # 配置模板(已納入版本控制)
├── .env # 本地配置(已從版本控制排除)
├── .env.local # 本地覆蓋配置(已從版本控制排除)
├── config/
│ └── README.md # 本文件
└── src/core/config.rs # 配置代碼
```
## 配置加載順序
1. `.env` - 默認本地配置
2. `.env.local` - 本地覆蓋(最高優先級)
## 環境變數列表
### 數據庫配置
| 變數 | 說明 | 默認值 |
|------|------|--------|
| `DATABASE_URL` | PostgreSQL 連接字串 | `postgres://accusys@localhost:5432/momentry` |
### Redis 配置
| 變數 | 說明 | 默認值 |
|------|------|--------|
| `REDIS_URL` | Redis 連接字串 | `redis://:accusys@localhost:6379` |
| `REDIS_PASSWORD` | Redis 密碼 | `accusys` |
### 存儲路徑
| 變數 | 說明 | 默認值 |
|------|------|--------|
| `MOMENTRY_OUTPUT_DIR` | 輸出目錄 | `/Users/accusys/momentry/output` |
| `MOMENTRY_BACKUP_DIR` | 備份目錄 | `/Users/accusys/momentry/backup/momentry` |
| `MOMENTRY_SCRIPTS_DIR` | 腳本目錄 | `/Users/accusys/momentry_core_0.1/scripts` |
| `MOMENTRY_PYTHON_PATH` | Python 路徑 | `/opt/homebrew/bin/python3.11` |
### 處理器超時(秒)
| 變數 | 說明 | 默認值 |
|------|------|--------|
| `MOMENTRY_ASR_TIMEOUT` | ASR 處理超時 | `3600` |
| `MOMENTRY_CUT_TIMEOUT` | CUT 處理超時 | `3600` |
| `MOMENTRY_DEFAULT_TIMEOUT` | 默認超時 | `7200` |
### 日誌
| 變數 | 說明 | 默認值 |
|------|------|--------|
| `RUST_LOG` | 日誌級別 | `info` |
| `MOMENTRY_LOG_LEVEL` | 日誌級別(備選) | `info` |
## 使用方式
### 1. 首次設置
```bash
# 複製模板
cp .env.example .env
# 編輯配置
nano .env
```
### 2. 本地覆蓋
創建 `.env.local` 設置僅本地適用的配置:
```bash
# .env.local 示例
DATABASE_URL=postgres://local:password@localhost:5432/momentry_dev
MOMENTRY_LOG_LEVEL=debug
```
### 3. 運行應用
```bash
# 加載配置並運行
source .env && cargo run
# 或使用 direnv
direnv allow
```
## 版本控制策略
| 文件 | 版本控制 | 說明 |
|------|---------|------|
| `.env.example` | ✅ 追蹤 | 模板,包含所有選項 |
| `.env` | ❌ 忽略 | 本地敏感配置 |
| `.env.local` | ❌ 忽略 | 本地覆蓋配置 |
## 部署檢查清單
- [ ] 複製 `.env.example``.env`
- [ ] 設置數據庫連接
- [ ] 設置 Redis 密碼
- [ ] 配置目錄路徑
- [ ] 確認日誌級別
-217
View File
@@ -1,217 +0,0 @@
# Momentry Core API 端點總覽
| 項目 | 內容 |
|------|------|
| 版本 | V1.1 |
| 日期 | 2026-03-25 |
---
## Base URL
| 環境 | URL |
|------|-----|
| 本地 | `http://localhost:3002` |
| 外部 | `https://api.momentry.ddns.net` |
---
## 端點列表
### 健康檢查
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/health` | 基本健康檢查 |
| GET | `/health/detailed` | 詳細健康檢查(含服務狀態) |
**範例**:
```bash
curl http://localhost:3002/health
# {"status":"ok","version":"0.1.0","uptime_ms":123456}
```
---
### 影片搜尋
| 方法 | 端點 | 說明 |
|------|------|------|
| POST | `/api/v1/search` | 語意搜尋(標準格式) |
| POST | `/api/v1/n8n/search` | 語意搜尋(n8n 專用格式) |
| POST | `/api/v1/search/hybrid` | 混合搜尋 |
**標準搜尋** (`/api/v1/search`):
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "test", "limit": 10}'
```
**n8n 格式搜尋** (`/api/v1/n8n/search`):
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query": "test", "limit": 10}'
```
---
### 影片管理
| 方法 | 端點 | 說明 |
|------|------|------|
| POST | `/api/v1/register` | 註冊影片 |
| POST | `/api/v1/probe` | 探測影片資訊(不註冊) |
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片資訊 |
| GET | `/api/v1/progress/:uuid` | 取得處理進度 |
**註冊影片**:
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/video.mp4"}'
```
**探測影片** (不註冊,只取得影片資訊):
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-d '{"path": "./demo/video.mp4"}'
```
**Probe 回應範例**:
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0,
"cached": false,
"format": {
"filename": "/path/to/video.mp4",
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "120.5",
"size": "12345678",
"bit_rate": "819200"
},
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_type": "video",
"width": 1920,
"height": 1080,
"r_frame_rate": "30/1",
"duration": "120.5"
}
]
}
```
**列出影片**:
```bash
curl http://localhost:3002/api/v1/videos
```
**查詢影片**:
```bash
curl "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
```
**處理進度**:
```bash
curl http://localhost:3002/api/v1/progress/5dea6618a606e7c7
```
---
## 端點對照表
| 功能 | n8n 使用 | WordPress 使用 | curl 測試 |
|------|-----------|----------------|------------|
| 健康檢查 | ✓ | ✓ | ✓ |
| 語意搜尋 | ✓ (n8n格式) | ✓ (標準格式) | ✓ |
| 影片探測 | ✓ | ✓ | ✓ |
| 註冊影片 | ✓ | ✓ | ✓ |
| 列出影片 | ✓ | ✓ | ✓ |
| 查詢影片 | ✓ | ✓ | ✓ |
| 處理進度 | ✓ | ✓ | ✓ |
---
## 回應格式
### n8n 格式 (`/api/v1/n8n/search`)
```json
{
"query": "charade",
"count": 10,
"hits": [
{
"id": "sentence_0001",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0001",
"text": "...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/video.mp4"
}
]
}
```
### 標準格式 (`/api/v1/search`)
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "...",
"score": 0.92
}
],
"query": "charade"
}
```
---
## HTTP 狀態碼
| 狀態 | 說明 |
|------|------|
| 200 | 成功 |
| 400 | 請求格式錯誤 |
| 404 | 端點或資源不存在 |
| 500 | 伺服器內部錯誤 |
| 502 | API 服務未啟動 |
---
## 錯誤處理
### 502 Bad Gateway
**原因**: Momentry API 服務未啟動
**解決**:
```bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點)
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 使用範例
-450
View File
@@ -1,450 +0,0 @@
# Momentry 備份版本管理規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren / OpenCode |
| 建立時間 | 2026-03-25 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-25 | 建立備份版本管理規範 | OpenCode |
---
## 1. 概述
本文檔定義 Momentry 系統的備份版本管理規範,確保新舊架構之間的回滾相容性。
### 1.1 版本定義
| 版本 | 日期 | 說明 |
|------|------|------|
| v1 | 2026-03-18 | 初始備份架構(不包含新架構組件)|
| v2 | 2026-03-25 | 新架構備份(包含 monitor_jobs, processor_results, Output 目錄)|
### 1.2 備份版本格式
| 版本 | 檔案命名格式 |
|------|-------------|
| v1 | `{service}_{type}_{YYYYMMDD}_{HHMMSS}.{ext}` |
| v2 | `{service}_{type}_v2_{YYYYMMDD}_{HHMMSS}.{ext}` |
### 1.3 各版本涵蓋範圍
| 組件 | v1 | v2 |
|------|-----|-----|
| PostgreSQL (videos, chunks) | ✅ | ✅ |
| PostgreSQL (monitor_jobs) | ❌ | ✅ |
| PostgreSQL (processor_results) | ❌ | ✅ |
| Redis | ✅ | ✅ |
| MongoDB Cache | ⚠️ | ⚠️ |
| Output (probe.json) | ❌ | ✅ |
> ⚠️ MongoDB 備份目前存在路徑問題,正在修復中
---
## 2. 備份版本識別
### 2.1 檔名識別
```bash
# 識別版本
detect_version() {
local backup_file=$1
if echo "$backup_file" | grep -q "_v2_"; then
echo "v2"
else
echo "v1"
fi
}
# 使用範例
detect_version "postgresql_db_momentry_v2_20260325_030000.sql.gz"
# 輸出: v2
detect_version "postgresql_db_momentry_20260324_030000.sql.gz"
# 輸出: v1
```
### 2.2 內容識別
```bash
# 檢查是否為 v2 備份
is_v2_backup() {
local backup_file=$1
gzip -dc "$backup_file" 2>/dev/null | grep -q "monitor_jobs" && echo "yes" || echo "no"
}
# 檢查是否包含 processor_results
has_processor_results() {
local backup_file=$1
gzip -dc "$backup_file" 2>/dev/null | grep -q "processor_results" && echo "yes" || echo "no"
}
```
### 2.3 檔案大小比較
| 版本 | PostgreSQL 備份大小 | 說明 |
|------|---------------------|------|
| v1 | ~18-19 MB | 基本資料表 |
| v2 | >19 MB | 包含新表格和索引 |
---
## 3. 回滾策略
### 3.1 回滾流程圖
```
┌─────────────────────────────────────────┐
│ 選擇還原目標 │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 選擇備份版本 │
│ ┌───────────┐ ┌───────────┐ │
│ │ v1 備份 │ │ v2 備份 │ │
│ └───────────┘ └───────────┘ │
└─────────────────────────────────────────┘
┌─────────┴─────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ v1 回滾 │ │ v2 回滾 │
└──────────┘ └──────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ 基本資料庫 │ │ 完整還原 │
└──────────┘ └──────────┘
```
### 3.2 回滾矩陣
| 還原目標 | v1 備份 | v2 備份 |
|----------|---------|---------|
| 基本資料庫 | ✅ | ✅ |
| + monitor_jobs | ❌ | ✅ |
| + processor_results | ❌ | ✅ |
| + Output 檔案 | ❌ | ✅ |
| + MongoDB Cache | ⚠️ | ⚠️ |
### 3.3 回滾相容性說明
#### v1 → v2(支援)
- v1 備份可以還原到 v2 架構
- 新架構組件會從空白狀態開始
- 不會造成資料損壞
#### v2 → v1(⚠️ 警告)
```
⚠️ v2 回滾到 v1 可能導致資料丟失
影響範圍:
- monitor_jobs 資料會消失
- processor_results 資料會消失
- Output 檔案參照可能失效
建議:
1. 在還原前建立 v2 快照
2. 或使用隔離還原(staging restore
```
---
## 4. 還原腳本保護機制
### 4.1 還原前檢查
```bash
# 還原前檢查版本相容性
pre_restore_check() {
local backup_file=$1
local version=$(detect_version "$backup_file")
local current_db_version=$(check_current_db_version)
echo "備份版本: $version"
echo "目前版本: $current_db_version"
# v2 → v1: 警告但允許(使用者需確認)
if [ "$version" = "v1" ] && [ "$current_db_version" = "v2" ]; then
echo "⚠️ 警告:即將回滾到 v1"
echo "影響:monitor_jobs 和 processor_results 資料將被清除"
read -p "確認繼續?(y/N): " confirm
[ "$confirm" != "y" ] && exit 1
fi
# v1 → v2: 直接允許
if [ "$version" = "v1" ] && [ "$current_db_version" = "v2" ]; then
echo "ℹ️ 提示:新架構組件將重新初始化"
fi
# v2 → v2: 直接允許
# v1 → v1: 直接允許
}
```
### 4.2 隔離還原(Staging Restore
```bash
# 只還原到暫存資料庫,不影響生產
restore_to_staging() {
local backup_file=$1
local version=$(detect_version "$backup_file")
echo "執行隔離還原..."
echo "版本: $version"
# 建立暫存資料庫
PGPASSWORD="$PG_PASSWORD" psql -U "$PG_USER" -d postgres << EOF
DROP DATABASE IF EXISTS momentry_staging;
CREATE DATABASE momentry_staging;
EOF
# 還原到暫存資料庫
PGPASSWORD="$PG_PASSWORD" pg_restore -U "$PG_USER" -d "momentry_staging" \
--no-owner --no-acl "$backup_file"
echo "✅ 還原完成:momentry_staging"
echo "驗證命令:psql -U accusys -d momentry_staging -c '\\dt'"
}
```
### 4.3 版本驗證命令
```bash
# 識別所有備份版本
ls /Users/accusys/momentry/backup/daily/postgresql/*.sql.gz | \
xargs -I {} sh -c 'echo "{}: $(detect_version {})"'
# 驗證 v2 備份內容
verify_v2_backup() {
local backup_file=$1
echo "驗證備份: $backup_file"
# 檢查 monitor_jobs
if gzip -dc "$backup_file" | grep -q "monitor_jobs"; then
echo "✅ 包含 monitor_jobs"
else
echo "❌ 缺少 monitor_jobs"
return 1
fi
# 檢查 processor_results
if gzip -dc "$backup_file" | grep -q "processor_results"; then
echo "✅ 包含 processor_results"
else
echo "❌ 缺少 processor_results"
return 1
fi
echo "✅ v2 備份驗證通過"
}
```
---
## 5. 版本遷移
### 5.1 v1 → v2 遷移步驟
| 步驟 | 說明 | 驗證 |
|------|------|------|
| 1 | 確認所有 v1 備份已完成 | `ls *.sql.gz \| grep -v v2` |
| 2 | 修改 `backup_all.sh` 加入 v2 標記 | 確認 TIMESTAMP 包含 `v2_` |
| 3 | 修正 MongoDB 路徑 | 確認指向正確目錄 |
| 4 | 新增 Output 目錄備份 | 確認 probe.json 被備份 |
| 5 | 執行測試備份 | 驗證命名格式正確 |
| 6 | 驗證 v2 備份完整性 | `verify_v2_backup` |
| 7 | 正式啟用 v2 備份 | 確認 crontab 使用新版 |
### 5.2 遷移驗證清單
```bash
#!/bin/bash
# verify_v2_migration.sh
echo "=== v2 遷移驗證 ==="
# 1. 檢查備份腳本
echo "1. 檢查備份腳本..."
if grep -q "v2_" /Users/accusys/momentry/scripts/backup_all.sh; then
echo " ✅ 版本標記已啟用"
else
echo " ❌ 版本標記未啟用"
fi
# 2. 檢查 MongoDB 路徑
echo "2. 檢查 MongoDB 路徑..."
if grep -q "/opt/homebrew/var/mongodb" /Users/accusys/momentry/scripts/backup_all.sh; then
echo " ✅ MongoDB 路徑已修正"
else
echo " ❌ MongoDB 路徑未修正"
fi
# 3. 檢查 Output 目錄備份
echo "3. 檢查 Output 目錄備份..."
if grep -q "momentry_output" /Users/accusys/momentry/scripts/backup_all.sh; then
echo " ✅ Output 目錄備份已啟用"
else
echo " ❌ Output 目錄備份未啟用"
fi
# 4. 檢查最新備份
echo "4. 檢查最新備份..."
latest_backup=$(ls -t /Users/accusys/momentry/backup/daily/postgresql/*.sql.gz 2>/dev/null | head -1)
if [ -n "$latest_backup" ]; then
version=$(detect_version "$latest_backup")
echo " 最新備份: $(basename $latest_backup)"
echo " 版本: $version"
if [ "$version" = "v2" ]; then
verify_v2_backup "$latest_backup"
fi
fi
echo "=== 驗證完成 ==="
```
---
## 6. 疑難排解
### 6.1 常見問題
| 問題 | 原因 | 解決方案 |
|------|------|----------|
| 無法識別版本 | 檔名被修改 | 使用內容分析 `gzip -dc \| grep "monitor_jobs"` |
| v2 備份還原失敗 | 磁碟空間不足 | 清理空間後重試 |
| v1 還原覆蓋 v2 | 操作失誤 | 使用隔離還原保護生產資料 |
| MongoDB 備份為空 | 路徑錯誤 | 修正為 `/opt/homebrew/var/mongodb` |
### 6.2 緊急回滾流程
```bash
#!/bin/bash
# emergency_restore.sh
set -e
BACKUP_FILE=$1
VERSION=$2
echo "=== 緊急回滾 ==="
echo "備份檔案: $BACKUP_FILE"
echo "目標版本: $VERSION"
# 1. 建立當前狀態快照
echo "1. 建立當前狀態快照..."
NOW=$(date +%Y%m%d_%H%M%S)
pg_dump -U accusys -d momentry | gzip > "/tmp/momentry_emergency_$NOW.sql.gz"
echo " 快照: /tmp/momentry_emergency_$NOW.sql.gz"
# 2. 執行還原
echo "2. 執行還原..."
gunzip -c "$BACKUP_FILE" | psql -U accusys -d momentry
# 3. 驗證
echo "3. 驗證還原..."
psql -U accusys -d momentry -c "SELECT COUNT(*) FROM monitor_jobs;"
echo "=== 回滾完成 ==="
```
---
## 7. 備份清單(v2
### 7.1 每日備份(v2 格式)
| 服務 | 備份項目 | 檔案命名 | 說明 |
|------|----------|----------|------|
| PostgreSQL | momentry | `postgresql_db_momentry_v2_{date}_{time}.sql.gz` | 完整資料庫 |
| PostgreSQL | video_register | `postgresql_db_video_register_v2_{date}_{time}.sql.gz` | 影片註冊資料 |
| Redis | RDB | `redis_rdb_v2_{date}_{time}.rdb` | Redis 快照 |
| MongoDB | 資料 | `mongodb_data_v2_{date}_{time}.tar.gz` | MongoDB 資料 |
| n8n | 資料+DB | `n8n_{date}_{time}.tar.gz`, `n8n_db_{date}_{time}.sql.gz` | n8n 完整 |
| SFTPGo | 配置+DB | `sftpgo_{date}_{time}.tar.gz`, `sftpgo_db_{date}_{time}.sql.gz` | SFTPGo |
| Gitea | 資料 | `gitea_{date}_{time}.tar.gz` | Gitea |
| Output | 檔案 | `momentry_output_v2_{date}_{time}.tar.gz` | probe.json 等 |
### 7.2 備份保留策略
| 類型 | 保留期限 | 位置 |
|------|----------|------|
| 每日備份 | 7 天 | `backup/daily/` |
| 每週備份 | 4 週 | `backup/weekly/` |
| 每月備份 | 12 個月 | `backup/monthly/` |
| 歸檔 | 1 年+ | `backup/archive/` |
---
## 8. 相關文件
| 文件 | 說明 |
|------|------|
| [SERVICES.md](./SERVICES.md) | 服務說明 |
| [MOMENTRY_CORE_MONITORING.md](./MOMENTRY_CORE_MONITORING.md) | 監控規範 |
| `/Users/accusys/momentry/scripts/backup_all.sh` | 備份腳本 |
| `/Users/accusys/momentry/scripts/restore_all.sh` | 還原腳本 |
| `/Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh` | 備份監控 |
---
## 附錄 A:v2 備份完整性檢查清單
```bash
#!/bin/bash
# check_v2_integrity.sh
BACKUP_DIR="/Users/accusys/momentry/backup/daily"
echo "=== v2 備份完整性檢查 ==="
# 檢查 PostgreSQL
echo "1. PostgreSQL..."
pg_backup=$(ls -t "$BACKUP_DIR/postgresql"/postgresql_db_momentry_v2_*.sql.gz 2>/dev/null | head -1)
if [ -n "$pg_backup" ]; then
echo " 備份: $(basename $pg_backup)"
verify_v2_backup "$pg_backup"
else
echo " ❌ 未找到 v2 備份"
fi
# 檢查 Output
echo "2. Output 目錄..."
output_backup=$(ls -t "$BACKUP_DIR/momentry"/momentry_output_v2_*.tar.gz 2>/dev/null | head -1)
if [ -n "$output_backup" ]; then
echo " 備份: $(basename $output_backup)"
echo " ✅ Output 備份已存在"
else
echo " ❌ 未找到 Output 備份"
fi
# 檢查 MongoDB
echo "3. MongoDB..."
mongo_backup=$(ls -t "$BACKUP_DIR/mongodb"/mongodb_data_v2_*.tar.gz 2>/dev/null | head -1)
if [ -n "$mongo_backup" ]; then
size=$(stat -f%z "$mongo_backup" 2>/dev/null || stat -c%s "$mongo_backup" 2>/dev/null)
echo " 備份: $(basename $mongo_backup)"
echo " 大小: $size bytes"
if [ "$size" -gt 1000 ]; then
echo " ✅ MongoDB 備份有效"
else
echo " ⚠️ MongoDB 備份可能為空"
fi
else
echo " ❌ 未找到 MongoDB 備份"
fi
echo "=== 檢查完成 ==="
```
-586
View File
@@ -1,586 +0,0 @@
# Video Chunk 切分規範
本文檔定義 Momentry Core 系統中影片 chunks 的切分原則與資料結構。
---
## 1. Chunk 概述
### 1.1 設計原則
1. **允許重疊**: 不同類型的 chunk 可以重疊(如語句 chunk 與時間 chunk
2. **Frame 精確度**: 時間坐標精確到影片 frame
3. **多元分類**: 支援語句、場景、時間三種分割方式
### 1.2 Chunk 類型
| 類型 | 說明 | 是否可重疊 |
|------|------|------------|
| **Sentence** | 語句分割 | ✅ 可與其他類型重疊 |
| **Cut** | 場景切割 | ✅ 可與其他類型重疊 |
| **TimeBased** | 時間長度切割 | ✅ 可與其他類型重疊 |
---
## 2. 時間坐標系統
### 2.1 時間格式
所有時間使用 **秒** 為單位,精確到 **微秒** (浮點數)
```json
{
"start_time": 10.5,
"end_time": 15.75
}
```
### 2.2 Frame 計算
```
frame_number = floor(time_in_seconds * fps)
time_at_frame = frame_number / fps
```
**範例**:
- 影片 FPS: 24/1 (24 fps)
- 時間: 10.5 秒
- Frame: floor(10.5 * 24) = 252
- 校驗: 252 / 24 = 10.5 秒 ✅
### 2.3 Frame 資訊結構
```json
{
"start_time": 10.5,
"start_frame": 252,
"end_time": 15.75,
"end_frame": 378,
"fps": "24/1",
"fps_value": 24.0
}
```
---
## 3. 三種切分方式
### 3.1 Sentence (語句分割)
**原則**:
- 根據 ASR 語音識別結果
- 每個識別的語句為一個 chunk
- 文字內容來自 ASR 輸出
**範例**:
```
ASR 輸出:
[
{"start": 10.0, "end": 15.0, "text": "Hello world"},
{"start": 15.0, "end": 20.0, "text": "This is a test"},
{"start": 20.0, "end": 25.5, "text": "Processing video"}
]
轉換為 Chunks:
┌────────────────────────────────────────┐
│ chunk_0001: 10.0s - 15.0s "Hello world" │
├────────────────────────────────────────┤
│ chunk_0002: 15.0s - 20.0s "This is a test" │
├────────────────────────────────────────┤
│ chunk_0003: 20.0s - 25.5s "Processing video" │
└────────────────────────────────────────┘
```
### 3.2 Cut (場景切割)
**原則**:
- 根據影片鏡頭變化 (scene change / cut detection)
- 使用 ffmpeg 或 Python (scenedetect) 偵測
- 每個場景為一個 chunk
**偵測方法**:
```bash
# 使用 ffmpeg 偵測場景變化
ffmpeg -i input.mp4 -filter:v "select='gt(scene,0.3)',showinfo" -f null -
```
**範例**:
```
場景偵測結果:
[
{"start": 0.0, "end": 45.2, "scene_id": 1},
{"start": 45.2, "end": 120.5, "scene_id": 2},
{"start": 120.5, "end": 180.0, "scene_id": 3}
]
轉換為 Chunks:
┌────────────────────────────────────────┐
│ chunk_0001: 0.0s - 45.2s (Scene 1) │
├────────────────────────────────────────┤
│ chunk_0002: 45.2s - 120.5s (Scene 2) │
├────────────────────────────────────────┤
│ chunk_0003: 120.5s - 180.0s (Scene 3) │
└────────────────────────────────────────┘
```
### 3.3 TimeBased (時間長度切割)
**原則**:
- 固定時間長度切割
- 預設 **10 秒** 為一個 chunk
- 最後一個 chunk 可能不足 10 秒
- **支援重疊** (可設定 overlap 秒數)
**參數配置**:
| 參數 | 預設值 | 說明 |
|------|--------|------|
| duration | 10.0 | 每個 chunk 時長 (秒) |
| overlap | 0.0 | 重疊時長 (秒) |
**範例** (無重疊):
```
影片時長: 35 秒, duration=10
Chunks:
┌────────────────────────────────────────┐
│ chunk_0001: 0.0s - 10.0s │
├────────────────────────────────────────┤
│ chunk_0002: 10.0s - 20.0s │
├────────────────────────────────────────┤
│ chunk_0003: 20.0s - 30.0s │
├────────────────────────────────────────┤
│ chunk_0004: 30.0s - 35.0s (不足10秒) │
└────────────────────────────────────────┘
```
**範例** (有重疊, overlap=2):
```
影片時長: 35 秒, duration=10, overlap=2
Chunks:
┌────────────────────────────────────────┐
│ chunk_0001: 0.0s - 10.0s │
├────────────────────────────────────────┤
│ chunk_0002: 8.0s - 18.0s (重疊 2秒) │
├────────────────────────────────────────┤
│ chunk_0003: 16.0s - 26.0s (重疊 2秒) │
├────────────────────────────────────────┤
│ chunk_0004: 24.0s - 34.0s (重疊 2秒) │
├────────────────────────────────────────┤
│ chunk_0005: 32.0s - 35.0s (重疊+不足) │
└────────────────────────────────────────┘
```
---
## 4. Chunk 資料結構
### 4.1 基本結構
```json
{
"uuid": "1636719dc31f78ac",
"chunk_id": "sentence_0001",
"chunk_index": 1,
"chunk_type": "sentence",
"start_time": 10.5,
"start_frame": 252,
"end_time": 15.75,
"end_frame": 378,
"fps": "24/1",
"fps_value": 24.0,
"content": {
"text": "Hello world, this is a test"
},
"metadata": {
"source": "asr",
"confidence": 0.95,
"language": "en"
}
}
```
### 4.2 欄位說明
| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `uuid` | String | ✅ | 影片 UUID (16 字元) |
| `chunk_id` | String | ✅ | Chunk 唯一 ID |
| `chunk_index` | Integer | ✅ | Chunk 索引 (從 0 開始) |
| `chunk_type` | String | ✅ | 類型: sentence/cut/time_based |
| `start_time` | Float | ✅ | 開始時間 (秒) |
| `start_frame` | Integer | ✅ | 開始 frame 編號 |
| `end_time` | Float | ✅ | 結束時間 (秒) |
| `end_frame` | Integer | ✅ | 結束 frame 編號 |
| `fps` | String | ✅ | FPS 表示 (如 "24/1") |
| `fps_value` | Float | ✅ | FPS 數值 (如 24.0) |
| `content` | Object | ✅ | 內容 (見下文) |
| `metadata` | Object | ❌ | 額外資訊 (見下文) |
### 4.3 Content 結構
根據 `chunk_type` 不同,content 結構也不同:
#### Sentence Content
```json
{
"content": {
"text": "Hello world, this is a test message",
"text_normalized": "hello world this is a test message",
"word_count": 7,
"char_count": 34
}
}
```
| 欄位 | 類型 | 說明 |
|------|------|------|
| `text` | String | 原始識別文字 |
| `text_normalized` | String | 正規化文字 (小寫,去除標點) |
| `word_count` | Integer | 字詞數量 |
| `char_count` | Integer | 字元數量 |
#### Cut Content
```json
{
"content": {
"scene_id": 2,
"scene_number": 2,
"transition_type": "cut",
"scene_change_score": 0.95
}
}
```
| 欄位 | 類型 | 說明 |
|------|------|------|
| `scene_id` | Integer | 場景 ID |
| `scene_number` | Integer | 場景編號 |
| `transition_type` | String | 轉場類型: cut/dissolve/fade |
| `scene_change_score` | Float | 場景變化分數 (0-1) |
#### TimeBased Content
```json
{
"content": {
"duration": 10.0,
"is_last": false,
"segment_number": 3,
"total_segments": 10
}
}
```
| 欄位 | 類型 | 說明 |
|------|------|------|
| `duration` | Float | 時長 (秒) |
| `is_last` | Boolean | 是否最後一個 chunk |
| `segment_number` | Integer | 分段編號 |
| `total_segments` | Integer | 總分段數 |
### 4.4 Metadata 結構
```json
{
"metadata": {
"source": "asr",
"confidence": 0.95,
"language": "en",
"model": "tiny",
"created_at": "2026-03-16T10:00:00Z"
}
}
```
| 欄位 | 類型 | 說明 |
|------|------|------|
| `source` | String | 來源: asr/scene_detect/time_based |
| `confidence` | Float | 信心度 (0-1) |
| `language` | String | 語言代碼 |
| `model` | String | 使用模型 |
| `created_at` | String | 創建時間 (ISO 8601) |
---
## 5. Chunk ID 命名規範
### 5.1 格式
```
{chunk_type}_{chunk_index:04}
```
| 類型 | 前綴 | 範例 |
|------|------|------|
| Sentence | `sentence_` | `sentence_0001` |
| Cut | `cut_` | `cut_0001` |
| TimeBased | `time_based_` | `time_based_0001` |
### 5.2 編號規則
-**0** 開始
- 使用 **4 位數** 補零
- 按時間順序遞增
---
## 6. 資料庫 Schema
### 6.1 PostgreSQL Table
```sql
CREATE TABLE chunks (
id BIGSERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
start_frame BIGINT NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
end_frame BIGINT NOT NULL,
fps VARCHAR(16) NOT NULL,
fps_value DOUBLE PRECISION NOT NULL,
content JSONB NOT NULL,
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(uuid, chunk_id)
);
-- 索引
CREATE INDEX idx_chunks_uuid ON chunks(uuid);
CREATE INDEX idx_chunks_type ON chunks(chunk_type);
CREATE INDEX idx_chunks_time ON chunks(start_time, end_time);
CREATE INDEX idx_chunks_uuid_type ON chunks(uuid, chunk_type);
```
### 6.2 查詢範例
```sql
-- 查詢影片所有 chunks
SELECT * FROM chunks WHERE uuid = '1636719dc31f78ac';
-- 查詢特定類型的 chunks
SELECT * FROM chunks WHERE uuid = '1636719dc31f78ac' AND chunk_type = 'sentence';
-- 查詢時間範圍內的 chunks
SELECT * FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND start_time <= 30.0 AND end_time >= 20.0;
-- 查詢時間範圍內的所有 chunks (混合類型)
SELECT * FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND start_time <= 30.0 AND end_time >= 20.0
ORDER BY chunk_type, chunk_index;
```
---
## 7. Rust 資料結構
### 7.1 Chunk 定義
```rust
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ChunkType {
Sentence,
Cut,
TimeBased,
}
impl ChunkType {
pub fn as_str(&self) -> &'static str {
match self {
ChunkType::Sentence => "sentence",
ChunkType::Cut => "cut",
ChunkType::TimeBased => "time_based",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chunk {
pub uuid: String,
pub chunk_id: String,
pub chunk_index: u32,
pub chunk_type: ChunkType,
pub start_time: f64,
pub start_frame: i64,
pub end_time: f64,
pub end_frame: i64,
pub fps: String,
pub fps_value: f64,
pub content: serde_json::Value,
pub metadata: Option<serde_json::Value>,
}
```
### 7.2 建立 Chunk
```rust
impl Chunk {
pub fn new(
uuid: String,
chunk_index: u32,
chunk_type: ChunkType,
start_time: f64,
end_time: f64,
fps: &str,
content: serde_json::Value,
) -> Self {
let fps_value = parse_fps(fps);
let start_frame = (start_time * fps_value) as i64;
let end_frame = (end_time * fps_value) as i64;
let chunk_id = format!("{}_{:04}", chunk_type.as_str(), chunk_index);
Self {
uuid,
chunk_id,
chunk_index,
chunk_type,
start_time,
start_frame,
end_time,
end_frame,
fps: fps.to_string(),
fps_value,
content,
metadata: None,
}
}
}
```
---
## 8. 時間切割器實作
### 8.1 TimeBasedSplitter
```rust
pub struct TimeBasedSplitter {
pub duration: f64, // 每個 chunk 時長 (秒)
pub overlap: f64, // 重疊時長 (秒)
}
impl TimeBasedSplitter {
pub fn new(duration: f64, overlap: f64) -> Self {
Self { duration, overlap }
}
pub fn split(&self, uuid: &str, video_duration: f64, fps: f64) -> Vec<Chunk> {
let mut chunks = Vec::new();
let step = self.duration - self.overlap;
let mut current_time = 0.0;
let mut index = 0;
while current_time < video_duration {
let end_time = (current_time + self.duration).min(video_duration);
let chunk = Chunk::new(
uuid.to_string(),
index,
ChunkType::TimeBased,
current_time,
end_time,
&format!("{:.0}/1", fps as u32),
serde_json::json!({
"duration": end_time - current_time,
"is_last": end_time >= video_duration,
"segment_number": index + 1,
}),
);
chunks.push(chunk);
current_time += step;
index += 1;
}
chunks
}
}
```
### 8.2 使用範例
```rust
// 建立時間切割器 (10秒, 無重疊)
let splitter = TimeBasedSplitter::new(10.0, 0.0);
let chunks = splitter.split(&uuid, video_duration, 24.0);
// 建立時間切割器 (10秒, 2秒重疊)
let splitter = TimeBasedSplitter::new(10.0, 2.0);
let chunks = splitter.split(&uuid, video_duration, 24.0);
```
---
## 9. 處理流程
### 9.1 完整流程
```
1. Register (註冊影片)
└── 取得 UUID, video_duration, fps
2. Probe (探測影片)
└── 取得 streams, format, fps
3. 產生 Sentence Chunks
└── 讀取 ASR 輸出
└── 為每個 segment 建立 chunk
4. 產生 Cut Chunks
└── 執行場景偵測
└── 為每個 scene 建立 chunk
5. 產生 TimeBased Chunks
└── 使用 TimeBasedSplitter
└── 為每個時間段建立 chunk
6. 儲存至資料庫
└── 批次寫入 PostgreSQL
```
### 9.2 輸出範例
```
影片: 35 秒, FPS: 24
Sentence Chunks (3 個):
sentence_0000: 0.0s - 10.0s (252 frames)
sentence_0001: 10.0s - 20.0s (480 frames)
sentence_0002: 20.0s - 35.0s (840 frames)
Cut Chunks (3 個):
cut_0000: 0.0s - 15.0s (360 frames)
cut_0001: 15.0s - 28.0s (672 frames)
cut_0002: 28.0s - 35.0s (168 frames)
TimeBased Chunks (4 個, 重疊 2秒):
time_based_0000: 0.0s - 10.0s (240 frames)
time_based_0001: 8.0s - 18.0s (240 frames)
time_based_0002: 16.0s - 26.0s (240 frames)
time_based_0003: 24.0s - 35.0s (264 frames)
```
---
## 10. 相關文件
- [JSON_OUTPUT_SPEC.md](./JSON_OUTPUT_SPEC.md) - JSON 輸出規範
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [AGENTS.md](../AGENTS.md) - 開發規範
-424
View File
@@ -1,424 +0,0 @@
# Momentry Core 開發日誌
> **文檔維護開始**2026-03-18
> **⚠️ 補充說明**:事後補記(2026-03-18 以前),僅供參考。未來紀錄將即時記錄,參考價值較高。
---
## 開發工具
### Coding LLM 模型
| 階段 | 工具 | 模型 | ID | 說明 |
|------|------|------|-----|------|
| **初期** | Claude CLI | - | - | 初始專案架構建立 |
| **中期** | OpenCode | big-pickle | opencode/big-pickle | 主要開發協作者 |
**切換記錄**
- 初期使用 Claude CLI 建立專案基本架構
- 中期切換至 OpenCode (big-pickle) 進行主要功能開發
---
## 2026-03-17
### ML 模型選用
| Processor | 模型 | 版本/大小 | 說明 |
|----------|------|-----------|------|
| **ASR** | WhisperX (faster-whisper) | base, int8 | 語音識別 + 對話分段 |
| **CUT** | PySceneDetect | 0.6.7.1 | ContentDetector 場景檢測 |
| **YOLO** | YOLOv8n | yolov8n.pt (6.2MB) | 物體檢測(nano 版本最快) |
| **OCR** | EasyOCR | 1.7.2 | 文字識別 |
| **Face** | OpenCV Haar Cascade | built-in | 人臉檢測(無需額外下載) |
| **Pose** | YOLOv8n-Pose | yolov8n-pose.pt (6.5MB) | 姿態估計(nano 版本) |
**模型下載**
- YOLOv8n: `yolov8n.pt` (6.2MB)
- YOLOv8n-Pose: `yolov8n-pose.pt` (6.5MB)
**Python 依賴**
```
torch==2.8.0
whisperx==3.8.2
ultralytics==8.4.23
scenedetect==0.6.7.1
easyocr==1.7.2
opencv-python==4.13.0.92
```
---
### ASR 實作完成
- 完成 Python ML processor scripts(使用本地模型)
- `asrx_processor.py` - whisperx for speaker diarization
- `cut_processor.py` - PySceneDetect for scene detection
- `yolo_processor.py` - YOLOv8 for object detection
- `ocr_processor.py` - EasyOCR for text recognition
- `face_processor.py` - OpenCV Haar Cascade for face detection
- `pose_processor.py` - YOLOv8 Pose for pose estimation
- 更新 `requirements.txt` with all dependencies
- 安裝完成:torch 2.8.0, whisperx 3.8.2, ultralytics 8.4.23, scenedetect 0.6.7.1, easyocr 1.7.2, opencv-python 4.13.0.92
- 下載模型:YOLOv8n.pt (6.2MB), YOLOv8n-Pose.pt (6.5MB)
### Async Streaming 實作
- 更新 Rust processor modules 使用 async streaming 進行 real-time progress
- `src/core/processor/asr.rs`
- `src/core/processor/cut.rs`
- `src/core/processor/yolo.rs`
- `src/core/processor/ocr.rs`
- `src/core/processor/face.rs`
- `src/core/processor/pose.rs`
### 測試結果
- 測試影片:BigBuckBunny_320x180.mp4
- ASR: 4 segments
- CUT: 134 scenes
- YOLO: 14315 frames(每幀處理耗時)
- OCR: 40 frames with text
- Face: 44 frames with faces
- Pose: Timeout
---
### Warning 清理
修復 clippy warnings
- 移除未使用的 imports (HashMap in mongodb_db.rs, postgres_db.rs)
- 新增 `#[allow(dead_code)]` 標註未使用變數
- 新增 `Default` implementation for MongoDb, QdrantDb
-`probe` module 重新命名為 `ffprobe`
- 新增 `player` feature in Cargo.toml
- 修復 `format_in_format_args` 警告
---
### TUI Progress Window 實作
建立新的 UI module
- 建立 `src/ui/mod.rs`
- 建立 `src/ui/progress/mod.rs`
實作功能:
- ProcessorProgress 結構(追蹤每個 processor 狀態)
- ProgressState 結構(管理所有 processors
- ProgressUi 結構(ratatui TUI 渲染)
- 整合到 `src/main.rs` 的 process 命令
TUI 顯示:
```
┌ Processing: BigBuckBunny_320x180.mp4 ────────────────────────────────────────┐
│ ASR [████████████] 100% (4 segs) │
│ CUT [████████████] 100% (134 scenes) │
│ ASRX [████████████] 100% (0 segs) │
│ YOLO [██░░░░░░░░░░░] 30% (4200/14315) ETA 2:30 │
│ OCR [---------] 0% │
│ Face [---------] 0% │
│ Pose [---------] 0% │
└──────────────────────────────────────────────────────────────────────────────┘
```
---
### 輸出位置討論
討論 stdout vs stderr vs TUI 的輸出配置:
- 最終結果 → stdout
- Python progress → 需改用 Redis Pub/Sub
- TUI Progress → stderr (ratatui)
---
## 2026-03-18
### Redis Message Bus 設計
討論使用 Redis 作為消息總線,分離 Python 輸出與 Rust TUI 顯示。
設計重點:
1. 頻道命名:`momentry:progress:{uuid}`
2. 本地 Redis`localhost:6379`
3. 失敗策略:完全失效(因 stdout 問題未解決)
### UUID 使用時機分析
分析 Redis Key 上使用 UUID 的時機:
**全局 Keys(無 UUID**
- health, stats, jobs 管理
**Per-Video KeysUUID 必要)**
- job:{uuid}, progress:{uuid}, metrics:{uuid}
**Per-Processor KeysUUID + Processor 必要)**
- job:{uuid}:processor:{name}
### 備份系統整合
參考 `docs/SERVICE_ADDITION_GUIDE.md` 設計規範,規劃 OutputDir 模組:
1. **環境變數**
- `MOMENTRY_OUTPUT_DIR` - JSON 輸出目錄
- `MOMENTRY_BACKUP_DIR` - 備份目錄(預設:`/Users/accusys/momentry/backup/momentry`
- `MOMENTRY_BACKUP_ENABLED` - 啟用備份
2. **命名格式**
- 備份格式:`momentry_data_{YYYYMMDD}_{HHMMSS}_{uuid}.{ext}`
- 校驗和:`{filename}.sha256`
3. **CLI 命令**
- `cargo run -- backup list` - 列出備份
- `cargo run -- backup cleanup` - 清理舊備份
- `cargo run -- backup verify` - 驗證備份
---
### 監控系統整合
討論將 momentry_core 納入監控系統:
1. **Layer 2: Service 監控**
- 新增 momentry_core CLI 檢查
2. **Layer 7: Backup 監控**
- 新增 momentry 備份配置
3. **Redis 監控**
- 健康檢查
- Job 狀態監控
- 即時進度監控
---
## 實作完成項目
### 程式碼變更
| 日期 | 檔案 | 變更 |
|------|------|------|
| 2026-03-17 | `src/core/processor/*.rs` | Async streaming 更新 |
| 2026-03-17 | `src/ui/mod.rs` | 新增 UI module |
| 2026-03-17 | `src/ui/progress/mod.rs` | 新增 Progress TUI |
| 2026-03-17 | `src/main.rs` | 整合 Progress UI |
| 2026-03-18 | `src/core/storage/output_dir.rs` | 新增 OutputDir 模組 |
| 2026-03-18 | `src/core/storage/mod.rs` | 新增 output_dir export |
| 2026-03-18 | `src/core/db/redis_client.rs` | 新增 Redis 客戶端(Hash + Pub/Sub |
| 2026-03-18 | `src/core/db/mod.rs` | 新增 redis_client export |
### 新增檔案
| 日期 | 檔案 | 說明 |
|------|------|------|
| 2026-03-18 | `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 |
| 2026-03-18 | `docs/MOMENTRY_CORE_MONITORING.md` | 監控規範(暫定) |
| 2026-03-18 | `scripts/redis_publisher.py` | Redis 訊息發布模組 |
### 更新檔案
| 日期 | 檔案 | 說明 |
|------|------|------|
| 2026-03-17 | `Cargo.toml` | 新增 player feature |
| 2026-03-17 | `src/lib.rs` | 新增 ui module exports |
| 2026-03-18 | `docs/PENDING_ISSUES.md` | 新增問題 #2, #3 |
| 2026-03-18 | `src/core/storage/output_dir.rs` | 預設改為 `./output` |
| 2026-03-18 | `scripts/yolo_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/cut_processor.py` | 新增 Redis |
| 2026-03-18 | `scripts/ocr_processor.py` | 新增 Redis |
| 2026-03-18 | `scripts/face_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/pose_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/asr_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/asrx_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `requirements.txt` | 新增 redis>=5.0.0 |
| 2026-03-18 | `src/core/processor/yolo.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/cut.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/ocr.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/face.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/pose.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/asr.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/asrx.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/main.rs` | 更新所有 processor 調用傳入 uuid |
| 2026-03-18 | `Cargo.toml` | 新增 futures-util 依賴 |
| 2026-03-18 | `src/core/db/redis_client.rs` | 新增 subscribe_and_callback 方法,密碼認證 |
| 2026-03-18 | `src/ui/progress/mod.rs` | 新增 update_from_redis 方法 |
| 2026-03-18 | `scripts/redis_publisher.py` | 新增密碼認證支援 |
| 2026-03-18 | 測試 | Redis Pub/Sub 成功運作 |
---
## 待解決問題
### 問題 #1: sqlx async INSERT 不會實際寫入數據庫
- 狀態:待解決
- 影響:`store_vector` 函數,PVector 存儲
### 問題 #2: TUI 與 stdout 輸出混合
- 狀態:已解決
- 解決方案:使用 Redis Message Bus
- 進度:
- ✅ Redis 客戶端 (`src/core/db/redis_client.rs`)
- ✅ Python redis_publisher.py
- ✅ 所有 Python processors 更新完成
- ✅ 所有 Rust processor 函數更新完成
- ✅ main.rs 調用更新完成
- ✅ Rust TUI Redis 訂閱已完成
### 問題 #3: Redis Message Bus 尚未實作
- 狀態:已解決
- 詳細設計:參考 `docs/MOMENTRY_CORE_REDIS_KEYS.md`
- 進度:Python 端 + Rust 端均已完成
---
## 環境變數
```bash
# 輸出目錄
MOMENTRY_OUTPUT_DIR=./output # 預設
# 備份
MOMENTRY_BACKUP_ENABLED=false # 預設
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry
# Redis(未來實作)
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=accusys
```
---
## 數據庫
- PostgreSQL: `postgres://accusys@localhost:5432/momentry`
- Redis: `localhost:6379`(待實作)
- Qdrant: `localhost:6333`
---
## 指令範例
```bash
# 註冊視頻
cargo run -- register /path/to/video.mp4
# 處理視頻
cargo run -- process <uuid>
# 列出備份
cargo run -- backup list
# 清理備份
cargo run -- backup cleanup
# 驗證備份
cargo run -- backup verify
# 查看狀態
cargo run -- status
# API Server
cargo run -- server --host 0.0.0.0 --port 3000
```
---
## 2026-03-18 (進行中)
### Redis Message Bus 實作
**問題**TUI 與 Python stdout 輸出混合,導致 TUI 顯示混亂
**解決方案**:使用 Redis Pub/Sub 作為訊息匯流排
**實作內容**
| 元件 | 檔案 | 狀態 |
|------|------|------|
| Redis 客戶端 | `src/core/db/redis_client.rs` | ✅ |
| Progress 訂閱 | `src/main.rs` | ✅ |
| UI 更新 | `src/ui/progress/mod.rs` | ✅ |
| Python Publisher | `scripts/redis_publisher.py` | ✅ |
| Python Processors | 7 個 `scripts/*_processor.py` | ✅ |
| Rust 函數 | `src/core/processor/*.rs` | ✅ |
**流程**
```
Python Processor ──(Redis Pub)──> Redis ──(Subscribe)──> Rust TUI
```
**測試結果**
- Redis 連線 ✅
- 密碼認證 ✅
- 即時進度發布 ✅
- TUI 即時更新 ✅
**新增依賴**
- `futures-util = "0.3"` (Cargo.toml)
- `redis >= 5.0.0` (requirements.txt)
---
## 2026-03-18 (HTTP API)
### HTTP API 實作
**問題**:TUI 運作正常但使用者偏好 HTTP API 來查詢進度
**解決方案**:建立 HTTP 端點 + Redis Hash 儲存
**實作內容**
| 元件 | 檔案 | 變更 |
|------|------|------|
| HTTP 端點 | `src/api/server.rs` | 新增 `/api/v1/progress/:uuid` |
| Redis Hash 查詢 | `src/core/db/redis_client.rs` | 新增 `get_processor_status` 方法 |
| Progress 儲存 | `src/main.rs` | 新增 Redis HSET 儲存進度 |
**API 端點**
```
GET /api/v1/progress/:uuid
Response:
{
"uuid": "5dea6618a606e7c7",
"processors": [
{"name": "asr", "status": "complete", "current": 0, "total": 0, "message": "7 segments"},
{"name": "cut", "status": "complete", "current": 134, "total": 134, "message": "134 scenes"},
{"name": "yolo", "status": "complete", "current": 14300, "total": 14315, "message": "..."},
...
]
}
```
**流程**
```
Python Processor ──(Redis Pub)──> Redis ──(Subscribe)──> Rust TUI
└──(HSET)──> Redis Hash
HTTP Client ──(GET /progress/:uuid)──> Rust API ─(HGETALL)──> Redis Hash
```
**測試結果**
- ✅ 編譯成功
- ✅ API 伺服器啟動 (port 3002)
- ✅ 即時進度查詢
- ✅ 完整流程測試 (BigBuckBunny_320x180.mp4)
**除錯記錄**
1. 語法錯誤:main.rs 有重複程式碼區塊 (lines 297-322),已移除
2. DB 連線池:從 5 增加到 10 個連線
3. PostgreSQL 狀態:處理 shutdown 狀態,殺掉 stale 連線
**新增變更**
- `src/api/server.rs` - 新增進度端點
- `src/core/db/redis_client.rs` - 新增 `get_processor_status` 方法
- `src/core/db/postgres_db.rs` - 連線池 5→10
- `src/main.rs` - Redis Hash 儲存 + 語法修復
**使用方式**
```bash
# 啟動 API 伺服器
cargo run --bin momentry -- server --host 127.0.0.1 --port 3002
# 註冊影片
cargo run --bin momentry -- register ~/test_video/BigBuckBunny_320x180.mp4
# 處理影片
cargo run --bin momentry -- process <uuid>
# 查詢進度
curl http://127.0.0.1:3002/api/v1/progress/<uuid>
```
-451
View File
@@ -1,451 +0,0 @@
# Caddy 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 Caddy Web Server,配置為本地部署,作為反向代理伺服器。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Caddy | ✅ 已安裝 v2.10.2 |
| 設定檔 | /Users/accusys/momentry/etc/Caddyfile |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.caddy.plist |
---
## 安裝步驟
### Step 1: 安裝 Caddy (使用 brew)
```bash
# 安裝 Caddy
brew install caddy
```
**驗證**:
```bash
caddy --version
# v2.10.2
```
---
### Step 2: 建立目錄
```bash
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/caddy
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/caddy
# 建立日誌文件
touch /Users/accusys/momentry/log/caddy.log
touch /Users/accusys/momentry/log/caddy.error.log
# 設定權限
# 注意: Caddy 使用 ports 80/443,必須以 root 身份運行
# 因此 var/caddy 目錄需要 root:admin 權限
chown -R accusys:staff /Users/accusys/momentry/etc/caddy
chown -R accusys:staff /Users/accusys/momentry/log
sudo chown -R root:admin /Users/accusys/momentry/var/caddy
```
---
### Step 3: 建立設定檔
建立 `/Users/accusys/momentry/etc/Caddyfile`:
```Caddyfile
{
email admin@accusys.com.tw
metrics
}
# 定義日誌 Snippet
(common_log) {
log {
output file /Users/accusys/momentry/log/{args[0]}.log {
roll_size 100mb
roll_keep 5
roll_keep_for 720h
}
format json
}
}
# Example: 反向代理到本地服務
example.momentry.ddns.net {
reverse_proxy localhost:8080 {
header_up Host {upstream_hostport}
}
import common_log example_access
}
```
---
### Step 4: 使用 plist 開機自動啟動
**注意**: Caddy 需要使用 ports 80 和 443,必須以 root 身份運行。
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.caddy.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.caddy.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "caddy"
type: "http"
port: 80
host: "localhost"
check_url: "http://localhost:2019/config/"
timeout: 5
enabled: true
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/etc/caddy/` | 配置 | **不要刪除** - Caddy 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/Users/accusys/momentry/var/caddy/` | 數據 | **不要刪除** - Caddy 數據 |
| `/opt/homebrew/opt/caddy/` | 安裝 | **刪除** - Caddy 安裝目錄 |
| `/opt/homebrew/opt/caddy/` | 安裝 | **刪除** - Caddy 安裝目錄 |
### Step 1: 停止 Caddy
```bash
# 找到 Caddy 進程
ps aux | grep caddy | grep -v grep
# 停止 Caddy
pkill caddy
# 確認停止
ps aux | grep caddy | grep -v grep || echo "Caddy 已停止"
```
---
### Step 2: 卸載 Caddy
```bash
# 卸載 Caddy
brew uninstall caddy
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.caddy.plist
sudo rm /Library/LaunchDaemons/com.momentry.caddy.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除配置目錄 (可選)
rm -rf /Users/accusys/momentry/etc/Caddyfile
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/caddy.log
rm -f /Users/accusys/momentry/log/caddy.error.log
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/caddy
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/etc
# /Users/accusys/momentry/log
# /Users/accusys/momentry/var
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== Caddy 卸載後檢查 ==="
# 1. 檢查 Caddy 進程
echo "1. Caddy 進程:"
ps aux | grep caddy | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 80/443
echo "2. Port 80/443:"
(lsof -i :80 > /dev/null 2>&1 || lsof -i :443 > /dev/null 2>&1) && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. caddy 命令
echo "3. caddy 命令:"
which caddy > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list caddy > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep caddy > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 配置目錄 (可選刪除)
echo "6. 配置目錄:"
[ -d "/Users/accusys/momentry/etc" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 日誌目錄 (可選刪除)
echo "7. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== Caddy 卸載後檢查 ===
1. Caddy 進程:
✓ 已停止
2. Port 80/443:
✓ 已釋放
3. caddy 命令:
✓ 已移除
4. brew 安裝:
✓ 已移除
5. launchctl 服務:
✓ 已移除
6. 配置目錄:
✓ 保留 (或 ✗ 已刪除)
7. 日誌目錄:
✓ 保留 (或 ✗ 已刪除)
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep caddy | grep -v grep
# 2. 檢查 Port
lsof -i :80
lsof -i :443
lsof -i :2019
# 3. 測試配置語法
caddy validate --config /Users/accusys/momentry/etc/Caddyfile
# 4. 查看 Caddy 版本
caddy version
# 5. 重新載入配置
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
# 6. 查看日誌
tail -20 /Users/accusys/momentry/log/caddy.log
# 7. 查看 Caddy 適配的網站
curl -I http://localhost:2019/config/
```
---
## Caddyfile 範例
### 基本反向代理
```Caddyfile
{
email admin@accusys.com.tw
}
# 反向代理到本地服務
example.local {
reverse_proxy localhost:8080
}
```
### 帶 SSL 的反向代理
```Caddyfile
{
email admin@accusys.com.tw
}
# 使用 Let's Encrypt 自動 SSL
example.momentry.ddns.net {
reverse_proxy localhost:8080 {
header_up Host {upstream_hostport}
}
}
```
### 多站點配置
```Caddyfile
{
email admin@accusys.com.tw
}
# 站點 1
site1.example.com {
reverse_proxy localhost:8080
}
# 站點 2
site2.example.com {
reverse_proxy localhost:8081
}
```
---
## 環境變數
`.env` 中:
```env
CADDY_CONFIG=/Users/accusys/momentry/etc/Caddyfile
CADDY_HOME=/Users/accusys/.local/share/caddy
```
---
## 故障排除
### Caddy 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/caddy.log
# 檢查配置語法
caddy validate --config /Users/accusys/momentry/etc/Caddyfile
# 檢查目錄權限
ls -la /Users/accusys/momentry/etc/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/etc
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 80
lsof -i :80
# 終止佔用程序
kill <PID>
```
### 需要重新載入配置
```bash
# 重新載入配置 (熱重載)
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
# 或者停止後重新啟動
sudo launchctl unload /Library/LaunchDaemons/com.momentry.caddy.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.caddy.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/caddy/` | Caddy 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/caddy/bin/caddy` | Caddy 執行檔 |
| 配置 | `/Users/accusys/momentry/etc/Caddyfile` | 設定檔 |
| 日誌 | `/Users/accusys/momentry/log/caddy.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/caddy.error.log` | 錯誤日誌 |
| 數據 | `/Users/accusys/momentry/var/caddy/` | Caddy 數據目錄 |
| plist | `/Library/LaunchDaemons/com.momentry.caddy.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/caddy_backup/Caddyfile` | 配置備份 |
---
## 常用指令
```bash
# 驗證配置
caddy validate --config /Users/accusys/momentry/etc/Caddyfile
# 熱重載配置
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
# 停止 Caddy
caddy stop
# 啟動 Caddy
caddy start --config /Users/accusys/momentry/etc/Caddyfile
# 適配所有網站
caddy adapt --config /Users/accusys/momentry/etc/Caddyfile --adapter caddyfile
```
---
## 備份與恢復
### 備份
```bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/Users/accusys/momentry/backup/daily/caddy"
mkdir -p "$BACKUP_DIR"
# 備份配置
tar -czf "$BACKUP_DIR/caddy_cfg_${TIMESTAMP}.tar.gz" \
/Users/accusys/momentry/etc/Caddyfile
# 驗證
sha256sum "$BACKUP_DIR/caddy_cfg_${TIMESTAMP}.tar.gz" > "$BACKUP_DIR/caddy_${TIMESTAMP}.sha256"
```
### 恢復
```bash
# 解壓配置
tar -xzf /Users/accusys/momentry/backup/daily/caddy/caddy_cfg_20260316_102416.tar.gz -C /
# 驗證並重載
caddy validate --config /Users/accusys/momentry/etc/Caddyfile
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
```
---
## 版本資訊
- 版本: 2.10.2
- 配置: /Users/accusys/momentry/etc/Caddyfile
- 日誌目錄: /Users/accusys/momentry/log/
-394
View File
@@ -1,394 +0,0 @@
# Gitea 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 Gitea Git 服務,配置為本地部署。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Gitea | ✅ 已安裝 v1.25.3 |
| 數據目錄 | /Users/accusys/momentry/var/gitea/ |
| 配置目錄 | /Users/accusys/momentry/etc/gitea/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.gitea.plist |
---
## 安裝步驟
### Step 1: 安裝 Gitea (使用 brew)
```bash
# 安裝 Gitea
brew install gitea
```
**驗證**:
```bash
gitea --version
# gitea version 1.25.3
```
---
### Step 2: 建立目錄
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/gitea/data
mkdir -p /Users/accusys/momentry/var/gitea/log
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/gitea
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/gitea.log
touch /Users/accusys/momentry/log/gitea.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/gitea
chown -R accusys:staff /Users/accusys/momentry/etc/gitea
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 3: 建立設定檔
建立 `/Users/accusys/momentry/etc/gitea/app.ini`:
```ini
APP_NAME = Gitea: Git with a cup of tea
RUN_USER = accusys
WORK_PATH = /Users/accusys/momentry/var/gitea
RUN_MODE = prod
[database]
DB_TYPE = postgres
HOST = 127.0.0.1:5432
NAME = gitea
USER = gitea
PASSWD = gitea_pass
SSL_MODE = disable
[repository]
ROOT = /Users/accusys/momentry/var/gitea/data/gitea-repositories
[server]
SSH_DOMAIN = gitea.momentry.ddns.net
DOMAIN = gitea.momentry.ddns.net
HTTP_PORT = 3000
ROOT_URL = http://gitea.momentry.ddns.net:3000/
APP_DATA_PATH = /Users/accusys/momentry/var/gitea/data
DISABLE_SSH = false
SSH_PORT = 2222
LFS_START_SERVER = true
OFFLINE_MODE = true
[lfs]
PATH = /Users/accusys/momentry/var/gitea/data/lfs
[log]
MODE = console, file
ROOT_PATH = /Users/accusys/momentry/log
```
---
### Step 4: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.gitea.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.gitea.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "gitea"
type: "http"
port: 3000
host: "localhost"
check_url: "http://localhost:3000/"
timeout: 5
enabled: true
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/gitea/` | 數據 | **不要刪除** - Gitea 數據 |
| `/Users/accusys/momentry/etc/gitea/` | 配置 | **不要刪除** - Gitea 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/opt/gitea/` | 安裝 | **刪除** - Gitea 安裝目錄 |
### Step 1: 停止 Gitea
```bash
# 找到 Gitea 進程
ps aux | grep gitea | grep -v grep
# 停止 Gitea
pkill gitea
# 確認停止
ps aux | grep gitea | grep -v grep || echo "Gitea 已停止"
```
---
### Step 2: 卸載 Gitea
```bash
# 卸載 Gitea
brew uninstall gitea
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.gitea.plist
sudo rm /Library/LaunchDaemons/com.momentry.gitea.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/gitea
# 刪除配置目錄 (可選)
rm -rf /Users/accusys/momentry/etc/gitea
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/gitea.log
rm -f /Users/accusys/momentry/log/gitea.error.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/etc
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== Gitea 卸載後檢查 ==="
# 1. 檢查 Gitea 進程
echo "1. Gitea 進程:"
ps aux | grep gitea | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 3000
echo "2. Port 3000:"
lsof -i :3000 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. gitea 命令
echo "3. gitea 命令:"
which gitea > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list gitea > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep gitea > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 數據目錄 (可選刪除)
echo "6. 數據目錄:"
[ -d "/Users/accusys/momentry/var/gitea" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 配置目錄 (可選刪除)
echo "7. 配置目錄:"
[ -d "/Users/accusys/momentry/etc/gitea" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== Gitea 卸載後檢查 ===
1. Gitea 進程:
✓ 已停止
2. Port 3000:
✓ 已釋放
3. gitea 命令:
✓ 已移除
4. brew 安裝:
✓ 已移除
5. launchctl 服務:
✓ 已移除
6. 數據目錄:
✓ 保留 (或 ✗ 已刪除)
7. 配置目錄:
✓ 保留 (或 ✗ 已刪除)
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep gitea | grep -v grep
# 2. 檢查 Port
lsof -i :3000
# 3. 測試連線
curl http://localhost:3000/
# 4. 查看版本
gitea --version
# 5. 驗證配置
gitea doctor --config /Users/accusys/momentry/etc/gitea/app.ini
# 6. 查看日誌
tail -20 /Users/accusys/momentry/log/gitea.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| URL | http://localhost:3000 |
| Domain | gitea.momentry.ddns.net |
| SSH Port | 2222 |
| Database | PostgreSQL (gitea) |
---
## 環境變數
`.env` 中:
```env
GITEA_URL=http://localhost:3000
GITEA_ROOT=/Users/accusys/momentry/var/gitea/data/gitea-repositories
```
---
## 故障排除
### Gitea 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/gitea.log
# 檢查配置語法
gitea doctor --config /Users/accusys/momentry/etc/gitea/app.ini
# 檢查目錄權限
ls -la /Users/accusys/momentry/var/gitea/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/gitea
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 3000
lsof -i :3000
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.gitea.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.gitea.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/gitea/` | Gitea 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/gitea/bin/gitea` | Gitea 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/gitea/data/` | 數據儲存 |
| 配置 | `/Users/accusys/momentry/etc/gitea/app.ini` | 設定檔 |
| 日誌 | `/Users/accusys/momentry/log/gitea.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/gitea.error.log` | 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.gitea.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/gitea_backup/app.ini` | 配置備份 |
---
## 資料庫資訊
Gitea 使用 PostgreSQL 作為資料庫:
| 項目 | 值 |
|------|-----|
| Database | gitea |
| User | gitea |
| Host | 127.0.0.1:5432 |
| Password | gitea_pass |
---
## 常用指令
```bash
# 啟動 Gitea
gitea web --config /Users/accusys/momentry/etc/gitea/app.ini
# 驗證配置
gitea doctor --config /Users/accusys/momentry/etc/gitea/app.ini
# 查看版本
gitea --version
# 備份數據
gitea dump --config /Users/accusys/momentry/etc/gitea/app.ini --zipfile /Users/accusys/momentry/var/gitea_backup.zip
```
---
## 版本資訊
- 版本: 1.25.3
- HTTP Port: 3000
- SSH Port: 2222
- 數據目錄: /Users/accusys/momentry/var/gitea/
- 配置: /Users/accusys/momentry/etc/gitea/app.ini
- 日誌目錄: /Users/accusys/momentry/log/
-380
View File
@@ -1,380 +0,0 @@
# MariaDB 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 MariaDB,配置為本地部署,支援遠端訪問。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| MariaDB | ✅ 已安裝 v12.1.2 |
| 數據目錄 | /Users/accusys/momentry/var/mariadb/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.mariadb.plist |
---
## 安裝步驟
### Step 1: 安裝 MariaDB (使用 brew)
```bash
# 安裝 MariaDB
brew install mariadb
```
**驗證**:
```bash
mariadb --version
# mariadb from 12.1.2-MariaDB
```
---
### Step 2: 建立目錄結構
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/mariadb
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/mariadb
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/mariadb.log
touch /Users/accusys/momentry/log/mariadb.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/mariadb
chown -R accusys:staff /Users/accusys/momentry/etc/mariadb
chown -R accusys:staff /Users/accusys/momentry/log
```
**注意**: 如果需要從舊數據遷移,需要先初始化新目錄:
```bash
# 初始化新數據目錄
mysql_install_db --datadir=/Users/accusys/momentry/var/mariadb
```
---
### Step 3: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.mariadb.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.mariadb.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
database:
mariadb:
enabled: true
host: "localhost"
port: 3306
user: "root"
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/mariadb/` | 數據 | **不要刪除** - 數據目錄 |
| `/Users/accusys/momentry/etc/mariadb/` | 配置 | **不要刪除** - 配置目錄 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/opt/mariadb/` | 安裝 | **刪除** - MariaDB 安裝目錄 |
### Step 1: 停止 MariaDB
```bash
# 找到 MariaDB 進程
ps aux | grep mariadb | grep -v grep
# 停止 MariaDB
mysqladmin -u root -p shutdown
# 或
pkill mariadbd
# 確認停止
ps aux | grep mariadb | grep -v grep || echo "MariaDB 已停止"
```
---
### Step 2: 卸載 MariaDB
```bash
# 卸載 MariaDB
brew uninstall mariadb
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.mariadb.plist
sudo rm /Library/LaunchDaemons/com.momentry.mariadb.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/mariadb
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/mariadb.log
rm -f /Users/accusys/momentry/log/mariadb.error.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== MariaDB 卸載後檢查 ==="
# 1. 檢查 MariaDB 進程
echo "1. MariaDB 進程:"
ps aux | grep mariadb | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 3306
echo "2. Port 3306:"
lsof -i :3306 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. mariadb 命令
echo "3. mariadb 命令:"
which mariadb > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list mariadb > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep mariadb > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 數據目錄 (可選刪除)
echo "6. 數據目錄:"
[ -d "/Users/accusys/momentry/var/mariadb" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 日誌目錄 (可選刪除)
echo "7. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== MariaDB 卸載後檢查 ===
1. MariaDB 進程:
✓ 已停止
2. Port 3306:
✓ 已釋放
3. mariadb 命令:
✓ 已移除
4. brew 安裝:
✓ 已移除
5. launchctl 服務:
✓ 已移除
6. 數據目錄:
✓ 保留 (或 ✗ 已刪除)
7. 日誌目錄:
✓ 保留 (或 ✗ 已刪除)
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep mariadb | grep -v grep
# 2. 檢查 Port
lsof -i :3306
# 3. 測試連線
mariadb -u root -e "SELECT 1;"
# 4. 查看所有數據庫
mariadb -u root -e "SHOW DATABASES;"
# 5. 查看用戶
mariadb -u root -e "SELECT User, Host FROM mysql.user;"
# 6. 查看表
mariadb -u root -e "USE mysql; SHOW TABLES;"
# 7. 查看日誌
tail -20 /Users/accusys/momentry/log/mariadb.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| Host | localhost |
| Port | 3306 |
| User | root |
---
## 環境變數
`.env` 中:
```env
MARIADB_URL=mariadb://root@localhost:3306
```
---
## 遠端訪問
- MariaDB 綁定到所有網路介面 (0.0.0.0)
- 本地網路其他機器可透過 IP 訪問
- 請設定用戶權限限制訪問
---
## 故障排除
### MariaDB 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/mariadb.log
# 檢查目錄權限
ls -la /Users/accusys/momentry/var/mariadb/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/mariadb
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 3306
lsof -i :3306
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.mariadb.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.mariadb.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/mariadb/` | MariaDB 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/mariadb/bin/mariadbd` | MariaDB 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/mariadb/` | 數據儲存 |
| 日誌 | `/Users/accusys/momentry/log/mariadb.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/mariadb.error.log` | 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.mariadb.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/mariadb_backup/` | 數據備份 |
---
## 備份與恢復
### 備份用戶配置
已創建專用備份用戶:
- 用戶名:`momentry_backup`
- 密碼:`momentry_backup_pwd_2026`
- 權限:SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER
### 備份 (mysqldump)
```bash
# 備份所有數據庫
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mysqldump -u momentry_backup -pmomentry_backup_pwd_2026 --all-databases | gzip > \
/Users/accusys/momentry/backup/daily/mariadb/mariadb_db_all_${TIMESTAMP}.sql.gz
# 備份指定數據庫 (WordPress)
mysqldump -u momentry_backup -pmomentry_backup_pwd_2026 wordpress | gzip > \
/Users/accusys/momentry/backup/daily/mariadb/mariadb_db_wordpress_${TIMESTAMP}.sql.gz
# 驗證
sha256sum /Users/accusys/momentry/backup/daily/mariadb/mariadb_db_*.sql.gz > \
/Users/accusys/momentry/backup/daily/mariadb/mariadb_db_${TIMESTAMP}.sha256
```
### 恢復 (mysql)
```bash
# 恢復所有數據庫
gunzip < /Users/accusys/momentry/backup/daily/mariadb/mariadb_db_all_20260316_101802.sql.gz | \
mysql -u momentry_backup -pmomentry_backup_pwd_2026
# 恢復指定數據庫
gunzip < /Users/accusys/momentry/backup/daily/mariadb/mariadb_db_wordpress_20260316_101802.sql.gz | \
mysql -u momentry_backup -pmomentry_backup_pwd_2026 wordpress
```
### 數據目錄複製 (完整遷移) - 離線
```bash
# 1. 停止 MariaDB
mysqladmin -u momentry_backup -pmomentry_backup_pwd_2026 shutdown
# 2. 複製數據目錄
cp -r /opt/homebrew/var/mysql/* /Users/accusys/momentry/var/mariadb/
# 3. 設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/mariadb
# 4. 啟動 MariaDB
sudo launchctl load /Library/LaunchDaemons/com.momentry.mariadb.plist
```
---
## 版本資訊
- 版本: 12.1.2
- Port: 3306
- User: root
- 數據目錄: /Users/accusys/momentry/var/mariadb/
- 日誌目錄: /Users/accusys/momentry/log/
-367
View File
@@ -1,367 +0,0 @@
# MongoDB 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 MongoDB Community Edition,配置為本地部署,支援遠端訪問。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| MongoDB (mongodb-community) | ✅ 已安裝 v8.2.6 |
| 數據目錄 | 保留 (/Users/accusys/momentry/var) - 共用 |
| 日誌目錄 | 保留 (/Users/accusys/momentry/log) - 共用 |
---
## 安裝步驟
### Step 1: 安裝 MongoDB Community
```bash
# 安裝 MongoDB Community
brew tap mongodb/brew
brew install mongodb-community
```
**驗證**:
```bash
mongod --version
# db version v8.x.x
mongosh --version
# 2.7.x
sudo launchctl list | grep mongo
# 確認 MongoDB 服務已載入
```
---
### Step 2: 數據目錄 (已存在 - 共用)
數據目錄已存在,無需建立:
- 數據目錄: `/Users/accusys/momentry/var`
- 配置目錄: `/Users/accusys/momentry/etc/mongodb`
- 日誌目錄: `/Users/accusys/momentry/log`
**建立配置目錄和日誌文件**:
```bash
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/mongodb
# 建立日誌文件
touch /Users/accusys/momentry/log/mongodb.log
touch /Users/accusys/momentry/log/mongodb.error.log
# 確認權限:
ls -la /Users/accusys/momentry/
chown -R accusys:staff /Users/accusys/momentry
```
---
### Step 3: 啟動 MongoDB (後台執行)
```bash
nohup /opt/homebrew/bin/mongod \
--dbpath /Users/accusys/momentry/var \
--logpath /Users/accusys/momentry/log/mongodb.log \
--port 27017 \
--bind_ip 0.0.0.0 \
> /Users/accusys/momentry/log/mongodb.log 2>&1 &
```
---
### Step 4: 建立資料庫用戶
```bash
mongosh --eval '
use admin
db.createUser({
user: "accusys",
pwd: "Test3200Test3200",
roles: [
{ role: "readWrite", db: "momentry" },
{ role: "dbAdmin", db: "momentry" }
]
})
---
### Step 5: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.mongodb.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
database:
mongodb:
enabled: true
host: "localhost"
port: 27017
user: "accusys"
database: "momentry"
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/` | 共用 | **不要刪除** - 多個系統共用 |
| `/Users/accusys/momentry/var` | 共用 | **不要刪除** - 數據目錄 |
| `/Users/accusys/momentry/etc/mongodb/` | 配置 | **不要刪除** - MongoDB 配置 |
| `/Users/accusys/momentry/log` | 共用 | **不要刪除** - 日誌目錄 |
| `~/.mongosh_history` | 專屬 | 可選刪除 - Mongo Shell 歷史 |
### Step 1: 停止 MongoDB
```bash
# 找到 MongoDB 進程
ps aux | grep mongod | grep -v grep
# 停止 MongoDB
pkill mongod
# 或
kill <PID>
# 確認停止
ps aux | grep mongod | grep -v grep || echo "MongoDB 已停止"
```
---
### Step 2: 卸載 MongoDB
```bash
# 完全卸載 (保留數據)
brew uninstall mongodb-community
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除 MongoDB 專屬配置 (如果有)
rm -f ~/.mongosh_history
# 刪除臨時文件 (可選)
rm -rf /tmp/mongodb-*
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== MongoDB 卸載後檢查 ==="
# 1. 檢查 MongoDB 進程
echo "1. MongoDB 進程:"
ps aux | grep mongod | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. 檢查 Port 27017
echo "2. Port 27017:"
lsof -i :27017 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. 檢查 mongod 命令
echo "3. mongod 命令:"
which mongod > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. 檢查 launchctl
echo "4. launchctl 服務:"
sudo launchctl list | grep mongo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. 檢查 Homebrew
echo "5. Homebrew 移除:"
brew list mongo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 檢查數據目錄 (應該存在)
echo "6. 數據目錄:"
[ -d "/Users/accusys/momentry/var" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 檢查日誌目錄 (應該存在)
echo "7. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== MongoDB 卸載後檢查 ===
1. MongoDB 進程:
✓ 已停止
2. Port 27017:
✓ 已釋放
3. mongod 命令:
✓ 已移除
4. launchctl 服務:
✓ 已移除
5. Homebrew 移除:
✓ 已移除
6. 數據目錄:
✓ 保留
7. 日誌目錄:
✓ 保留
```
---
## 手動檢查命令
```bash
# 1. 檢查 Process 是否運行
ps aux | grep mongo | grep -v grep
# 2. 檢查 Port 27017
lsof -i :27017
# 3. 測試連線 (無認證)
mongosh --eval "db.adminCommand('ping')"
# 4. 測試連線 (有認證)
mongosh "mongodb://accusys:Test3200Test3200@localhost:27017/admin" --eval "db.adminCommand('ping')"
# 5. 查看所有資料庫
mongosh "mongodb://accusys:Test3200Test3200@localhost:27017/admin" --quiet --eval "db.adminCommand({listDatabases:1}).databases"
# 6. 查看用戶
mongosh "mongodb://accusys:Test3200Test3200@localhost:27017/admin" --quiet --eval "db.getUser('accusys')"
# 7. 查看日誌
tail -20 /Users/accusys/momentry/log/mongodb.log
tail -20 /Users/accusys/momentry/log/mongodb.error.log
```
---
## 管理命令
### 啟動/停止
```bash
# 停止
pkill mongod
# 或
kill <PID>
# 啟動 (後台)
nohup /opt/homebrew/bin/mongod \
--dbpath /Users/accusys/momentry/var \
--logpath /Users/accusys/momentry/log/mongodb.log \
--port 27017 \
--bind_ip 0.0.0.0 \
> /Users/accusys/momentry/log/mongodb.log 2>&1 &
# 使用 plist (開機自動啟動)
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
```
---
## 連線字串
```bash
# 無認證 (本地)
mongodb://localhost:27017
# 有認證 (admin 資料庫)
mongodb://accusys:Test3200Test3200@localhost:27017/admin
# 有認證 (momentry 資料庫)
mongodb://accusys:Test3200Test3200@localhost:27017/momentry?authSource=admin
```
---
## 環境變數
`.env` 中:
```env
MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
MONGODB_DATABASE=momentry
```
---
## 遠端訪問
- MongoDB 綁定到 `0.0.0.0` (所有網路介面)
- 本地網路其他機器可透過 IP 訪問
- 建議設定防火牆規則限制訪問 IP
---
## 故障排除
### MongoDB 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/mongodb.log
# 檢查目錄權限
ls -la /Users/accusys/momentry/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 27017
lsof -i :27017
# 終止佔用程序
kill <PID>
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 數據目錄 | `/Users/accusys/momentry/var` | **共用 - 不要刪除** |
| 日誌目錄 | `/Users/accusys/momentry/log` | **共用 - 不要刪除** |
| mongod | `/opt/homebrew/bin/mongod` | 安裝後存在 |
| Homebrew | `/opt/homebrew/Cellar/mongodb-community/` | 卸載時刪除 |
| Homebrew | `/opt/homebrew/Cellar/mongodb-database-tools/` | 卸載時刪除 |
| Homebrew | `/opt/homebrew/Cellar/mongosh/` | 卸載時刪除 |
| 配置檔 | `/opt/homebrew/etc/mongod.conf` | 卸載時刪除 |
---
## 版本資訊
- 用戶: accusys
- 密碼: Test3200Test3200
- 數據目錄: /Users/accusys/momentry/var (共用 - 不要刪除!)
- 日誌目錄: /Users/accusys/momentry/log (共用 - 不要刪除!)
-466
View File
@@ -1,466 +0,0 @@
# n8n 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 n8n 工作流自動化平台,配置為本地部署,使用 Queue 模式。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| n8n | ✅ 已安裝 v2.3.5 |
| 數據目錄 | /Users/accusys/momentry/var/n8n/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Main Plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist |
| Worker Plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist |
| 數據庫 | PostgreSQL (n8n) |
| 隊列 | Redis |
---
## 安裝步驟
### Step 1: 安裝 n8n (使用 brew)
```bash
# 安裝 n8n
brew install n8n
```
**驗證**:
```bash
n8n --version
# 2.3.5
```
---
### Step 2: 建立目錄
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/n8n
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/n8n
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/n8n.main.log
touch /Users/accusys/momentry/log/n8n.main.error.log
touch /Users/accusys/momentry/log/n8n.worker.log
touch /Users/accusys/momentry/log/n8n.worker.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/n8n
chown -R accusys:staff /Users/accusys/momentry/etc/n8n
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 3: 數據遷移 (如果從舊位置遷移)
```bash
# 停止舊服務
sudo launchctl unload /Library/LaunchDaemons/com.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.n8n.worker.plist
# 複製數據
cp -r /Users/accusys/.n8n/* /Users/accusys/momentry/var/n8n/
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/n8n
```
---
### Step 4: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.n8n.main.plist /Library/LaunchDaemons/
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.n8n.worker.plist /Library/LaunchDaemons/
# 移除舊 plist (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.n8n.main.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.n8n.worker.plist 2>/dev/null
sudo rm /Library/LaunchDaemons/com.n8n.main.plist 2>/dev/null
sudo rm /Library/LaunchDaemons/com.n8n.worker.plist 2>/dev/null
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "n8n"
type: "http"
port: 5678
host: "localhost"
check_url: "http://localhost:5678/"
timeout: 5
enabled: true
```
### 添加健康檢查函數
`monitor/service/health_check.sh` 中添加:
```bash
check_n8n() {
local start=$(date +%s%N)
if curl -s http://localhost:5678/ > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} n8n (5678) - ${ms}ms"
record_service "n8n" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} n8n (5678) - Down"
record_service "n8n" "down" "0" "Connection failed"
return 1
fi
}
```
### n8n Workflow 監控
n8n 有專門的工作流監控腳本,請參考 `monitor/workflow/n8n_workflow_monitor.sh`
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/n8n/` | 數據 | **不要刪除** - n8n 數據 |
| `/Users/accusys/momentry/etc/n8n/` | 配置 | **不要刪除** - n8n 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - n8n 日誌 |
| `/opt/homebrew/lib/node_modules/n8n/` | 安裝 | **刪除** - n8n 安裝目錄 |
### Step 1: 停止 n8n
```bash
# 停止 n8n 服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
# 確認停止
ps aux | grep n8n | grep -v grep || echo "n8n 已停止"
```
---
### Step 2: 卸載 n8n
```bash
# 卸載 n8n
brew uninstall n8n
# 移除 plist
sudo rm /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo rm /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/n8n
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/n8n-*.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/log
# PostgreSQL n8n 數據庫 (如需保留)
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== n8n 卸載後檢查 ==="
# 1. 檢查 n8n 進程
echo "1. n8n Main 進程:"
ps aux | grep "n8n.*start" | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
echo "2. n8n Worker 進程:"
ps aux | grep "n8n.*worker" | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 8085
echo "3. Port 8085:"
lsof -i :8085 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. Port 5690-5691
echo "4. Port 5690-5691:"
lsof -i :5690 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 4. n8n 命令
echo "5. n8n 命令:"
which n8n > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. brew 安裝
echo "6. brew 安裝:"
brew list n8n > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. launchctl 服務
echo "7. launchctl 服務:"
sudo launchctl list | grep n8n > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
```
---
## 備份步驟
### 備份 n8n 數據
```bash
# 建立備份目錄
mkdir -p /Users/accusys/momentry/var/n8n_backup
# 1. 備份 PostgreSQL 數據庫
PGPASSWORD=accusys pg_dump -U accusys -d n8n > /Users/accusys/momentry/var/n8n_backup/n8n_database_$(date +%Y%m%d).sql
# 2. 備份用戶數據夾
cp -r /Users/accusys/momentry/var/n8n /Users/accusys/momentry/var/n8n_backup/
# 3. 壓縮備份
cd /Users/accusys/momentry/var && tar -czvf n8n_backup_$(date +%Y%m%d).tar.gz n8n_backup/
# 4. 清理臨時備份
rm -rf /Users/accusys/momentry/var/n8n_backup
```
### 還原數據
```bash
# 1. 停止 n8n
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
# 2. 還原 PostgreSQL 數據庫
PGPASSWORD=accusys psql -U accusys -d n8n < /Users/accusys/momentry/var/n8n_backup/n8n_database_20260314.sql
# 3. 還原用戶數據夾
rm -rf /Users/accusys/momentry/var/n8n
cp -r /Users/accusys/momentry/var/n8n_backup/n8n /Users/accusys/momentry/var/n8n
chown -R accusys:staff /Users/accusys/momentry/var/n8n
# 4. 啟動 n8n
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep n8n | grep -v grep
# 2. 檢查 Port
lsof -i :5678
lsof -i :5690
lsof -i :5691
# 3. 測試連線
curl http://localhost:5678/
# 4. 查看版本
n8n --version
# 5. 查看日誌
tail -20 /Users/accusys/momentry/log/n8n-main.log
tail -20 /Users/accusys/momentry/log/n8n-worker.log
# 6. 查看錯誤日誌
tail -20 /Users/accusys/momentry/log/n8n-main.error.log
tail -20 /Users/accusys/momentry/log/n8n-worker.error.log
# 7. 檢查 launchctl 狀態
sudo launchctl list | grep n8n
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| URL | http://localhost:5678 |
| Domain | n8n.momentry.ddns.net |
| 數據庫 | PostgreSQL (n8n) |
| 隊列 | Redis |
| Encryption Key | Test3200Test3200Test3200 |
### Queue 模式端口
| 服務 | Port |
|------|------|
| Main | 5678 |
| Worker Broker | 5690 |
| Worker Health Check | 5691 |
---
## 環境變數
`.env` 中:
```env
N8N_URL=http://localhost:5678
N8N_USER_FOLDER=/Users/accusys/momentry/var/n8n
```
---
## 故障排除
### n8n 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/n8n-main.log
tail -f /Users/accusys/momentry/log/n8n-worker.log
# 檢查環境變數
launchctl list | grep n8n
# 檢查數據庫連線
PGPASSWORD=accusys psql -U accusys -d n8n -c "SELECT 1"
# 檢查 Redis 連線
redis-cli -a accusys ping
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port
lsof -i :8085
# 終止佔用程序
kill <PID>
```
### 數據庫連線失敗
```bash
# 檢查 PostgreSQL
pg_isready -h 127.0.0.1 -p 5432 -U n8n
# 測試連線
PGPASSWORD=accusys psql -h 127.0.0.1 -U n8n -d n8n -c "SELECT version();"
```
### 需要重新載入 plist
```bash
# 卸載舊服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/lib/node_modules/n8n/` | n8n 安裝目錄 |
| 執行檔 | `/opt/homebrew/bin/n8n` | n8n 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/n8n/` | 數據儲存 |
| 配置目錄 | `/Users/accusys/momentry/etc/n8n/` | 配置儲存 |
| Main 日誌 | `/Users/accusys/momentry/log/n8n.main.log` | 主服務日誌 |
| Main 錯誤日誌 | `/Users/accusys/momentry/log/n8n.main.error.log` | 主服務錯誤日誌 |
| Worker 日誌 | `/Users/accusys/momentry/log/n8n.worker.log` | Worker 日誌 |
| Worker 錯誤日誌 | `/Users/accusys/momentry/log/n8n.worker.error.log` | Worker 錯誤日誌 |
| Main Plist | `/Library/LaunchDaemons/com.momentry.n8n.main.plist` | 主服務開機啟動 |
| Worker Plist | `/Library/LaunchDaemons/com.momentry.n8n.worker.plist` | Worker 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/n8n_backup/` | 數據備份 |
---
## 數據庫資訊
n8n 使用 PostgreSQL 作為數據庫:
| 項目 | 值 |
|------|-----|
| Database | n8n |
| User | n8n |
| Host | 127.0.0.1:5432 |
| Password | accusys |
### Redis 隊列資訊
| 項目 | 值 |
|------|-----|
| Host | 127.0.0.1:6379 |
| Password | accusys |
| Mode | Queue (Bull) |
---
## 常用指令
```bash
# 啟動 n8n Main
n8n start
# 啟動 n8n Worker
n8n worker
# 查看版本
n8n --version
# 備份數據
PGPASSWORD=accusys pg_dump -U accusys -d n8n > n8n_backup.sql
# 重新載入服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
```
---
## 版本資訊
- 版本: 2.3.5
- Main Port: 5678
- Worker Ports: 5690-5691
- 數據目錄: /Users/accusys/momentry/var/n8n/
- 日誌目錄: /Users/accusys/momentry/log/
- 數據庫: PostgreSQL n8n
- 隊列: Redis
-359
View File
@@ -1,359 +0,0 @@
# Ollama 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 Ollama,配置為本地部署,用於運行大型語言模型 (LLM)。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Ollama | ✅ 已安裝 v0.13.5 |
| Port | 11434 |
| Models 目錄 | /Users/accusys/.ollama/models/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.ollama.plist |
---
## 安裝步驟
### Step 1: 安裝 Ollama (使用 brew)
```bash
# 安裝 Ollama
brew install ollama
```
**驗證**:
```bash
ollama --version
# ollama version is 0.13.5
```
---
### Step 2: 建立目錄
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/ollama
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/ollama
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/ollama.log
touch /Users/accusys/momentry/log/ollama.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/ollama
chown -R accusys:staff /Users/accusys/momentry/etc/ollama
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 3: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.ollama.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "ollama"
type: "http"
port: 11434
host: "localhost"
check_url: "http://localhost:11434/api/tags"
timeout: 5
enabled: true
```
### 添加健康檢查函數
`monitor/service/health_check.sh` 中添加:
```bash
check_ollama() {
local start=$(date +%s%N)
if curl -s http://localhost:11434/api/tags > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} Ollama (11434) - ${ms}ms"
record_service "ollama" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Ollama (11434) - Down"
record_service "ollama" "down" "0" "Connection failed"
return 1
fi
}
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/ollama/` | 數據 | **不要刪除** - Ollama 數據 |
| `/Users/accusys/momentry/etc/ollama/` | 配置 | **不要刪除** - Ollama 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/opt/ollama/` | 安裝 | **刪除** - Ollama 安裝目錄 |
### Step 1: 停止 Ollama
```bash
# 找到 Ollama 進程
ps aux | grep ollama | grep -v grep
# 停止 Ollama
pkill ollama
# 確認停止
ps aux | grep ollama | grep -v grep || echo "Ollama 已停止"
```
---
### Step 2: 卸載 Ollama
```bash
# 卸載 Ollama
brew uninstall ollama
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.ollama.plist
sudo rm /Library/LaunchDaemons/com.momentry.ollama.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/ollama.log
rm -f /Users/accusys/momentry/log/ollama.error.log
```
**注意: 不要刪除以下目錄**:
```bash
# 這些是重要的,不要刪除!
# /Users/accusys/.ollama/models
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== Ollama 卸載後檢查 ==="
# 1. 檢查 Ollama 進程
echo "1. Ollama 進程:"
ps aux | grep ollama | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 11434
echo "2. Port 11434:"
lsof -i :11434 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. ollama 命令
echo "3. ollama 命令:"
which ollama > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list ollama > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep ollama > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 模型目錄 (應該保留)
echo "6. 模型目錄:"
[ -d "/Users/accusys/.ollama/models" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep ollama | grep -v grep
# 2. 檢查 Port
lsof -i :11434
# 3. 測試連線
curl http://localhost:11434/
# 4. 查看版本
ollama --version
# 5. 查看已安裝的模型
ollama list
# 6. 查看日誌
tail -20 /Users/accusys/momentry/log/ollama.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| Host | localhost |
| Port | 11434 |
| Models | /Users/accusys/.ollama/models |
---
## 環境變數
`.env` 中:
```env
OLLAMA_HOST=0.0.0.0:11434
OLLAMA_MODELS=/Users/accusys/.ollama/models
OLLAMA_FLASH_ATTENTION=1
OLLAMA_KV_CACHE_TYPE=q8_0
```
### 環境變數說明
| 變數 | 說明 | 預設值 |
|------|------|---------|
| OLLAMA_HOST | 綁定主機和端口 | 127.0.0.1:11434 |
| OLLAMA_MODELS | 模型儲存目錄 | ~/.ollama/models |
| OLLAMA_FLASH_ATTENTION | 啟用 Flash Attention | 0 |
| OLLAMA_KV_CACHE_TYPE | KV 緩存類型 | f16 |
---
## 遠端訪問
- Ollama 綁定到 `0.0.0.0:11434` (所有網路介面)
- 本地網路其他機器可透過 IP 訪問
- 請注意安全風險
---
## 故障排除
### Ollama 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/ollama.log
# 檢查模型目錄權限
ls -la /Users/accusys/.ollama/models/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/.ollama
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 11434
lsof -i :11434
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.ollama.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/ollama/` | Ollama 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/ollama/bin/ollama` | Ollama 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/ollama/` | 數據儲存 |
| 配置目錄 | `/Users/accusys/momentry/etc/ollama/` | 配置儲存 |
| 模型目錄 | `/Users/accusys/.ollama/models/` | AI 模型儲存 |
| 日誌 | `/Users/accusys/momentry/log/ollama.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/ollama.error.log` | 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.ollama.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/ollama_backup/environment.txt` | 環境變數備份 |
---
## 常用指令
```bash
# 查看版本
ollama --version
# 查看已安裝的模型
ollama list
# 拉取模型
ollama pull mistral
ollama pull llama2
# 運行模型
ollama run mistral
# 刪除模型
ollama remove mistral
# 查看模型資訊
ollama show mistral
```
---
## 已安裝的模型
查看已安裝的模型:
```bash
ollama list
```
---
## 版本資訊
- 版本: 0.13.5
- Port: 11434
- Models: /Users/accusys/.ollama/models/
- 日誌目錄: /Users/accusys/momentry/log/
-379
View File
@@ -1,379 +0,0 @@
# PHP 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 PHP 及 PHP-FPM,配置為本地部署。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| PHP | ✅ 已安裝 v8.5.2 |
| PHP-FPM | ✅ 執行中 |
| 配置目錄 | /Users/accusys/momentry/etc/php/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.php.plist |
---
## 安裝步驟
### Step 1: 安裝 PHP (使用 brew)
```bash
# 安裝 PHP
brew install php
# 安裝 PHP-FPM (通常包含在 php 中)
brew install php --fpm
```
**驗證**:
```bash
php --version
# PHP 8.5.2 (cli)
```
---
### Step 2: 建立目錄
```bash
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/php
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/php.log
touch /Users/accusys/momentry/log/php.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/etc/php
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 3: 建立設定檔
建立 `/Users/accusys/momentry/etc/php/php-fpm.conf`:
```ini
[global]
pid = /Users/accusys/momentry/var/php-fpm.pid
error_log = /Users/accusys/momentry/log/php.error.log
log_level = notice
[www]
user = accusys
group = staff
listen = 127.0.0.1:9000
listen.owner = accusys
listen.group = staff
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
```
複製 php.ini:
```bash
cp /opt/homebrew/etc/php/8.5/php.ini /Users/accusys/momentry/etc/php/
```
---
### Step 4: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.php.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.php.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "php-fpm"
type: "tcp"
port: 9000
host: "localhost"
timeout: 5
enabled: true
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/etc/php/` | 配置 | **不要刪除** - PHP 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/opt/php/` | 安裝 | **刪除** - PHP 安裝目錄 |
### Step 1: 停止 PHP-FPM
```bash
# 找到 PHP-FPM 進程
ps aux | grep php-fpm | grep -v grep
# 停止 PHP-FPM
pkill php-fpm
# 確認停止
ps aux | grep php-fpm | grep -v grep || echo "PHP-FPM 已停止"
```
---
### Step 2: 卸載 PHP
```bash
# 卸載 PHP
brew uninstall php
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.php.plist
sudo rm /Library/LaunchDaemons/com.momentry.php.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除配置目錄 (可選)
rm -rf /Users/accusys/momentry/etc/php
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/php.log
rm -f /Users/accusys/momentry/log/php.error.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/etc
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== PHP 卸載後檢查 ==="
# 1. 檢查 PHP-FPM 進程
echo "1. PHP-FPM 進程:"
ps aux | grep php-fpm | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 9000
echo "2. Port 9000:"
lsof -i :9000 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. php 命令
echo "3. php 命令:"
which php > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list php > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep php > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 配置目錄 (可選刪除)
echo "6. 配置目錄:"
[ -d "/Users/accusys/momentry/etc/php" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 日誌目錄 (可選刪除)
echo "7. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
---
## 手動檢查命令
```bash
# 1. 檢查 PHP 版本
php --version
# 2. 檢查 PHP-FPM 進程
ps aux | grep php-fpm | grep -v grep
# 3. 檢查 Port
lsof -i :9000
# 4. 測試 PHP
php -r "echo 'PHP OK' . PHP_EOL;"
# 5. 查看 PHP 模組
php -m
# 6. 查看 PHP 配置
php -i | grep "Loaded Configuration File"
# 7. 查看日誌
tail -20 /Users/accusys/momentry/log/php.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| PHP-FPM Port | 9000 |
| PHP Version | 8.5.2 |
| Config | /Users/accusys/momentry/etc/php/php-fpm.conf |
| php.ini | /Users/accusys/momentry/etc/php/php.ini |
---
## 環境變數
`.env` 中:
```env
PHP_INI_SCAN_DIR=/Users/accusys/momentry/etc/php/conf.d
```
---
## 故障排除
### PHP-FPM 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/php.error.log
# 檢查配置語法
/opt/homebrew/opt/php/sbin/php-fpm --test --fpm-config /Users/accusys/momentry/etc/php/php-fpm.conf
# 檢查目錄權限
ls -la /Users/accusys/momentry/etc/php/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/etc/php
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 9000
lsof -i :9000
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.php.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.php.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/php/` | PHP 安裝目錄 |
| 執行檔 | `/opt/homebrew/bin/php` | PHP 執行檔 |
| PHP-FPM | `/opt/homebrew/opt/php/sbin/php-fpm` | PHP-FPM 執行檔 |
| php.ini | `/Users/accusys/momentry/etc/php/8.5/php.ini` | PHP 配置 |
| PHP-FPM 配置 | `/Users/accusys/momentry/etc/php/8.5/php-fpm.conf` | PHP-FPM 主配置 |
| PHP-FPM pool | `/Users/accusys/momentry/etc/php/8.5/php-fpm.d/` | Pool 配置 |
| 日誌 | `/Users/accusys/momentry/log/php.log` | 執行日誌 |
| 錯誤日誌 | `/opt/homebrew/var/log/php-fpm.log` | PHP-FPM 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.php.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/backup/daily/php/` | 配置備份 |
---
## 常用指令
```bash
# 測試 PHP-FPM 配置
/opt/homebrew/opt/php/sbin/php-fpm --test --fpm-config /Users/accusys/momentry/etc/php/php-fpm.conf
# 查看 PHP 模組
php -m
# 查看已載入的配置
php -i
# 測試 PHP 腳本
php -r "echo 'Hello World' . PHP_EOL;"
# 查看 PHP-FPM 狀態
curl http://127.0.0.1:9000/status
```
---
## 備份與恢復
### 備份
```bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/Users/accusys/momentry/backup/daily/php"
mkdir -p "$BACKUP_DIR"
# 備份配置 (注意:PHP-FPM 實際使用 /Users/accusys/momentry/etc/php/8.5/ 配置)
tar -czf "$BACKUP_DIR/php_cfg_${TIMESTAMP}.tar.gz" \
/Users/accusys/momentry/etc/php/8.5/php.ini \
/Users/accusys/momentry/etc/php/8.5/php-fpm.conf \
/Users/accusys/momentry/etc/php/8.5/php-fpm.d/
# 驗證
sha256sum "$BACKUP_DIR/php_cfg_${TIMESTAMP}.tar.gz" > "$BACKUP_DIR/php_${TIMESTAMP}.sha256"
```
### 恢復
```bash
# 解壓配置
tar -xzf /Users/accusys/momentry/backup/daily/php/php_cfg_20260316_102727.tar.gz -C /
# 測試配置
/opt/homebrew/opt/php/sbin/php-fpm --test --fpm-config /Users/accusys/momentry/etc/php/8.5/php-fpm.conf
```
---
## 版本資訊
- PHP Version: 8.5.2
- PHP-FPM Port: 9000
- 配置目錄: /Users/accusys/momentry/etc/php/
- 日誌目錄: /Users/accusys/momentry/log/
-372
View File
@@ -1,372 +0,0 @@
# PostgreSQL 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 PostgreSQL,配置為本地部署,支援遠端訪問。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| PostgreSQL | ✅ 已安裝 v18.1 |
| 數據目錄 | /Users/accusys/momentry/var/postgresql/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.postgresql.plist |
---
## 安裝步驟
### Step 1: 安裝 PostgreSQL (使用 brew)
```bash
# 安裝 PostgreSQL
brew install postgresql@18
```
**驗證**:
```bash
postgres --version
# postgres (PostgreSQL) 18.1
```
---
### Step 2: 建立目錄結構
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/postgresql
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/postgresql
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/postgresql.log
touch /Users/accusys/momentry/log/postgresql.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/postgresql
chown -R accusys:staff /Users/accusys/momentry/etc/postgresql
chown -R accusys:staff /Users/accusys/momentry/log
```
**注意**: 如果需要從舊數據遷移,需要先初始化新目錄:
```bash
# 初始化新數據目錄 (會創建默認數據庫)
initdb -D /Users/accusys/momentry/var/postgresql -U accusys
```
---
### Step 3: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.postgresql.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
database:
postgresql:
enabled: true
host: "localhost"
port: 5432
user: "accusys"
database: "momentry"
```
### 添加健康檢查函數
`monitor/database/postgres_monitor.sh` 中已包含 PostgreSQL 監控。
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/postgresql/` | 數據 | **不要刪除** - 數據目錄 |
| `/Users/accusys/momentry/etc/postgresql/` | 配置 | **不要刪除** - 配置目錄 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/opt/postgresql@18/` | 安裝 | **刪除** - PostgreSQL 安裝目錄 |
### Step 1: 停止 PostgreSQL
```bash
# 找到 PostgreSQL 進程
ps aux | grep postgres | grep -v grep
# 停止 PostgreSQL
pg_ctl -D /opt/homebrew/var/postgresql@18 stop
# 或
pkill -f postgresql
# 確認停止
ps aux | grep postgres | grep -v grep || echo "PostgreSQL 已停止"
```
---
### Step 2: 卸載 PostgreSQL
```bash
# 卸載 PostgreSQL
brew uninstall postgresql@18
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
sudo rm /Library/LaunchDaemons/com.momentry.postgresql.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/postgresql
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/postgresql.log
rm -f /Users/accusys/momentry/log/postgresql.error.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== PostgreSQL 卸載後檢查 ==="
# 1. 檢查 PostgreSQL 進程
echo "1. PostgreSQL 進程:"
ps aux | grep postgres | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 5432
echo "2. Port 5432:"
lsof -i :5432 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. postgres 命令
echo "3. postgres 命令:"
which postgres > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list postgresql@18 > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep postgresql > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 數據目錄 (可選刪除)
echo "6. 數據目錄:"
[ -d "/Users/accusys/momentry/var/postgresql" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 日誌目錄 (可選刪除)
echo "7. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== PostgreSQL 卸載後檢查 ===
1. PostgreSQL 進程:
✓ 已停止
2. Port 5432:
✓ 已釋放
3. postgres 命令:
✓ 已移除
4. brew 安裝:
✓ 已移除
5. launchctl 服務:
✓ 已移除
6. 數據目錄:
✓ 保留 (或 ✗ 已刪除)
7. 日誌目錄:
✓ 保留 (或 ✗ 已刪除)
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep postgres | grep -v grep
# 2. 檢查 Port
lsof -i :5432
# 3. 測試連線
psql -U accusys -l
# 4. 查看所有數據庫
psql -U accusys -c "\l"
# 5. 查看連接
psql -U accusys -c "\conninfo"
# 6. 查看表
psql -U accusys -d momentry -c "\dt"
# 7. 查看日誌
tail -20 /Users/accusys/momentry/log/postgresql.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| Host | localhost |
| Port | 5432 |
| User | accusys |
| Database | momentry, video_register, gitea, n8n |
---
## 環境變數
`.env` 中:
```env
POSTGRES_URL=postgresql://accusys@localhost:5432
POSTGRES_DB=momentry
```
---
## 遠端訪問
- PostgreSQL 綁定到所有網路介面 (0.0.0.0)
- 本地網路其他機器可透過 IP 訪問
- 請設定 `pg_hba.conf` 限制訪問 IP
---
## 故障排除
### PostgreSQL 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/postgresql.log
# 檢查目錄權限
ls -la /Users/accusys/momentry/var/postgresql/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/postgresql
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 5432
lsof -i :5432
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/postgresql@18/` | PostgreSQL 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/postgresql@18/bin/postgres` | PostgreSQL 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/postgresql/` | 數據儲存 |
| 日誌 | `/Users/accusys/momentry/log/postgresql.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/postgresql.error.log` | 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.postgresql.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/momentry_db_backup_latest.sql` | momentry 數據庫備份 |
| 備份 | `/Users/accusys/momentry/var/video_register_db_backup_latest.sql` | video_register 數據庫備份 |
---
## 備份與恢復
### 備份 (pg_dump)
```bash
# 備份 momentry 數據庫
pg_dump -U accusys momentry > /Users/accusys/momentry/var/momentry_db_backup_latest.sql
# 備份 video_register 數據庫
pg_dump -U accusys video_register > /Users/accusys/momentry/var/video_register_db_backup_latest.sql
```
### 恢復 (psql)
```bash
# 恢復 momentry 數據庫
psql -U accusys -d momentry < /Users/accusys/momentry/var/momentry_db_backup_latest.sql
# 恢復 video_register 數據庫
psql -U accusys -d video_register < /Users/accusys/momentry/var/video_register_db_backup_latest.sql
```
### 數據目錄複製 (完整遷移)
```bash
# 1. 停止 PostgreSQL
pg_ctl -D /Users/accusys/momentry/var/postgresql stop
# 2. 複製數據目錄
cp -r /opt/homebrew/var/postgresql@18/* /Users/accusys/momentry/var/postgresql/
# 3. 設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/postgresql
# 4. 啟動 PostgreSQL
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
```
---
## 版本資訊
- 版本: 18.1
- Port: 5432
- User: accusys
- 數據目錄: /Users/accusys/momentry/var/postgresql/
- 日誌目錄: /Users/accusys/momentry/log/
-456
View File
@@ -1,456 +0,0 @@
# Qdrant 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 Qdrant Vector Database,配置為本地部署,支援遠端訪問。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Qdrant | ✅ 已安裝 v1.17.0 |
| 數據目錄 | /Users/accusys/momentry/var/qdrant/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.qdrant.plist |
---
## 安裝步驟
### Step 1: 安裝 Qdrant (使用 cargo)
```bash
# 安裝 Qdrant 從 GitHub
cargo install --git https://github.com/qdrant/qdrant.git --locked
```
**驗證**:
```bash
qdrant --version
# qdrant 1.17.0
```
---
### Step 2: 驗證 Qdrant 安裝
```bash
# 驗證 Qdrant 安裝
~/.cargo/bin/qdrant --version
# qdrant 1.17.0
```
---
### Step 3: 建立目錄結構
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/qdrant
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/qdrant
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/qdrant.log
touch /Users/accusys/momentry/log/qdrant.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/qdrant
chown -R accusys:staff /Users/accusys/momentry/etc/qdrant
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 4: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.qdrant.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.qdrant.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
database:
qdrant:
enabled: true
host: "localhost"
port: 6333
```
### 添加健康檢查函數
`monitor/database/qdrant_monitor.sh` 中已包含 Qdrant 監控。
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/qdrant/` | 數據 | **不要刪除** - Qdrant 數據 |
| `/Users/accusys/momentry/etc/qdrant/` | 配置 | **不要刪除** - Qdrant 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `~/.cargo/bin/qdrant` | 安裝 | **刪除** - Qdrant 執行檔 |
### Step 1: 停止 Qdrant
```bash
# 找到 Qdrant 進程
ps aux | grep qdrant | grep -v grep
# 停止 Qdrant
pkill qdrant
# 或
kill <PID>
# 確認停止
ps aux | grep qdrant | grep -v grep || echo "Qdrant 已停止"
```
---
### Step 2: 卸載 Qdrant (cargo)
```bash
# 移除 cargo 安裝的 Qdrant
cargo uninstall qdrant
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.qdrant.plist
sudo rm /Library/LaunchDaemons/com.momentry.qdrant.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/qdrant
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/qdrant.log
rm -f /opt/homebrew/var/log/qdrant.error.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== Qdrant 卸載後檢查 ==="
# 1. 檢查 Qdrant 進程
echo "1. Qdrant 進程:"
ps aux | grep qdrant | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 6333
echo "2. Port 6333:"
lsof -i :6333 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. Port 6334
echo "3. Port 6334:"
lsof -i :6334 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 4. qdrant 命令
echo "4. qdrant 命令:"
which qdrant > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. cargo 安裝
echo "5. cargo 安裝:"
cargo install --list | grep qdrant > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. launchctl 服務
echo "6. launchctl 服務:"
sudo launchctl list | grep qdrant > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 7. 數據目錄 (可選刪除)
echo "7. 數據目錄:"
[ -d "/Users/accusys/momentry/var/qdrant" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 8. 日誌目錄 (可選刪除)
echo "8. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== Qdrant 卸載後檢查 ===
1. Qdrant 進程:
✓ 已停止
2. Port 6333:
✓ 已釋放
3. Port 6334:
✓ 已釋放
4. qdrant 命令:
✓ 已移除
5. cargo 安裝:
✓ 已移除
6. launchctl 服務:
✓ 已移除
7. 數據目錄:
✓ 保留 (或 ✗ 已刪除)
8. 日誌目錄:
✓ 保留 (或 ✗ 已刪除)
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep qdrant | grep -v grep
# 2. 檢查 Port
lsof -i :6333
lsof -i :6334
# 3. 測試連線 (無認證)
curl http://localhost:6333/collections
# 4. 測試連線 (有認證)
curl -H "api-key: Test3200Test3200Test3200" http://localhost:6333/collections
# 5. 查看所有 collections
curl -s -H "api-key: Test3200Test3200Test3200" http://localhost:6333/collections
# 6. 查看日誌
tail -20 /Users/accusys/momentry/log/qdrant.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| REST API | http://localhost:6333 |
| gRPC | localhost:6334 |
| API Key | Test3200Test3200Test3200 |
| Qdrant UI | http://localhost:6333/dashboard |
---
## 環境變數
`.env` 中:
```env
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=Test3200Test3200Test3200
```
---
## 遠端訪問
- Qdrant 綁定到 `0.0.0.0` (所有網路介面)
- 本地網路其他機器可透過 IP 訪問
- 建議設定防火牆規則限制訪問 IP
---
## 故障排除
### Qdrant 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/qdrant.log
# 檢查目錄權限
ls -la /Users/accusys/momentry/var/qdrant/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/qdrant
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 6333
lsof -i :6333
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.qdrant.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.qdrant.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| `/Users/accusys/.cargo/bin/qdrant` | 二進制 | cargo 安裝位置 (直接使用) |
| `/opt/homebrew/bin/qdrant` | 符號連結 | ~~已棄用~~ - 不再需要 |
| 數據目錄 | `/Users/accusys/momentry/var/qdrant/` | 數據儲存 |
| 日誌 | `/Users/accusys/momentry/log/qdrant.log` | 執行日誌 |
| 錯誤日誌 | `/opt/homebrew/var/log/qdrant.error.log` | 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.qdrant.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/qdrant_backup/` | 數據備份 |
---
## 備份與恢復
### 備份
Qdrant 提供兩種備份方式:Snapshots (推薦) 和手動複製。
#### 方式一:使用 Snapshots API (推薦)
```bash
# 創建備份目錄
mkdir -p /Users/accusys/momentry/var/qdrant_backup
# 獲取所有 collections
curl -s -H "api-key: Test3200Test3200Test3200" \
http://localhost:6333/collections | jq -r '.result[].name'
# 為每個 collection 創建 snapshot
# 假設 collection 名稱為 "chunks"
curl -X POST -H "api-key: Test3200Test3200Test3200" \
http://localhost:6333/collections/chunks/snapshots \
-o /Users/accusys/momentry/var/qdrant_backup/chunks_snapshot_$(date +%Y%m%d).tar.gz
```
#### 方式二:手動複製數據目錄 (停機備份)
```bash
# 停止 Qdrant
pkill qdrant
# 等待停止
sleep 2
# 複製數據目錄
TIMESTAMP=$(date +%Y%m%d)
mkdir -p /Users/accusys/momentry/var/qdrant_backup
tar -czf /Users/accusys/momentry/var/qdrant_backup/qdrant_data_${TIMESTAMP}.tar.gz \
-C /Users/accusys/momentry/var qdrant/
# 啟動 Qdrant
launchctl load /Library/LaunchDaemons/com.momentry.qdrant.plist
```
#### 自動備份腳本
```bash
#!/bin/bash
# backup_qdrant.sh
set -e
QDRANT_HOST="localhost"
QDRANT_PORT="6333"
QDRANT_API_KEY="Test3200Test3200Test3200"
BACKUP_DIR="/Users/accusys/momentry/var/qdrant_backup"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
echo "開始 Qdrant 備份..."
# 獲取所有 collections
COLLECTIONS=$(curl -s -H "api-key: $QDRANT_API_KEY" \
http://${QDRANT_HOST}:${QDRANT_PORT}/collections | \
jq -r '.result[].name')
if [ -z "$COLLECTIONS" ]; then
echo "警告: 沒有找到任何 collections"
else
for COLLECTION in $COLLECTIONS; do
echo "備份 collection: $COLLECTION"
curl -X POST -H "api-key: $QDRANT_API_KEY" \
"http://${QDRANT_HOST}:${QDRANT_PORT}/collections/${COLLECTION}/snapshots" \
-o "${BACKUP_DIR}/${COLLECTION}_${TIMESTAMP}.tar.gz" 2>/dev/null || \
echo "警告: $COLLECTION 備份失敗"
done
fi
# 壓縮所有 snapshot
cd "$BACKUP_DIR"
tar -czf qdrant_snapshots_${TIMESTAMP}.tar.gz *.tar.gz 2>/dev/null || true
rm -f *.tar.gz
# 清理 30 天前的備份
find "$BACKUP_DIR" -name "qdrant_snapshots_*.tar.gz" -mtime +30 -delete
echo "Qdrant 備份完成: ${BACKUP_DIR}/qdrant_snapshots_${TIMESTAMP}.tar.gz"
```
### 恢復
```bash
# 停止 Qdrant
pkill qdrant
sleep 2
# 解壓縮備份
TIMESTAMP="20260315"
tar -xzf /Users/accusys/momentry/var/qdrant_backup/qdrant_snapshots_${TIMESTAMP}.tar.gz \
-C /Users/accusys/momentry/var/qdrant_backup/
# 恢復數據目錄 (方式二備份)
# rm -rf /Users/accusys/momentry/var/qdrant/*
# tar -xzf /Users/accusys/momentry/var/qdrant_backup/qdrant_data_${TIMESTAMP}.tar.gz \
# -C /Users/accusys/momentry/var/
# 啟動 Qdrant
launchctl load /Library/LaunchDaemons/com.momentry.qdrant.plist
```
### 排程備份
```bash
# 編輯 crontab
crontab -e
# 添加每天凌晨 3 點執行備份
0 3 * * * /Users/accusys/momentry/scripts/backup_qdrant.sh >> /Users/accusys/momentry/log/backup.log 2>&1
```
---
## 版本資訊
- 版本: 1.17.0
- API Key: Test3200Test3200Test3200
- 數據目錄: /Users/accusys/momentry/var/qdrant/
- 日誌目錄: /Users/accusys/momentry/log/
-367
View File
@@ -1,367 +0,0 @@
# Redis 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 Redis,配置為本地部署,支援遠端訪問。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Redis | ✅ 已安裝 v8.4.0 |
| 數據目錄 | /Users/accusys/momentry/var/redis/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.redis.plist |
---
## 安裝步驟
### Step 1: 安裝 Redis (使用 brew)
```bash
# 安裝 Redis
brew install redis
```
**驗證**:
```bash
redis-server --version
# Redis server v8.4.0
```
---
### Step 2: 建立目錄結構
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/redis
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/redis
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/redis.log
touch /Users/accusys/momentry/log/redis.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/redis
chown -R accusys:staff /Users/accusys/momentry/etc/redis
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 3: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.redis.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/redis/` | 數據 | **不要刪除** - 數據目錄 |
| `/Users/accusys/momentry/etc/redis/` | 配置 | **不要刪除** - 配置目錄 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/opt/redis/` | 安裝 | **刪除** - Redis 安裝目錄 |
### Step 1: 停止 Redis
```bash
# 找到 Redis 進程
ps aux | grep redis | grep -v grep
# 停止 Redis
redis-cli -a accusys SHUTDOWN
# 或
pkill redis-server
# 確認停止
ps aux | grep redis | grep -v grep || echo "Redis 已停止"
```
---
### Step 2: 卸載 Redis
```bash
# 卸載 Redis
brew uninstall redis
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
sudo rm /Library/LaunchDaemons/com.momentry.redis.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/redis
# 刪除配置目錄 (可選)
rm -rf /Users/accusys/momentry/etc/redis
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/redis.log
rm -f /Users/accusys/momentry/log/redis.error.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/etc
# /Users/accusys/momentry/log
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== Redis 卸載後檢查 ==="
# 1. 檢查 Redis 進程
echo "1. Redis 進程:"
ps aux | grep redis | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 6379
echo "2. Port 6379:"
lsof -i :6379 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. redis-server 命令
echo "3. redis-server 命令:"
which redis-server > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list redis > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep redis > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 數據目錄 (可選刪除)
echo "6. 數據目錄:"
[ -d "/Users/accusys/momentry/var/redis" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
# 7. 日誌目錄 (可選刪除)
echo "7. 日誌目錄:"
[ -d "/Users/accusys/momentry/log" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
**預期結果**:
```
=== Redis 卸載後檢查 ===
1. Redis 進程:
✓ 已停止
2. Port 6379:
✓ 已釋放
3. redis-server 命令:
✓ 已移除
4. brew 安裝:
✓ 已移除
5. launchctl 服務:
✓ 已移除
6. 數據目錄:
✓ 保留 (或 ✗ 已刪除)
7. 日誌目錄:
✓ 保留 (或 ✗ 已刪除)
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "redis"
type: "tcp"
port: 6379
host: "localhost"
timeout: 5
enabled: true
```
### 添加健康檢查函數
`monitor/service/health_check.sh` 中添加:
```bash
check_redis() {
local start=$(date +%s%N)
if redis-cli -a accusys ping > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} Redis (6379) - ${ms}ms"
record_service "redis" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Redis (6379) - Down"
record_service "redis" "down" "0" "Connection failed"
return 1
fi
}
```
```bash
# 1. 檢查進程
ps aux | grep redis | grep -v grep
# 2. 檢查 Port
lsof -i :6379
# 3. 測試連線 (無認證)
redis-cli -a accusys PING
# 4. 測試連線 (有認證)
redis-cli -a accusys -e "PING"
# 5. 查看所有 keys
redis-cli -a accusys KEYS '*'
# 6. 查看 info
redis-cli -a accusys INFO
# 7. 查看日誌
tail -20 /Users/accusys/momentry/log/redis.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| Host | localhost |
| Port | 6379 |
| Password | accusys |
---
## 環境變數
`.env` 中:
```env
REDIS_URL=redis://:accusys@localhost:6379
```
---
## 遠端訪問
- Redis 綁定到 `0.0.0.0` (所有網路介面)
- 本地網路其他機器可透過 IP 訪問
- 密碼認證: `accusys`
---
## 故障排除
### Redis 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/redis.log
# 檢查目錄權限
ls -la /Users/accusys/momentry/var/redis/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/redis
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port 6379
lsof -i :6379
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/redis/` | Redis 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/redis/bin/redis-server` | Redis 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/redis/` | 數據儲存 |
| 配置目錄 | `/Users/accusys/momentry/etc/redis/` | 配置儲存 |
| 日誌 | `/Users/accusys/momentry/log/redis.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/redis.error.log` | 錯誤日誌 |
| plist | `/Library/LaunchDaemons/com.momentry.redis.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/redis_backup_latest.rdb` | 數據備份 |
---
## 備份與恢復
### 備份
```bash
# 觸發保存並備份
redis-cli -a accusys SAVE
cp /opt/homebrew/var/db/redis/dump.rdb /Users/accusys/momentry/var/redis_backup_latest.rdb
```
### 恢復
```bash
# 停止 Redis
redis-cli -a accusys SHUTDOWN
# 複製備份文件覆蓋
cp /Users/accusys/momentry/var/redis_backup_latest.rdb /Users/accusys/momentry/var/redis/dump.rdb
# 啟動 Redis
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
---
## 版本資訊
- 版本: 8.4.0
- Port: 6379
- Password: accusys
- 數據目錄: /Users/accusys/momentry/var/redis/
- 日誌目錄: /Users/accusys/momentry/log/
-284
View File
@@ -1,284 +0,0 @@
# RustDesk 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 RustDesk 遠端桌面服務,配置為本地部署。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| RustDesk | ✅ 已安裝 |
| 數據目錄 | /Users/accusys/momentry/var/rustdesk/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| HBBS Plist | /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist |
| HBBR Plist | /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist |
---
## 服務端口
| 服務 | Port | 協議 |
|------|------|------|
| hbbs (TCP) | 21115 | 主端口 |
| hbbs (TCP/UDP) | 21116 | NAT 測試 |
| hbbs (WebSocket) | 21118 | WebSocket |
| hbbr (TCP) | 21117 | 中繼端口 |
| hbbr (TCP) | 21119 | 中繼 extra |
---
## 安裝步驟
### Step 1: 安裝 RustDesk (使用 brew)
```bash
# 安裝 RustDesk
brew install rustdesk
```
**驗證**:
```bash
hbbs --version
hbbr --version
```
---
### Step 2: 建立目錄
```bash
# 建立數據目錄
mkdir -p /Users/accusys/momentry/var/rustdesk
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/rustdesk
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立日誌文件
touch /Users/accusys/momentry/log/rustdesk.hbbs.log
touch /Users/accusys/momentry/log/rustdesk.hbbs.error.log
touch /Users/accusys/momentry/log/rustdesk.hbbr.log
touch /Users/accusys/momentry/log/rustdesk.hbbr.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/var/rustdesk
chown -R accusys:staff /Users/accusys/momentry/etc/rustdesk
chown -R accusys:staff /Users/accusys/momentry/log
```
---
### Step 3: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.rustdesk.hbbs.plist /Library/LaunchDaemons/
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.rustdesk.hbbr.plist /Library/LaunchDaemons/
# 移除舊 plist (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.rustdesk.hbbs.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.rustdesk.hbbr.plist 2>/dev/null
sudo rm /Library/LaunchDaemons/com.rustdesk.hbbs.plist 2>/dev/null
sudo rm /Library/LaunchDaemons/com.rustdesk.hbbr.plist 2>/dev/null
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "rustdesk-hbbs"
type: "tcp"
port: 21115
host: "localhost"
timeout: 5
enabled: true
- name: "rustdesk-hbbr"
type: "tcp"
port: 21117
host: "localhost"
timeout: 5
enabled: true
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/var/rustdesk/` | 數據 | **不要刪除** - RustDesk 數據 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/opt/homebrew/bin/hbbr` | 安裝 | **刪除** - RustDesk 安裝 |
| `/opt/homebrew/bin/hbbs` | 安裝 | **刪除** - RustDesk 安裝 |
### Step 1: 停止 RustDesk
```bash
# 停止 RustDesk 服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist
# 確認停止
ps aux | grep rustdesk | grep -v grep || echo "RustDesk 已停止"
```
---
### Step 2: 卸載 RustDesk
```bash
# 卸載 RustDesk
brew uninstall rustdesk
# 移除 plist
sudo rm /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist
sudo rm /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除數據目錄 (可選)
rm -rf /Users/accusys/momentry/var/rustdesk
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/rustdesk-*.log
```
**注意: 不要刪除以下共用目錄**:
```bash
# 這些是共用的,不要刪除!
# /Users/accusys/momentry/var
# /Users/accusys/momentry/log
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep rustdesk | grep -v grep
# 2. 檢查 Port
lsof -i :21115
lsof -i :21116
lsof -i :21117
lsof -i :21118
lsof -i :21119
# 3. 測試連線
nc -zv localhost 21115
nc -zv localhost 21116
# 4. 查看日誌
tail -20 /Users/accusys/momentry/log/rustdesk-hbbs.log
tail -20 /Users/accusys/momentry/log/rustdesk-hbbr.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| Server ID | 59.124.167.225 |
| NAT Test Port | 21116 |
| Relay Port | 21117, 21119 |
---
## 故障排除
### RustDesk 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/rustdesk-hbbs.log
tail -f /Users/accusys/momentry/log/rustdesk-hbbr.log
# 檢查數據目錄權限
ls -la /Users/accusys/momentry/var/rustdesk/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/var/rustdesk
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port
lsof -i :21116
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/bin/hbbs` | RustDesk Server 執行檔 |
| 安裝 | `/opt/homebrew/bin/hbbr` | RustDesk Relay 執行檔 |
| 數據目錄 | `/Users/accusys/momentry/var/rustdesk/` | 數據儲存 |
| HBBS 日誌 | `/Users/accusys/momentry/log/rustdesk-hbbs.log` | 服務日誌 |
| HBBR 日誌 | `/Users/accusys/momentry/log/rustdesk-hbbr.log` | 中繼日誌 |
| HBBS Plist | `/Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist` | 開機啟動 |
| HBBR Plist | `/Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist` | 開機啟動 |
---
## 版本資訊
- 安裝方式: Homebrew (Cask)
- Client 版本: 1.4.6
- Server 版本: 1.1.15 (hbbs/hbbr binaries from homebrew)
- 數據目錄: /Users/accusys/momentry/var/rustdesk/
- 日誌目錄: /Users/accusys/momentry/log/
---
## 注意事項
### Server 版本
Homebrew 的 RustDesk Cask 只提供客戶端應用程序。服務器二進制文件 (hbbs, hbbr) 需要從其他來源安裝或自行編譯。當前使用的版本較舊 (1.1.15)。
如需更新服務器版本,可以考慮:
1. 從源代碼編譯最新版本
2. 使用 RustDesk 官方提供的 Docker 鏡像
3. 等待 Homebrew 添加服務器公式
-360
View File
@@ -1,360 +0,0 @@
# SFTPGo 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 SFTPGo,配置為本地部署,用於 SFTP/FTP/WebDAV 檔案傳輸服務。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| SFTPGo | ✅ 已安裝 v2.7.0 |
| Port | 8080 (HTTP), 2022 (SFTP) |
| 配置目錄 | /Users/accusys/momentry/etc/sftpgo/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.sftpgo.plist |
---
## 安裝步驟
### Step 1: 安裝 SFTPGo (使用 brew)
```bash
# 安裝 SFTPGo
brew install sftpgo
```
**驗證**:
```bash
sftpgo --version
# SFTPGo 2.7.0
```
---
### Step 2: 建立目錄
```bash
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/sftpgo
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立工作目錄
mkdir -p /Users/accusys/workspace/sftpgo
# 建立日誌文件
touch /Users/accusys/momentry/log/sftpgo.log
touch /Users/accusys/momentry/log/sftpgo.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/etc/sftpgo
chown -R accusys:staff /Users/accusys/momentry/log
chown -R accusys:staff /Users/accusys/workspace/sftpgo
```
---
### Step 3: 建立設定檔
建立 `/Users/accusys/momentry/etc/sftpgo/sftpgo.json`:
```json
{
"common": {
"idle_timeout": 15,
"upload_mode": 0,
"max_per_host_connections": 20
},
"users": [
{
"username": "accusys",
"password": "",
"public_keys": [],
"home_dir": "/Users/accusys/workspace/sftpgo",
"uid": 501,
"gid": 20,
"permissions": {
"/": ["*"]
}
}
],
"httpd": {
"bind_port": 8080,
"bind_address": "0.0.0.0"
},
"ftpd": {
"bind_port": 21,
"bind_address": "0.0.0.0"
},
"sftpd": {
"bind_port": 2022,
"bind_address": "0.0.0.0"
},
"webdavd": {
"bind_port": 0,
"bind_address": ""
}
}
```
---
### Step 4: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.sftpgo.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
---
## 監控配置
### 添加到監控配置
`monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "sftpgo"
type: "http"
port: 8080
host: "localhost"
check_url: "http://localhost:8080/api/v2/info"
timeout: 5
enabled: true
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/etc/sftpgo/` | 配置 | **不要刪除** - SFTPGo 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/Users/accusys/workspace/sftpgo/` | 數據 | **不要刪除** - 上傳檔案目錄 |
| `/opt/homebrew/opt/sftpgo/` | 安裝 | **刪除** - SFTPGo 安裝目錄 |
### Step 1: 停止 SFTPGo
```bash
# 找到 SFTPGo 進程
ps aux | grep sftpgo | grep -v grep
# 停止 SFTPGo
pkill sftpgo
# 確認停止
ps aux | grep sftpgo | grep -v grep || echo "SFTPGo 已停止"
```
---
### Step 2: 卸載 SFTPGo
```bash
# 卸載 SFTPGo
brew uninstall sftpgo
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.sftpgo.plist
sudo rm /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除配置目錄 (可選)
rm -rf /Users/accusys/momentry/etc/sftpgo
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/sftpgo.log
rm -f /Users/accusys/momentry/log/sftpgo.error.log
```
**注意: 不要刪除以下目錄**:
```bash
# 這些是重要的,不要刪除!
# /Users/accusys/momentry/etc/sftpgo
# /Users/accusys/momentry/log
# /Users/accusys/workspace/sftpgo
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== SFTPGo 卸載後檢查 ==="
# 1. 檢查 SFTPGo 進程
echo "1. SFTPGo 進程:"
ps aux | grep sftpgo | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 8080/2022
echo "2. Port 8080/2022:"
(lsof -i :8080 > /dev/null 2>&1 || lsof -i :2022 > /dev/null 2>&1) && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. sftpgo 命令
echo "3. sftpgo 命令:"
which sftpgo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list sftpgo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep sftpgo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 配置目錄 (可選刪除)
echo "6. 配置目錄:"
[ -d "/Users/accusys/momentry/etc/sftpgo" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep sftpgo | grep -v grep
# 2. 檢查 Port
lsof -i :8080
lsof -i :2022
# 3. 測試連線
curl http://localhost:8080/
# 4. 查看版本
sftpgo --version
# 5. 驗證配置
sftpgo validate --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 6. 查看日誌
tail -20 /Users/accusys/momentry/log/sftpgo.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| HTTP/WebDAV | http://localhost:8080 |
| SFTP | localhost:2022 |
| FTP | localhost:21 |
| Admin API | http://localhost:8080/api/v2/info |
---
## 環境變數
`.env` 中:
```env
SFTPGO_CONFIG=/Users/accusys/momentry/etc/sftpgo/sftpgo.json
SFTPGO_DATA_DIR=/Users/accusys/workspace/sftpgo
```
---
## 故障排除
### SFTPGo 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/sftpgo.log
# 驗證配置語法
sftpgo validate --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 檢查目錄權限
ls -la /Users/accusys/momentry/etc/sftpgo/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/etc/sftpgo
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port
lsof -i :8080
lsof -i :2022
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.sftpgo.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/sftpgo/` | SFTPGo 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/sftpgo/bin/sftpgo` | SFTPGo 執行檔 |
| 配置 | `/Users/accusys/momentry/etc/sftpgo/sftpgo.json` | 設定檔 |
| 日誌 | `/Users/accusys/momentry/log/sftpgo.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/sftpgo.error.log` | 錯誤日誌 |
| 工作目錄 | `/Users/accusys/workspace/sftpgo/` | 上傳檔案目錄 |
| plist | `/Library/LaunchDaemons/com.momentry.sftpgo.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/sftpgo_backup/sftpgo.json` | 配置備份 |
---
## 常用指令
```bash
# 驗證配置
sftpgo validate --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 查看版本
sftpgo --version
# 查看可用命令
sftpgo --help
# 重載配置 (熱重載)
sftpgo reload --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
```
---
## 版本資訊
- 版本: 2.7.0
- HTTP Port: 8080
- SFTP Port: 2022
- FTP Port: 21
- 配置: /Users/accusys/momentry/etc/sftpgo/sftpgo.json
- 工作目錄: /Users/accusys/workspace/sftpgo
- 日誌目錄: /Users/accusys/momentry/log/
-682
View File
@@ -1,682 +0,0 @@
# Job Worker 實作計畫
| 項目 | 內容 |
|------|------|
| 建立者 | Warren / OpenCode |
| 建立時間 | 2026-03-24 |
| 文件版本 | V1.1 |
| 狀態 | ✅ 已實作 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-24 | 建立實作計畫 | OpenCode |
| V1.1 | 2026-03-25 | 實作完成,更新狀態 | OpenCode |
---
## 實作狀態
### ✅ 已完成
| 元件 | 檔案 | 狀態 |
|------|------|------|
| MonitorJob 結構 | `src/core/db/postgres_db.rs` | ✅ |
| ProcessorResult 結構 | `src/core/db/postgres_db.rs` | ✅ |
| Worker 配置 | `src/worker/config.rs` | ✅ |
| Job Worker | `src/worker/job_worker.rs` | ✅ |
| Processor Pool | `src/worker/processor.rs` | ✅ |
| Worker 模組 | `src/worker/mod.rs` | ✅ |
| PostgreSQL 表格 | `monitor_jobs`, `processor_results` | ✅ |
| 類型修復 | `i32`, `NaiveDateTime` | ✅ |
### 待整合
| 項目 | 說明 |
|------|------|
| Worker 服務啟動 | 需要加入 launchd plist |
| 監控整合 | 需要加入 MOMENTRY_CORE_MONITORING.md |
| 備份涵蓋 | 需要確認備份包含新表格 |
---
## 1. 設計決策
### 1.1 確認的設計決策
| 項目 | 決策 | 理由 |
|------|------|------|
| 觸發方式 | 輪詢(Job Worker) | 暫無可靠的 API 觸發機制 |
| 並行處理 | 最多 2 個 | 可根據 CPU/GPU 能力調整 |
| 失敗處理 | 獨立模組,部分完成可接續 | 任何模組失敗都產出狀態記錄 |
| Worker 啟動 | 獨立進程 | 隔離、易管理 |
| 並行上限調整 | 環境變數 + 預設值 | 靈活、可調整 |
| 狀態同步 | PostgreSQL + Redis | 可靠 + 即時 |
### 1.2 環境變數
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `MOMENTRY_MAX_CONCURRENT` | 2 | 最大並行 processor 數 |
| `MOMENTRY_POLL_INTERVAL` | 5 | 輪詢間隔(秒) |
| `MOMENTRY_WORKER_ENABLED` | true | 是否啟用 worker |
---
## 2. 系統架構
### 2.1 完整流程圖
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 檔案註冊觸發處理流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. SFTPGo 上傳 │
│ │ │
│ ▼ │
│ 2. Hook 呼叫 Register API │
│ │ │
│ ▼ │
│ 3. Register API │
│ ├─► ffprobe 提取 metadata │
│ ├─► 寫入 videos 表 │
│ └─► 建立 monitor_jobs 記錄 (status=pending) │
│ │ │
│ ▼ │
│ 4. Job Worker (獨立進程,輪詢機制) │
│ ├─► 輪詢 pending jobs │
│ ├─► 檢查 videos 表 fs_json 決定需要處理什麼 │
│ ├─► 並行執行 processors (最多 2 個) │
│ └─► 更新 videos, monitor_jobs, processor_results 表 │
│ │ │
│ ▼ │
│ 5. 處理結果 │
│ ├─► 更新 videos 表 (fs_json, psql_chunk, qvector_chunk) │
│ ├─► 更新 monitor_jobs 表 (status, progress) │
│ ├─► 更新 processor_results 表 (每個模組狀態) │
│ └─► Redis Pub/Sub 即時進度 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 2.2 Job Worker 架構
```
┌─────────────────────────────────────────────────────────────────────┐
│ Job Worker 架構 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PostgreSQL │ ───▶ │ Worker │ ───▶ │ Processor │ │
│ │ Job Queue │ │ Loop │ │ Pool │ │
│ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Video State │ │ Processor 1 │ │
│ │ Check │ │ (ASR/YOLO) │ │
│ └─────────────┘ ├─────────────┤ │
│ │ Processor 2 │ │
│ │ (CUT/OCR) │ │
│ └─────────────┘ │
│ │
│ Redis ──── Pub/Sub ──── 即時進度 │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 3. 資料庫結構
### 3.1 Migration 檔案
**檔案**: `migrations/003_job_worker.sql`
```sql
-- ================================================================
-- Migration 003: Job Worker System
-- ================================================================
-- 3.1.1 更新 videos 表
ALTER TABLE videos ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'pending';
ALTER TABLE videos ADD COLUMN IF NOT EXISTS user_id BIGINT;
ALTER TABLE videos ADD COLUMN IF NOT EXISTS job_id INTEGER REFERENCES monitor_jobs(id);
COMMENT ON COLUMN videos.status IS 'pending, processing, completed, failed';
COMMENT ON COLUMN videos.user_id IS 'WordPress user ID';
COMMENT ON COLUMN videos.job_id IS 'Associated monitor_jobs ID';
-- 3.1.2 更新 monitor_jobs 表
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS video_id BIGINT REFERENCES videos(id);
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS user_id BIGINT;
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS processors VARCHAR(20)[];
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS completed_processors VARCHAR(20)[];
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS failed_processors VARCHAR(20)[];
COMMENT ON COLUMN monitor_jobs.processors IS 'Processors to run: asr, cut, yolo, ocr, face, pose, asrx';
COMMENT ON COLUMN monitor_jobs.completed_processors IS 'Successfully completed processors';
COMMENT ON COLUMN monitor_jobs.failed_processors IS 'Failed processors';
-- 3.1.3 新增 processor_results 表
CREATE TABLE IF NOT EXISTS processor_results (
id SERIAL PRIMARY KEY,
job_id INTEGER REFERENCES monitor_jobs(id) ON DELETE CASCADE,
video_id BIGINT REFERENCES videos(id) ON DELETE CASCADE,
processor VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
output_path TEXT,
started_at TIMESTAMP,
completed_at TIMESTAMP,
error_message TEXT,
progress_total INT DEFAULT 0,
progress_current INT DEFAULT 0,
last_checkpoint JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(job_id, processor)
);
CREATE INDEX IF NOT EXISTS idx_processor_results_job ON processor_results(job_id);
CREATE INDEX IF NOT EXISTS idx_processor_results_video ON processor_results(video_id);
CREATE INDEX IF NOT EXISTS idx_processor_results_status ON processor_results(status);
COMMENT ON TABLE processor_results IS 'Tracks individual processor execution status';
COMMENT ON COLUMN processor_results.status IS 'pending, running, completed, failed, skipped';
-- 3.1.4 更新 videos 表標記欄位用途
COMMENT ON COLUMN videos.fs_video IS 'Video file exists on filesystem';
COMMENT ON COLUMN videos.fs_json IS 'All processor JSON files generated';
COMMENT ON COLUMN videos.fs_chunks IS 'Chunk files generated';
COMMENT ON COLUMN videos.fs_vectors IS 'Vector files generated';
COMMENT ON COLUMN videos.psql_chunk IS 'Chunks stored in PostgreSQL';
COMMENT ON COLUMN videos.pvector_chunk IS 'Vectors stored in PostgreSQL';
COMMENT ON COLUMN videos.qvector_chunk IS 'Vectors stored in Qdrant';
```
### 3.2 表關係圖
```
videos monitor_jobs
┌──────────────────────┐ ┌──────────────────────┐
│ id (PK) │◄────────│ video_id (FK) │
│ uuid │ │ user_id │
│ status │ │ processors[] │
│ fs_video │ │ completed_processors[]│
│ fs_json │ │ failed_processors[] │
│ job_id (FK)─────────┼────────►│ status │
│ user_id │ │ id (PK) │
└──────────────────────┘ └──────────────────────┘
processor_results
┌──────────────────────┐
│ job_id (FK) │
│ video_id (FK) │
│ processor │
│ status │
│ progress_current │
│ last_checkpoint │
│ id (PK) │
└──────────────────────┘
```
---
## 4. 模組並行策略
### 4.1 模組分類
| 模組 | 資源需求 | 獨立性 | 建議並行 |
|------|----------|--------|----------|
| ASR | GPU/CPU | 高 | ✅ 可並行 |
| CUT | CPU | 高 | ✅ 可並行 |
| YOLO | GPU | 中 | ✅ 可並行 |
| OCR | GPU/CPU | 高 | ✅ 可並行 |
| Face | GPU | 中 | ✅ 可並行 |
| Pose | GPU | 中 | ✅ 可並行 |
| ASRX | GPU/CPU | 高 | ✅ 可並行 |
### 4.2 建議並行組合
| 組合 | 模組 1 | 模組 2 | 說明 |
|------|---------|---------|------|
| GPU+CPU | YOLO/Pose/Face | ASR/CUT/OCR | 平衡負載 |
| 雙GPU | YOLO | Pose | 雙 GPU 卡片 |
| 雙CPU | ASR | CUT/OCR | 無 GPU 時 |
### 4.3 Worker 配置
```rust
// src/worker/config.rs
#[derive(Debug, Clone)]
pub struct WorkerConfig {
pub max_concurrent: usize, // 預設 2
pub poll_interval_secs: u64, // 預設 5
pub enabled: bool, // 預設 true
}
impl Default for WorkerConfig {
fn default() -> Self {
Self {
max_concurrent: 2,
poll_interval_secs: 5,
enabled: true,
}
}
}
impl WorkerConfig {
pub fn from_env() -> Self {
Self {
max_concurrent: std::env::var("MOMENTRY_MAX_CONCURRENT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(2),
poll_interval_secs: std::env::var("MOMENTRY_POLL_INTERVAL")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(5),
enabled: std::env::var("MOMENTRY_WORKER_ENABLED")
.ok()
.map(|v| v != "false")
.unwrap_or(true),
}
}
}
```
---
## 5. 失敗處理機制
### 5.1 設計原則
```
每個模組獨立處理:
- 成功 → 產出完整 .jsonstatus=completed
- 失敗 → 產出 .json 包含 error 狀態,status=failed
- 部分完成 → 可從 checkpoint 繼續,status=running
```
### 5.2 Processor 輸出格式
```json
{
"processor": "asr",
"status": "completed|failed|partial",
"completed_at": "2026-03-24T12:00:00Z",
"result": { ... },
"error": null,
"last_checkpoint": {
"frame": 5000,
"timestamp": 180.5
}
}
```
### 5.3 失敗處理流程
```rust
async fn run_processor(&self, module: &str, video: &Video) -> Result<()> {
let output_path = self.get_output_path(video, module);
match self.execute_processor(module, video, &output_path).await {
Ok(result) => {
// 成功:更新狀態
self.db.update_processor_status(job_id, module, "completed").await?;
self.publish_progress(job_id, module, 100).await?;
}
Err(e) => {
// 失敗:仍然保存部分結果
let partial_result = self.get_partial_result(&output_path);
self.db.update_processor_status(job_id, module, "failed").await?;
self.db.save_error_message(job_id, module, &e.to_string()).await?;
// 記錄錯誤但不中斷其他模組
tracing::warn!("Processor {} failed: {}", module, e);
}
}
Ok(())
}
```
---
## 6. 實作結構
### 6.1 目錄結構
```
src/
├── worker/
│ ├── mod.rs # Worker 模組導出
│ ├── config.rs # Worker 配置
│ ├── worker.rs # Worker 主邏輯
│ ├── processor.rs # Processor 執行器
│ ├── queue.rs # Job 佇列管理
│ └── progress.rs # 進度追蹤
├── api/
│ └── server.rs # 更新 Register API
└── main.rs # 新增 worker 命令
```
### 6.2 核心模組
#### 6.2.1 Worker Config (`src/worker/config.rs`)
```rust
pub struct WorkerConfig {
pub max_concurrent: usize,
pub poll_interval_secs: u64,
pub enabled: bool,
}
impl WorkerConfig {
pub fn from_env() -> Self { ... }
}
```
#### 6.2.2 Worker Loop (`src/worker/worker.rs`)
```rust
pub struct JobWorker {
db: PostgresDb,
redis: RedisCache,
config: WorkerConfig,
semaphore: Arc<Semaphore>,
}
impl JobWorker {
pub async fn run(&self) -> Result<()> {
loop {
if self.config.enabled {
self.process_pending_jobs().await?;
}
tokio::time::sleep(Duration::from_secs(self.config.poll_interval_secs)).await;
}
}
async fn process_pending_jobs(&self) -> Result<()> {
// 1. 檢查並發數
// 2. 取得 pending jobs
// 3. 分配給 worker pool
// 4. 並行執行 processors
}
}
```
#### 6.2.3 Processor Pool (`src/worker/processor.rs`)
```rust
pub struct ProcessorPool {
max_concurrent: usize,
}
impl ProcessorPool {
pub async fn execute(&self, job: &Job, video: &Video) -> Result<ProcessorResult> {
// 根據 videos 表決定需要執行哪些 processor
// 並行執行最多 2 個
// 處理失敗但不中斷其他 processor
}
}
```
---
## 7. API 端點設計
### 7.1 新增端點
| 端點 | 方法 | 說明 |
|------|------|------|
| `/api/v1/jobs` | GET | 列出所有 jobs |
| `/api/v1/jobs/:uuid` | GET | 取得特定 job 詳細 |
| `/api/v1/jobs/:uuid/retry` | POST | 重試失敗的 processor |
| `/api/v1/jobs/:uuid/cancel` | POST | 取消 job |
### 7.2 端點詳情
#### GET /api/v1/jobs
```json
Response:
{
"jobs": [
{
"id": 1,
"uuid": "abc123def456",
"status": "running",
"progress": 60,
"processors": ["asr", "cut", "yolo", "ocr", "face", "pose"],
"completed": ["asr", "cut", "yolo"],
"failed": []
}
]
}
```
#### GET /api/v1/jobs/:uuid
```json
Response:
{
"id": 1,
"uuid": "abc123def456",
"video_id": 10,
"status": "running",
"processors": {
"asr": {"status": "completed", "progress": 100},
"cut": {"status": "completed", "progress": 100},
"yolo": {"status": "running", "progress": 45, "current": 5000, "total": 11000},
"ocr": {"status": "pending"},
"face": {"status": "pending"},
"pose": {"status": "pending"}
},
"created_at": "2026-03-24T12:00:00Z",
"started_at": "2026-03-24T12:01:00Z"
}
```
---
## 8. Redis Key 設計
### 8.1 現有 Key 保持
```bash
momentry:job:{uuid} # Job Hash
momentry:job:{uuid}:processor:{name} # Processor Hash
momentry:progress:{uuid} # Pub/Sub Channel
momentry:jobs:active # Set: 運行中 UUIDs
momentry:jobs:completed # Set: 完成 UUIDs
momentry:jobs:failed # Set: 失敗 UUIDs
```
### 8.2 進度更新時序
```
Processor 執行
├─► 每秒更新 Redis Hash (即時)
├─► 每 10% 或完成時更新 PostgreSQL (持久)
└─► 失敗時立即更新 PostgreSQL (錯誤記錄)
```
---
## 9. 實作順序
### Phase 1: 資料庫遷移
| 任務 | 說明 |
|------|------|
| 1.1 | 建立 `migrations/003_job_worker.sql` |
| 1.2 | 更新 `postgres_db.rs` 對應的 struct |
| 1.3 | 執行 migration 驗證 |
### Phase 2: Worker 框架
| 任務 | 說明 |
|------|------|
| 2.1 | 建立 `src/worker/mod.rs` |
| 2.2 | 建立 `src/worker/config.rs` |
| 2.3 | 建立 `src/worker/worker.rs` |
| 2.4 | 建立 `src/worker/processor.rs` |
### Phase 3: Register API 整合
| 任務 | 說明 |
|------|------|
| 3.1 | 修改 `src/api/server.rs` 的 register 函數 |
| 3.2 | 加入建立 monitor_jobs 的邏輯 |
| 3.3 | 更新 videos 表 status 欄位 |
### Phase 4: Processor 執行
| 任務 | 說明 |
|------|------|
| 4.1 | 實作 processor 並行執行(最多 2 個) |
| 4.2 | 實作失敗處理(保存部分結果) |
| 4.3 | 實作 checkpoint 恢復 |
### Phase 5: 進度追蹤
| 任務 | 說明 |
|------|------|
| 5.1 | Redis Pub/Sub 整合 |
| 5.2 | PostgreSQL 定期同步 |
| 5.3 | API 進度端點更新 |
### Phase 6: API 端點
| 任務 | 說明 |
|------|------|
| 6.1 | GET /api/v1/jobs |
| 6.2 | GET /api/v1/jobs/:uuid |
| 6.3 | POST /api/v1/jobs/:uuid/retry |
| 6.4 | POST /api/v1/jobs/:uuid/cancel |
### Phase 7: CLI 命令
| 任務 | 說明 |
|------|------|
| 7.1 | `cargo run -- worker` 命令 |
| 7.2 | Worker 啟動/停止/狀態顯示 |
| 7.3 | launchd plist 設定 |
### Phase 8: 測試
| 任務 | 說明 |
|------|------|
| 8.1 | 單元測試 |
| 8.2 | 端到端測試 |
| 8.3 | 失敗處理測試 |
| 8.4 | 並行執行測試 |
---
## 10. CLI 命令
### 10.1 Worker 命令
```bash
# 啟動 worker
cargo run -- worker
# 顯示 worker 幫助
cargo run -- worker --help
```
### 10.2 環境變數
```bash
# Worker 配置
export MOMENTRY_MAX_CONCURRENT=2
export MOMENTRY_POLL_INTERVAL=5
export MOMENTRY_WORKER_ENABLED=true
# 現有環境變數
export DATABASE_URL=postgres://accusys@localhost:5432/momentry
export REDIS_URL=redis://:accusys@localhost:6379
```
---
## 11. 預估工時
| Phase | 任務 | 預估工時 |
|-------|------|----------|
| 1 | 資料庫遷移 | 2h |
| 2 | Worker 框架 | 4h |
| 3 | Register API 整合 | 2h |
| 4 | Processor 執行 | 4h |
| 5 | 進度追蹤 | 2h |
| 6 | API 端點 | 3h |
| 7 | CLI 命令 | 2h |
| 8 | 測試 | 4h |
| **總計** | | **23h** |
---
## 12. 參考文件
| 文件 | 用途 |
|------|------|
| `docs/MOMENTRY_CORE_MONITORING.md` | 監控系統規範 |
| `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計 |
| `docs/PROCESSING_PIPELINE.md` | 處理流程 |
| `docs/CHUNK_DESIGN.md` | 資料庫設計 |
| `docs/API_REFERENCE.md` | API 參考 |
---
## 13. 附錄
### A. 狀態機
```
┌──────────────┐
│ PENDING │
└──────┬───────┘
│ register 後
┌──────────────┐
┌─────▶│ PROCESSING │◀──────┐
│ └──────┬───────┘ │
│ │ │
部分失敗 all completed 全部失敗
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ PARTIAL │ │COMPLETED │ │ FAILED │
└──────────┘ └──────────┘ └──────────┘
```
### B. videos 表 status 欄位
| 值 | 說明 |
|------|------|
| `pending` | 已註冊,等待處理 |
| `processing` | 處理中 |
| `completed` | 所有處理完成 |
| `failed` | 處理失敗 |
### C. processor_results 表 status 欄位
| 值 | 說明 |
|------|------|
| `pending` | 等待執行 |
| `running` | 執行中 |
| `completed` | 執行成功 |
| `failed` | 執行失敗 |
| `skipped` | 跳過(如檔案已存在) |
-503
View File
@@ -1,503 +0,0 @@
# Momentry JSON 輸出檔案規範
本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。
---
## 1. 輸出檔案總覽
### 1.1 檔案類型
| 類型 | 前綴 | 說明 | 狀態 |
|------|------|------|------|
| **Probe** | `{uuid}.probe.json` | 影片元數據 | ✅ 已實作 |
| **ASR** | `{uuid}.asr.json` | 語音識別結果 | ✅ 已實作 |
| **ASRx** | `{uuid}.asrx.json` | 說話者分離 | 🔜 規劃中 |
| **OCR** | `{uuid}.ocr.json` | 文字辨識結果 | 🔜 規劃中 |
| **YOLO** | `{uuid}.yolo.json` | 物件偵測結果 | 🔜 規劃中 |
| **Face** | `{uuid}.face.json` | 人臉偵測結果 | 🔜 規劃中 |
| **Pose** | `{uuid}.pose.json` | 姿態估計結果 | 🔜 規劃中 |
| **Thumbnail** | `{uuid}/thumb_XXX.jpg` | 縮圖檔案 | ✅ 已實作 |
### 1.2 命名規範
```
{UUID}.{類型}.json
範例:
1636719dc31f78ac.probe.json - 影片探測結果
1636719dc31f78ac.asr.json - 語音識別結果
1636719dc31f78ac.ocr.json - 文字辨識結果
```
- **UUID**: 16 字元,基於檔案路徑計算
- **類型**: 小寫 snake_case
- **副檔名**: `.json`
---
## 2. 輸出目錄結構
### 2.1 預設輸出位置
```
momentry_core_0.1/
├── {uuid}.probe.json # 影片探測
├── {uuid}.asr.json # 語音識別
├── {uuid}.asrx.json # 說話者分離
├── {uuid}.ocr.json # 文字辨識
├── {uuid}.yolo.json # 物件偵測
├── {uuid}.face.json # 人臉偵測
├── {uuid}.pose.json # 姿態估計
└── thumbnails/
└── {uuid}/
├── thumb_000.jpg
├── thumb_001.jpg
└── ...
```
### 2.2 儲存策略
| 資料類型 | 儲存位置 | 說明 |
|----------|----------|------|
| JSON 檔案 | 專案根目錄 | 方便快速存取 |
| 縮圖 | thumbnails/{uuid}/ | 分離儲存 |
| 資料庫 | PostgreSQL | 長期儲存 |
---
## 3. JSON 結構定義
### 3.1 Probe (影片探測)
**檔案**: `{uuid}.probe.json`
```json
{
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_type": "video",
"width": 1920,
"height": 1080,
"r_frame_rate": "60000/1001",
"duration": "6879.329524",
"sample_rate": null,
"channels": null
},
{
"index": 1,
"codec_name": "aac",
"codec_type": "audio",
"width": null,
"height": null,
"r_frame_rate": "0/0",
"duration": "6879.245333",
"sample_rate": "48000",
"channels": 2
}
],
"format": {
"filename": "/path/to/video.mov",
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "6879.329524",
"size": "2361629896",
"bit_rate": "2748000"
}
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `streams` | Array | 媒體串流陣列 |
| `streams[].index` | Integer | 串流索引 |
| `streams[].codec_name` | String | 編碼名稱 |
| `streams[].codec_type` | String | 串流類型 (video/audio) |
| `streams[].width` | Integer | 寬度 (video) |
| `streams[].height` | Integer | 高度 (video) |
| `streams[].r_frame_rate` | String | 幀率 |
| `streams[].duration` | String | 持續時間 (秒) |
| `streams[].sample_rate` | String | 採樣率 (audio) |
| `streams[].channels` | Integer | 聲道數 (audio) |
| `format` | Object | 檔案格式資訊 |
| `format.filename` | String | 原始檔案路徑 |
| `format.format_name` | String | 格式名稱 |
| `format.duration` | String | 總時長 (秒) |
| `format.size` | String | 檔案大小 (bytes) |
| `format.bit_rate` | String | 位元率 |
---
### 3.2 ASR (語音識別)
**檔案**: `{uuid}.asr.json`
```json
{
"language": "en",
"language_probability": 0.9945855736732483,
"segments": [
{
"start": 0.0,
"end": 19.04,
"text": "Hello and welcome to the old-time movie show."
},
{
"start": 19.04,
"end": 25.44,
"text": "Today we are featuring the 1963 comedy mystery film Charade."
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `language` | String | 偵測語言代碼 (ISO 639-1) |
| `language_probability` | Float | 語言偵測機率 (0-1) |
| `segments` | Array | 語音分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 識別文字 |
---
### 3.3 ASRx (說話者分離)
**檔案**: `{uuid}.asrx.json`
```json
{
"language": "en",
"language_probability": 0.95,
"segments": [
{
"start": 0.0,
"end": 19.04,
"text": "Hello and welcome to the old-time movie show.",
"speaker_id": "SPEAKER_00",
"speaker_embedding": [0.123, -0.456, ...]
},
{
"start": 19.04,
"end": 25.44,
"text": "Today we are featuring the 1963 comedy mystery film Charade.",
"speaker_id": "SPEAKER_01",
"speaker_embedding": [0.789, -0.123, ...]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `language` | String | 偵測語言代碼 |
| `language_probability` | Float | 語言偵測機率 |
| `segments` | Array | 語音分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 識別文字 |
| `segments[].speaker_id` | String | 說話者 ID |
| `segments[].speaker_embedding` | Array | 說話者嵌入向量 (可選) |
---
### 3.4 OCR (文字辨識)
**檔案**: `{uuid}.ocr.json`
```json
{
"segments": [
{
"start": 10.5,
"end": 12.3,
"text": "EXAMPLE TEXT",
"boxes": [
{
"x1": 100,
"y1": 50,
"x2": 400,
"y2": 100
}
],
"confidence": 0.95
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | OCR 分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 辨識文字 |
| `segments[].boxes` | Array | 文字邊界框陣列 |
| `segments[].boxes[].x1` | Integer | 左上 X 座標 |
| `segments[].boxes[].y1` | Integer | 左上 Y 座標 |
| `segments[].boxes[].x2` | Integer | 右下 X 座標 |
| `segments[].boxes[].y2` | Integer | 右下 Y 座標 |
| `segments[].confidence` | Float | 辨識信心度 |
---
### 3.5 YOLO (物件偵測)
**檔案**: `{uuid}.yolo.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"objects": [
{
"class": "person",
"confidence": 0.92,
"box": {
"x1": 150,
"y1": 200,
"x2": 400,
"y2": 800
}
},
{
"class": "car",
"confidence": 0.87,
"box": {
"x1": 800,
"y1": 400,
"x2": 1200,
"y2": 700
}
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].objects` | Array | 偵測物件陣列 |
| `segments[].objects[].class` | String | 物件類別 |
| `segments[].objects[].confidence` | Float | 偵測信心度 |
| `segments[].objects[].box` | Object | 邊界框 |
---
### 3.6 Face (人臉偵測)
**檔案**: `{uuid}.face.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"faces": [
{
"face_id": "face_001",
"box": {
"x1": 100,
"y1": 50,
"x2": 300,
"y2": 350
},
"embedding": [0.123, -0.456, ...],
"emotion": "happy",
"age": 35,
"gender": "female"
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].faces` | Array | 人臉陣列 |
| `segments[].faces[].face_id` | String | 人臉 ID |
| `segments[].faces[].box` | Object | 邊界框 |
| `segments[].faces[].embedding` | Array | 人臉嵌入向量 |
| `segments[].faces[].emotion` | String | 情緒分類 (可選) |
| `segments[].faces[].age` | Integer | 年齡估計 (可選) |
| `segments[].faces[].gender` | String | 性別估計 (可選) |
---
### 3.7 Pose (姿態估計)
**檔案**: `{uuid}.pose.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"poses": [
{
"person_id": "person_001",
"keypoints": {
"nose": {"x": 320, "y": 120, "confidence": 0.98},
"left_eye": {"x": 335, "y": 110, "confidence": 0.95},
"right_eye": {"x": 305, "y": 110, "confidence": 0.93},
"left_shoulder": {"x": 280, "y": 180, "confidence": 0.91},
"right_shoulder": {"x": 360, "y": 180, "confidence": 0.89}
},
"confidence": 0.92
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].poses` | Array | 姿態陣列 |
| `segments[].poses[].person_id` | String | 人員 ID |
| `segments[].poses[].keypoints` | Object | 關鍵點 |
| `segments[].poses[].keypoints.{name}` | Object | 各關鍵點 |
| `segments[].poses[].keypoints.{name}.x` | Integer | X 座標 |
| `segments[].poses[].keypoints.{name}.y` | Integer | Y 座標 |
| `segments[].poses[].keypoints.{name}.confidence` | Float | 信心度 |
| `segments[].poses[].confidence` | Float | 整體信心度 |
---
## 4. 處理流程
### 4.1 處理管線
```
影片檔案
┌─────────────────┐
│ 1. Register │ 建立 UUID,註冊影片
└────────┬────────┘
┌────▼────┐
│ 2. Probe │ ffprobe 擷取元數據
└────┬────┘
│ {uuid}.probe.json
┌────▼─────┐
│ 3. ASR │ faster-whisper 語音識別
└────┬─────┘
│ {uuid}.asr.json
┌────▼──────┐
│ 4. ASRx │ 說話者分離 (pyannote)
└────┬──────┘
│ {uuid}.asrx.json
┌────▼────┐
│ 5. OCR │ Tesseract 文字辨識
└────┬────┘
│ {uuid}.ocr.json
┌────▼────┐
│ 6. YOLO │ 物件偵測
└────┬────┘
│ {uuid}.yolo.json
┌────▼────┐
│ 7. Face │ 人臉偵測
└────┬────┘
│ {uuid}.face.json
┌────▼────┐
│ 8. Pose │ 姿態估計
└────┬────┘
│ {uuid}.pose.json
┌────▼──────┐
│ 9. Chunk │ 轉換為資料庫 chunks
└───────────┘
```
### 4.2 失敗處理
| 階段 | 失敗時 | 處理 |
|------|--------|------|
| Probe | 無法讀取影片 | 終止流程,輸出錯誤 |
| ASR | 無音軌 | 產生空 segments,繼續流程 |
| OCR/YOLO/Face/Pose | 處理失敗 | 跳過該階段,記錄日誌 |
---
## 5. 資料庫儲存
### 5.1 Chunk 結構
```sql
CREATE TABLE chunks (
id BIGSERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
content JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(uuid, chunk_id)
);
```
### 5.2 轉換範例
```rust
// ASR → Chunk (Sentence)
for (i, seg) in asr_result.segments.iter().enumerate() {
let chunk = Chunk::new(
uuid.clone(),
i as u32,
ChunkType::Sentence,
seg.start,
seg.end,
serde_json::json!({"text": seg.text}),
);
db.store_chunk(&chunk).await?;
}
```
---
## 6. 版本歷史
| 版本 | 日期 | 變更 |
|------|------|------|
| 1.0.0 | 2026-03-16 | 初始版本 |
---
## 7. 相關文件
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [AGENTS.md](../AGENTS.md) - 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
-674
View File
@@ -1,674 +0,0 @@
# Momentry Core 監控規範 (暫定)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-25 | 新增可配置 Redis Key Prefix 說明 | Warren | OpenCode / GLM-5 |
| V1.2 | 2026-03-25 | 新增 Job Worker 監控、processor_results 表 | Warren | OpenCode / GLM-5 |
---
> **⚠️ 狀態**: 此文檔為暫定版本,momentry_core 仍在開發中,待開發完成後再正式納入監控系統。
---
## 1. 概述
momentry_core 是 Rust 開發的數字資產管理系統,專注於視頻分析和 RAG 功能。
## 2. 監控架構
### Layer 2: Service 監控
| 項目 | 類型 | 檢查方式 | Port | 狀態 |
|------|------|----------|------|------|
| postgresql | TCP | `pg_isready` | 5432 | ✅ 運行中 |
| redis | TCP | `redis-cli ping` | 6379 | ✅ 運行中 |
| n8n | HTTP | `/healthz` | 5678 | ✅ 運行中 |
| sftpgo | HTTP | `/healthz`, `/api/v2/token` | 8080 | ✅ 運行中 |
| qdrant | HTTP | `/collections` | 6333 | ✅ 運行中 |
| momentry_core | CLI | `momentry --version` | - | ⚠️ 待編譯 |
**附屬服務狀態** (非核心但相關):
| gitea | HTTP | `/` | 3000 | ✅ 運行中 |
| ollama | HTTP | `/api/tags` | 11434 | ✅ 運行中 |
| caddy | HTTP | `/` | 80 | ✅ 運行中 |
### Layer 7: Backup 監控
| 項目 | 類型 | 位置 |
|------|------|------|
| momentry | data | `/Users/accusys/momentry/backup/momentry` |
| sftpgo | config + db | `/Users/accusys/momentry/backup/daily/sftpgo/` |
---
## 3. Redis 監控架構
> **⚠️ 注意**: 從 V1.1 開始,所有 Redis Keys 都支援自定義前綴。
> 預設前綴:生產環境為 `momentry:`,開發環境為 `momentry_dev:`
>
> 若使用非預設前綴,請將下方命令中的 `momentry:` 替換為實際前綴。
### 3.1 健康檢查
```bash
# 檢查 Redis 連線
redis-cli -a accusys ping
# 檢查 momentry 健康狀態
redis-cli GET momentry:health:current
```
### 3.2 Job 狀態監控
```bash
# 查看運行中的 Jobs
redis-cli SMEMBERS momentry:jobs:active
# 查看 Job 狀態
redis-cli HGETALL momentry:job:5dea6618a606e7c7
```
### 3.3 即時進度監控
```bash
# 訂閱進度頻道
redis-cli SUBSCRIBE momentry:progress:5dea6618a606e7c7
```
---
## 4. 監控腳本
### 4.1 健康檢查項目細節
基於當前系統狀態,需要監控的核心項目:
#### 4.1.1 資料庫服務檢查
```bash
# PostgreSQL - 檢查伺服器運行與關鍵資料庫
check_postgresql() {
if pg_isready -h localhost -p 5432 > /dev/null 2>&1; then
echo -e "${GREEN}${NC} PostgreSQL"
# 檢查關鍵資料庫是否存在
local missing_dbs=()
for db in momentry video_register n8n sftpgo; do
if ! psql -h localhost -U postgres -lqt 2>/dev/null | cut -d\| -f1 | grep -qw "$db"; then
missing_dbs+=("$db")
fi
done
if [ ${#missing_dbs[@]} -gt 0 ]; then
echo -e " ${YELLOW}${NC} 缺失資料庫: ${missing_dbs[*]}"
record_service "postgresql" "warning" "1" "Missing databases: ${missing_dbs[*]}"
else
record_service "postgresql" "up" "1" ""
fi
else
echo -e "${RED}${NC} PostgreSQL"
record_service "postgresql" "down" "0" "PostgreSQL not responding"
fi
}
```
#### 4.1.2 Redis 檢查
```bash
check_redis() {
if redis-cli -a "$REDIS_PASSWORD" ping 2>/dev/null | grep -q PONG; then
echo -e "${GREEN}${NC} Redis"
record_service "redis" "up" "1" ""
else
echo -e "${RED}${NC} Redis"
record_service "redis" "down" "0" "Redis not responding"
fi
}
```
#### 4.1.3 SFTPGo 檢查
```bash
check_sftpgo() {
# 1. 檢查 HTTP API
if curl -s http://localhost:8080/healthz > /dev/null 2>&1; then
# 2. 檢查 API 認證
if TOKEN=$(curl -s -X GET http://localhost:8080/api/v2/token -u "admin:$SFTPGO_ADMIN_PASSWORD" 2>/dev/null | jq -r '.access_token') && [ -n "$TOKEN" ]; then
echo -e "${GREEN}${NC} SFTPGo"
record_service "sftpgo" "up" "1" ""
else
echo -e "${YELLOW}${NC} SFTPGo (API認證失敗)"
record_service "sftpgo" "warning" "1" "API authentication failed"
fi
else
echo -e "${RED}${NC} SFTPGo"
record_service "sftpgo" "down" "0" "HTTP API not responding"
fi
}
```
#### 4.1.4 n8n 檢查
```bash
check_n8n() {
if curl -s http://localhost:5678/healthz > /dev/null 2>&1; then
echo -e "${GREEN}${NC} n8n"
record_service "n8n" "up" "1" ""
else
echo -e "${RED}${NC} n8n"
record_service "n8n" "down" "0" "API not responding"
fi
}
```
#### 4.1.5 Qdrant 檢查
```bash
check_qdrant() {
if curl -s http://localhost:6333/collections > /dev/null 2>&1; then
echo -e "${GREEN}${NC} Qdrant"
record_service "qdrant" "up" "1" ""
else
echo -e "${RED}${NC} Qdrant"
record_service "qdrant" "down" "0" "API not responding"
fi
}
```
#### 4.1.6 Momentry Core 檢查
```bash
check_momentry_core() {
local binary="/Users/accusys/momentry/target/release/momentry"
if [ ! -f "$binary" ]; then
binary="/Users/accusys/momentry/target/debug/momentry"
fi
if [ -f "$binary" ] && $binary --version > /dev/null 2>&1; then
echo -e "${GREEN}${NC} Momentry Core"
record_service "momentry_core" "up" "1" ""
else
echo -e "${RED}${NC} Momentry Core"
record_service "momentry_core" "down" "0" "Binary not found or not executable"
fi
}
```
### 4.2 檢查腳本範例完整實現
```bash
#!/bin/bash
# monitor/service/health_check.sh
export REDIS_PASSWORD="accusys"
export SFTPGO_ADMIN_PASSWORD="Test3200Test3200"
check_postgresql
check_redis
check_sftpgo
check_n8n
check_qdrant
check_momentry_core
```
### 4.2 Redis Job 監控腳本
**檔案**: `monitor/service/redis_job_monitor.sh`
此腳本專門監控 Redis 中的 Job 狀態與即時進度:
```bash
#!/bin/bash
# Momentry Core Redis Job 監控
REDIS_PASSWORD="${REDIS_PASSWORD:-accusys}"
REDIS_PREFIX="${REDIS_PREFIX:-momentry:}"# 可配置前綴,預設 momentry:
# 檢查 Job 狀態
check_job_status() {
local active_jobs=$(redis-cli -a "$REDIS_PASSWORD" SCARD "${REDIS_PREFIX}jobs:active" 2>/dev/null || echo "0")
local completed_jobs=$(redis-cli -a "$REDIS_PASSWORD" SCARD "${REDIS_PREFIX}jobs:completed" 2>/dev/null || echo "0")
local failed_jobs=$(redis-cli -a "$REDIS_PASSWORD" SCARD "${REDIS_PREFIX}jobs:failed" 2>/dev/null || echo "0")
echo "Momentry Jobs: Active=$active_jobs, Completed=$completed_jobs, Failed=$failed_jobs"
echo "$active_jobs $completed_jobs $failed_jobs"
}
# 檢查特定 Job 進度
check_job_progress() {
local job_uuid=$1
if [ -z "$job_uuid" ]; then
echo "Usage: $0 progress <uuid>"
return 1
fi
local progress=$(redis-cli -a "$REDIS_PASSWORD" HGETALL "${REDIS_PREFIX}job:$job_uuid" 2>/dev/null)
if [ -n "$progress" ]; then
echo "Job $job_uuid progress:"
echo "$progress" | while read -r key value; do
[ -n "$key" ] && echo " $key: $value"
done
else
echo "No progress data for job $job_uuid"
fi
}
# 訂閱即時進度頻道
subscribe_progress() {
local job_uuid=$1
if [ -z "$job_uuid" ]; then
echo "Subscribing to all progress channels..."
redis-cli -a "$REDIS_PASSWORD" PSUBSCRIBE "${REDIS_PREFIX}progress:*" 2>/dev/null
else
echo "Subscribing to job $job_uuid progress..."
redis-cli -a "$REDIS_PASSWORD" SUBSCRIBE "${REDIS_PREFIX}progress:$job_uuid" 2>/dev/null
fi
}
# 列出所有活動 Job
list_active_jobs() {
echo "Active Jobs:"
redis-cli -a "$REDIS_PASSWORD" SMEMBERS "${REDIS_PREFIX}jobs:active" 2>/dev/null | while read -r uuid; do
[ -n "$uuid" ] && echo " - $uuid"
done
}
# 主程序
case "$1" in
status)
check_job_status
;;
progress)
check_job_progress "$2"
;;
subscribe)
subscribe_progress "$2"
;;
list)
list_active_jobs
;;
*)
echo "Usage: $0 {status|progress <uuid>|subscribe [uuid]|list}"
exit 1
;;
esac
```
### 4.3 監控腳本排程
使用 cron 定期執行健康檢查:
```bash
# crontab -e
# 每 5 分鐘執行一次健康檢查
*/5 * * * * /Users/accusys/momentry/scripts/health_check.sh >> /Users/accusys/momentry/log/health_check.log 2>&1
```
### 4.4 監控數據記錄
健康檢查結果應寫入監控數據庫,建議的 `monitor_services` 表結構:
```sql
CREATE TABLE monitor_services (
id SERIAL PRIMARY KEY,
service_name VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL, -- up, down, warning
error_message TEXT,
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_services_checked ON monitor_services(checked_at);
CREATE INDEX idx_monitor_services_name ON monitor_services(service_name);
```
---
## 5. 配置更新
### 5.1 環境變數
必需監控相關環境變數配置在 `.env` 或系統中:
```bash
# Redis 連接
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=accusys
# Redis Key Prefix (可選,預設: momentry:)
REDIS_PREFIX=momentry:
# SFTPGo 管理員密碼 (用於 API 健康檢查)
SFTPGO_ADMIN_PASSWORD=Test3200Test3200
# Momentry Core 路徑 (可選,如果不在標準位置)
MOMENTRY_BINARY_PATH="/Users/accusys/momentry/target/release/momentry"
```
### 5.2 監控配置範例
**檔案**: `monitor/config/monitor_config.yaml`
```yaml
services:
# Redis - 消息隊列與狀態存儲
- name: "redis"
type: "tcp"
host: "localhost"
port: 6379
timeout: 3
enabled: true
# PostgreSQL - 數據庫
- name: "postgresql"
type: "tcp"
host: "localhost"
port: 5432
timeout: 5
enabled: true
check_sql: "SELECT 1;" # 可選:執行 SQL 驗證
# n8n - 工作流引擎
- name: "n8n"
type: "http"
host: "localhost"
port: 5678
check_url: "http://localhost:5678/healthz"
timeout: 5
enabled: true
# SFTPGo - 文件上傳服務
- name: "sftpgo"
type: "http"
host: "localhost"
port: 8080
check_url: "http://localhost:8080/healthz"
timeout: 5
enabled: true
# 附加認證檢查(每小時一次)
auth_check:
endpoint: "/api/v2/token"
method: "GET"
username: "admin"
password_env: "SFTPGO_ADMIN_PASSWORD"
# Qdrant - 向量數據庫
- name: "qdrant"
type: "http"
host: "localhost"
port: 6333
check_url: "http://localhost:6333/collections"
timeout: 5
enabled: true
# Momentry Core CLI
- name: "momentry_core"
type: "cli"
binary_path: "/Users/accusys/momentry/target/release/momentry"
args: ["--version"]
timeout: 3
enabled: true
```
### 5.3 備份配置更新
統一備份系統 (`backup_all.sh`) 已包含 SFTPGo 備份,無需額外配置。
備份保留策略 (預設):
- **每日備份**: 保留 7 天
- **每週備份**: 保留 4 週
- **每月備份**: 保留 12 個月
備份存儲位置:
- 配置文件: `/Users/accusys/momentry/backup/daily/<service>/`
- 最新備份鏈接: `/Users/accusys/momentry/backup/latest/` (由 backup_monitor.sh 管理)
### 5.4 監控腳本配置
建立符號鏈接到監控腳本目錄:
```bash
ln -sf /Users/accusys/momentry/scripts/backup_all.sh /usr/local/bin/backup_all
ln -sf /Users/accusys/momentry/scripts/health_check.sh /usr/local/bin/health_check
```
這樣可以在任何地方執行:
```bash
health_check
backup_all status
backup_all restore sftpgo 20260321_101928
```
---
## 6. 報警規則
| 層級 | 異常類型 | 等級 | 處理 |
|------|----------|------|------|
| Service | PostgreSQL 未運行 | Critical | 記錄 + 通知 |
| Service | Redis 未運行 | Critical | 記錄 + 通知 |
| Service | n8n API 無回應 | Critical | 記錄 + 通知 |
| Service | SFTPGo API 無回應 | Critical | 記錄 + 通知 |
| Service | Qdrant 未運行 | Critical | 記錄 + 通知 |
| Service | Momentry CLI 缺失 | Critical | 記錄 + 通知 |
| Database | 關鍵資料庫不存在 | Warning | 記錄 |
| Backup | 備份失敗 | Critical | 記錄 |
| Backup | 備份過期 | Warning | 記錄 |
| Job | 處理失敗 | Warning | 記錄 |
| Job | 處理超時 | Warning | 記錄 |
### 6.1 資料庫缺失處理
當檢查到以下資料庫不存在時,應記錄警告:
- `momentry` - Momentry Core 主資料庫
- `video_register` - 視頻註冊資料庫
- `n8n` - n8n 工作流資料庫
- `sftpgo` - SFTPGo 資料庫
**處理程序**:
1. 確認是否為首次安裝(資料庫尚未建立)
2. 檢查備份並執行還原
3. 如果是意外刪除,立即從最新備份恢復
### 6.2 SFTPGo 監控特殊注意
SFTPGo 使用雙重認證檢查:
1. **健康檢查**: `/healthz` (無需認證)
2. **API 可用性**: `/api/v2/token` (需要 Basic Auth)
`/healthz` 正常但 `/api/v2/token` 失敗,可能是:
- admin 密碼被重置
- 數據庫連接問題
- API 配置錯誤
應立即檢查:
```bash
tail -20 /Users/accusys/momentry/log/sftpgo.log
tail -20 /Users/accusys/momentry/log/sftpgo.error.log
```
---
## 7. 數據庫記錄
### monitor_jobs 表
```sql
CREATE TABLE monitor_jobs (
id SERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
video_path VARCHAR(512),
status VARCHAR(20),
current_processor VARCHAR(20),
progress_total INT,
progress_current INT,
error_count INT DEFAULT 0,
last_error TEXT,
started_at TIMESTAMP,
updated_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_jobs_uuid ON monitor_jobs(uuid);
CREATE INDEX idx_monitor_jobs_status ON monitor_jobs(status);
CREATE INDEX idx_monitor_jobs_created_at ON monitor_jobs(created_at);
```
### processor_results 表
```sql
CREATE TABLE processor_results (
id SERIAL PRIMARY KEY,
job_id INTEGER REFERENCES monitor_jobs(id) ON DELETE CASCADE,
video_id BIGINT REFERENCES videos(id),
processor VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
started_at TIMESTAMP,
completed_at TIMESTAMP,
error_message TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_processor_results_job ON processor_results(job_id);
CREATE INDEX idx_processor_results_video ON processor_results(video_id);
CREATE INDEX idx_processor_results_status ON processor_results(status);
```
**processor 狀態值**:
- `pending` - 等待處理
- `running` - 處理中
- `completed` - 已完成
- `failed` - 處理失敗
- `skipped` - 跳過(已在其他處理中完成)
---
## 8. 環境變數
```bash
# 輸出目錄
MOMENTRY_OUTPUT_DIR=/path/to/output
# 備份
MOMENTRY_BACKUP_ENABLED=true
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry
# Redis
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=accusys
```
---
## 9. 待實作項目
| # | 項目 | 狀態 | 更新日期 |
|---|------|------|---------|
| 1 | 實作 Redis Pub/Sub 客戶端 | ✅ 已實作 | 2026-03-21 |
| 2 | 修改 Python processors 使用 Redis | ✅ 已實作 | 2026-03-21 |
| 3 | 設計並實現 health_check.sh | ✅ 已實作 | 2026-03-22 |
| 4 | 創建 monitor_jobs 表 | ✅ 已實作 | 2026-03-21 |
| 5 | SFTPGo 備份與還原流程 | ✅ 已實作 | 2026-03-22 |
| 6 | SFTPGo API 管理工具 | ✅ 已實作 | 2026-03-22 |
| 7 | SFTPGo Hook 自動註冊 | ✅ 已實作 | 2026-03-22 |
| 8 | 文檔化監控規範 | ✅ 已實作 | 2026-03-22 |
| 9 | Job Worker 輪詢機制 | ✅ 已實作 | 2026-03-25 |
| 10 | processor_results 表 | ✅ 已實作 | 2026-03-25 |
| 11 | Probe API 端點 | ✅ 已實作 | 2026-03-25 |
| 12 | 整合測試 | 🔜 待實作 | - |
| 13 | 生產環境部署驗證 | ⏳ 待開始 | - |
### Job Worker 監控 (2026-03-25 新增)
**Worker 服務狀態檢查**:
```bash
# 檢查 Worker 程序
ps aux | grep momentry
# 查看 Worker 日誌
tail -f /Users/accusys/momentry/log/worker.log
```
**monitor_jobs 狀態查詢**:
```bash
# 查看待處理工作
psql -U accusys -d momentry -c "SELECT * FROM monitor_jobs WHERE status = 'pending';"
# 查看執行中工作
psql -U accusys -d momentry -c "SELECT * FROM monitor_jobs WHERE status = 'running';"
# 查看失敗工作
psql -U accusys -d momentry -c "SELECT * FROM monitor_jobs WHERE status = 'failed';"
```
**processor_results 狀態查詢**:
```bash
# 查看特定工作的處理器狀態
psql -U accusys -d momentry -c "
SELECT pr.*, mj.uuid
FROM processor_results pr
JOIN monitor_jobs mj ON pr.job_id = mj.id
WHERE mj.uuid = 'a1b10138a6bbb0cd';
"
# 查看所有失敗的處理器
psql -U accusys -d momentry -c "
SELECT pr.processor, COUNT(*) as failures
FROM processor_results pr
WHERE pr.status = 'failed'
GROUP BY pr.processor;
"
```
**Redis 工作狀態**:
```bash
# 查看活躍工作
redis-cli SMEMBERS momentry:jobs:active
# 查看工作詳情
redis-cli HGETALL momentry:job:{uuid}
```
### 已完成實作 (2026-03-22)
**監控系統**:
- 完整健康檢查腳本設計: `docs/MOMENTRY_CORE_MONITORING.md`
- 多層次服務監控 (Layer 2: Service, Layer 7: Backup)
- Redis Job 監控腳本: `monitor/service/redis_job_monitor.sh`
- SFTPGo 特殊監控 (API 認證檢查)
**SFTPGo 管理**:
- 備份還原機制: `backup_all.sh` (第 325-546 行)
- API 管理用戶與組 (完整文件於 `docs/INSTALL_SFTPGO.md`)
- Hook 自動註冊流程: `/Users/accusys/sftpgo_test/register_hook.sh`
- Demo 用戶與組完整測試環境
**文檔更新**:
- `docs/INSTALL_SFTPGO.md`: 新增備份還原、API管理、Hook配置章節
- `docs/MOMENTRY_CORE_MONITORING.md`: 完善監控規範
### 待驗證功能
- [ ] 端到端測試: SFTP 上傳 → Hook → Momentry 註冊 → n8n 工作流
- [ ] Momentry Core API 搜索功能: `GET /api/v1/searchable`
- [ ] 背景處理自動觸發: `cargo run -- process <uuid>`
---
## 10. 參考文檔
- [Redis Key 設計規範](./MOMENTRY_CORE_REDIS_KEYS.md)
- [監控系統總覽](../monitor/MONITORING.md)
- [備份規範](./SERVICE_ADDITION_GUIDE.md)
- [SFTPGo 安裝與管理指南](./INSTALL_SFTPGO.md)
- [API 參考文件](../docs/API_REFERENCE.md)
- [n8n 整合指南](./N8N_INTEGRATION_GUIDE.md)
-283
View File
@@ -1,283 +0,0 @@
# Momentry Core Redis Key 設計規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-25 | 新增可配置 Redis Key Prefix | Warren | OpenCode / GLM-5 |
---
## 1. 概述
本文檔說明 momentry_core 如何使用 Redis 作為監控和狀態管理系統。
## 2. 可配置 Redis Key Prefix
### 2.1 環境變數
從 V1.1 開始,所有 Redis Keys 都支援自定義前綴:
```bash
MOMENTRY_REDIS_PREFIX=momentry:
```
此設定允許多個 momentry 實例共用同一個 Redis 伺服器,例如:
- **生產環境**: `MOMENTRY_REDIS_PREFIX=momentry:`
- **開發環境**: `MOMENTRY_REDIS_PREFIX=momentry_dev:`
### 2.2 Key 格式
所有 Key 都遵循以下格式:
```
{prefix}{key_type}:{uuid}
```
範例 (生產環境):
```
momentry:job:5dea6618a606e7c7
momentry:jobs:active
momentry:health:current
```
範例 (開發環境):
```
momentry_dev:job:5dea6618a606e7c7
momentry_dev:jobs:active
momentry_dev:health:current
```
### 2.3 預設值
| Binary | 預設 Port | 預設 Redis Prefix |
|--------|-----------|-------------------|
| `momentry` (生產) | 3002 | `momentry:` |
| `momentry_playground` (開發) | 3003 | `momentry_dev:` |
## 3. UUID 使用時機
### 3.1 全局 Keys(無 UUID
- 單一實例全局狀態
- 聚合統計數據
### 3.2 Per-Video KeysUUID 必要)
- 每個視頻獨立處理狀態
- 即時進度追蹤
### 3.3 Per-Processor KeysUUID + Processor 必要)
- 每個 processor 獨立狀態
## 4. Key 命名空間
```
momentry
├── health: # 健康檢查
│ ├── current # 當前狀態 (TTL: 60s)
│ └── services # 依賴服務狀態
├── config: # 配置
├── stats: # 聚合統計
│ ├── total_jobs # 總 Jobs 數
│ ├── processed_today # 今日處理數
│ ├── cpu:current # 當前 CPU 使用
│ └── memory:current # 當前 Memory 使用
├── jobs: # Jobs 管理
│ ├── active # Set: 運行中 UUIDs
│ ├── completed # Set: 完成 UUIDs
│ └── failed # Set: 失敗 UUIDs
├── job:{uuid} # Per-Video Job 狀態 (TTL: 24h)
│ ├── status # 狀態 String
│ ├── video_path # 視頻路徑
│ ├── current_processor # 當前 processor
│ ├── progress_total # 總進度
│ ├── progress_current # 當前進度
│ ├── started_at # 開始時間
│ ├── updated_at # 最後更新
│ └── processor:{name} # Per-Processor 狀態
│ ├── status
│ ├── progress
│ ├── current
│ ├── total
│ └── started_at
├── progress:{uuid} # Pub/Sub 頻道 (即時進度)
├── result:{uuid} # 處理結果 Hash
├── output:{uuid} # 輸出路徑
├── metrics:{uuid} # Per-Video 指標
│ ├── cpu # CPU 歷史 List (100條, TTL: 1h)
│ ├── memory # Memory 歷史 List (100條, TTL: 1h)
│ └── duration # 處理時長
└── log:{uuid} # 處理日誌 String
```
## 5. Key 詳細說明
### 全局 Keys
| Key | Type | TTL | 說明 |
|-----|------|-----|------|
| `momentry:health:current` | String | 60s | 當前健康狀態 |
| `momentry:health:services` | Hash | 60s | 依賴服務健康狀態 |
| `momentry:stats:total_jobs` | String | - | 總 Jobs 數 |
| `momentry:stats:processed_today` | String | 86400s | 今日處理數 |
| `momentry:stats:cpu:current` | String | 10s | 當前 CPU 使用 |
| `momentry:stats:memory:current` | String | 10s | 當前 Memory 使用 |
| `momentry:jobs:active` | Set | - | 運行中 Job UUIDs |
| `momentry:jobs:completed` | Set | - | 完成 Job UUIDs |
| `momentry:jobs:failed` | Set | - | 失敗 Job UUIDs |
### Per-Video Keys
| Key | Type | TTL | 說明 |
|-----|------|-----|------|
| `momentry:job:{uuid}` | Hash | 24h | Job 完整狀態 |
| `momentry:job:{uuid}:status` | String | 24h | Job 狀態 |
| `momentry:progress:{uuid}` | Pub/Sub | - | 即時進度頻道 |
| `momentry:result:{uuid}` | Hash | 24h | 處理結果 |
| `momentry:output:{uuid}` | String | 24h | 輸出路徑 |
| `momentry:metrics:{uuid}:cpu` | List | 1h | CPU 歷史 (100條) |
| `momentry:metrics:{uuid}:memory` | List | 1h | Memory 歷史 (100條) |
| `momentry:metrics:{uuid}:duration` | String | 24h | 處理時長 |
| `momentry:log:{uuid}` | String | 24h | 處理日誌 |
### Per-Processor Keys
| Key | Type | TTL | 說明 |
|-----|------|-----|------|
| `momentry:job:{uuid}:processor:{name}` | Hash | 24h | Processor 狀態 |
| `momentry:job:{uuid}:processor:{name}:status` | String | 24h | 狀態 |
| `momentry:job:{uuid}:processor:{name}:progress` | String | 24h | 進度百分比 |
| `momentry:job:{uuid}:processor:{name}:current` | String | 24h | 當前項目 |
| `momentry:job:{uuid}:processor:{name}:total` | String | 24h | 總項目數 |
| `momentry:job:{uuid}:processor:{name}:started_at` | String | 24h | 開始時間 |
## 6. TTL 策略
| Key 類型 | TTL | 原因 |
|----------|-----|------|
| Health | 60s | 需要定期更新 |
| Job | 24h | 處理完成後保留一天 |
| Processor | 24h | 處理完成後保留一天 |
| Metrics | 1h | 只保留近期歷史 |
| Progress Pub/Sub | - | 不持久,僅即時訊息 |
| Stats | 無 | 持久統計 |
## 7. 訊息格式
### Pub/Sub 訊息 (progress:{uuid})
```json
{
"type": "info | progress | complete | error",
"processor": "yolo | ocr | face | pose | cut | asr | asrx",
"timestamp": 1700000000,
"data": {
"message": "Processing frame 5000",
"current": 5000,
"total": 14315
}
}
```
### Job 狀態 Hash
```json
{
"uuid": "5dea6618a606e7c7",
"video_path": "/path/to/video.mp4",
"status": "running",
"current_processor": "yolo",
"progress_total": 70,
"progress_current": 50,
"started_at": 1700000000,
"updated_at": 1700000100,
"error_count": 0,
"last_error": ""
}
```
### Processor 狀態 Hash
```json
{
"name": "yolo",
"status": "running",
"progress": 70,
"current_frame": 10000,
"total_frames": 14315,
"started_at": 1700000000,
"updated_at": 1700000100
}
```
## 8. 實作函數 (Rust)
所有 Redis Key 生成函數使用 `REDIS_KEY_PREFIX` 靜態變數:
```rust
use crate::core::config::REDIS_KEY_PREFIX;
fn global_key(key: &str) -> String {
format!("{}{}", REDIS_KEY_PREFIX, key)
}
fn job_key(uuid: &str) -> String {
format!("{}job:{}", REDIS_KEY_PREFIX, uuid)
}
fn processor_key(uuid: &str, processor: &str) -> String {
format!("{}job:{}:processor:{}", REDIS_KEY_PREFIX, uuid, processor)
}
fn progress_channel(uuid: &str) -> String {
format!("{}progress:{}", REDIS_KEY_PREFIX, uuid)
}
fn metrics_key(uuid: &str, metric: &str) -> String {
format!("{}metrics:{}:{}", REDIS_KEY_PREFIX, uuid, metric)
}
fn jobs_set_key(status: &str) -> String {
format!("{}jobs:{}", REDIS_KEY_PREFIX, status)
}
```
**注意**: `REDIS_KEY_PREFIX` 定義於 `src/core/config.rs`,由環境變數 `MOMENTRY_REDIS_PREFIX` 控制。
## 9. 環境變數
```bash
# Redis 連接
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=accusys
REDIS_DB=0
# Redis Key Prefix (可選,預設: momentry:)
MOMENTRY_REDIS_PREFIX=momentry:
# 生產環境範例 (.env)
MOMENTRY_SERVER_PORT=3002
MOMENTRY_REDIS_PREFIX=momentry:
# 開發環境範例 (.env.development)
MOMENTRY_SERVER_PORT=3003
MOMENTRY_REDIS_PREFIX=momentry_dev:
```
## 11. 監控腳本
使用 Redis 進行監控的腳本應參考:
- `monitor/service/momentry_redis_monitor.sh` - Redis 健康檢查
- `monitor/service/momentry_job_monitor.sh` - Job 狀態監控
-433
View File
@@ -1,433 +0,0 @@
# Node.js 開發指南
## 概述
本文檔說明 Momentry 專案中 Node.js 環境的配置、管理與監控。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| 系統預設 Node.js | v25.8.1 |
| 鎖定 Node.js (n8n) | v22.22.1 |
| n8n 版本 | v2.3.5 |
| npm 路徑 (預設) | /opt/homebrew/bin/npm |
| node 路徑 (預設) | /opt/homebrew/bin/node |
| node@22 路徑 | /opt/homebrew/opt/node@22/bin/node |
---
## 版本策略
### 為什麼需要版本鎖定?
n8n 要求 Node.js 版本為 22.x LTS。系統預設的 Node.js 25.x 會導致:
- n8n 啟動警告
- Task-runner 執行錯誤
- 相容性問題
### 版本對照表
| 用途 | Node.js 版本 | 路徑 |
|------|-------------|------|
| 系統預設 | 25.8.1 | /opt/homebrew/bin/node |
| n8n 專用 | 22.22.1 | /opt/homebrew/opt/node@22/bin/node |
| 開發測試 | 22.x | /opt/homebrew/opt/node@22/bin/node |
---
## 安裝步驟
### 安裝特定版本 Node.js
```bash
# 安裝 Node.js 22.x (LTS)
brew install node@22
# 驗證安裝
/opt/homebrew/opt/node@22/bin/node --version
# v22.22.1
```
### 安裝全局工具
```bash
# 使用 node@22 安裝全局工具
/opt/homebrew/opt/node@22/bin/npm install -g <package-name>
# 例如安裝 n8n
/opt/homebrew/opt/node@22/bin/npm install -g n8n
```
---
## 使用方式
### 方式 1:直接使用完整路徑
```bash
# 使用 node@22
/opt/homebrew/opt/node@22/bin/node script.js
# 使用 npm@22
/opt/homebrew/opt/node@22/bin/npm install
```
### 方式 2:修改 PATH 環境變數
在 shell 配置檔案 (~/.zshrc) 中加入:
```bash
# 優先使用 node@22
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
# 重新載入
source ~/.zshrc
# 驗證
node --version
# v22.22.1
```
### 方式 3:使用 nvm (推薦)
```bash
# 安裝 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 安裝 Node.js 22.x
nvm install 22
nvm use 22
# 設定預設版本
nvm alias default 22
```
---
## n8n 配置
### 環境變數設定
在 plist 或啟動腳本中設定:
```xml
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/node@22/bin/node</string>
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
```
### Task Runner 配置
n8n Task Runner 用於執行 Code node 中的 JavaScript 代碼。
確保 PATH 中 node@22 在前面,這樣 task-runner 子進程會使用正確版本。
---
## 監控配置
### 健康檢查
`monitor/service/health_check.sh` 已包含 Node.js 版本檢查:
```bash
# 檢查 n8n 進程是否使用正確的 Node.js 版本
check_node() {
local LOCKED_NODE_VERSION="22"
# ...
}
```
### 執行監控
```bash
# 單獨檢查 Node.js
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check node
# 檢查 Python
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check python
```
### 查看版本狀態
```bash
# 查詢資料庫中的版本記錄
psql -U accusys -h localhost -d momentry -c "SELECT * FROM node_version_baseline;"
```
---
## 應用Registry
記錄系統中所有使用 Node.js 的應用程式。
| 應用 | Node.js 版本 | 執行路徑 | Port | 狀態 | 說明 |
|------|-------------|----------|------|------|------|
| n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5690 | ✅ 執行中 | 工作流自動化平台 |
| - | - | - | - | - | 新增應用請填入此表 |
---
## 新增 Node.js 應用決策
### 決策樹
```
新應用需要 Node.js?
├─ 支援 Node.js 22.x ────→ 使用現有 node@22
│ (路徑: /opt/homebrew/opt/node@22/bin/node)
├─ 需要特定版本 (如 18.x, 20.x) ────→ 安裝新版本 node@XX
│ │
│ ├─ 建立獨立目錄: /opt/homebrew/opt/node@XX/
│ ├─ 更新本文檔 Registry
│ └─ 建立獨立 plist 使用完整路徑
└─ 需要最新版本 ────→ 使用系統預設 node@25
(路徑: /opt/homebrew/bin/node)
```
### 步驟清單
1. **確認版本需求**
```bash
# 查看應用支援的 Node.js 版本
# 通常在 package.json 或官方文件中說明
```
2. **選擇現有版本或安裝新版本**
- 若支援 22.x → 使用 node@22
- 若需要特定版本 → 使用完整路徑隔離
3. **安裝新版本 (如需要)**
```bash
# 安裝特定版本
brew install node@20
# 或
brew install node@18
# 驗證安裝
/opt/homebrew/opt/node@20/bin/node --version
```
4. **建立隔離的服務配置**
- 使用完整路徑,不要依賴 PATH
- plist 中明确指定 node 路徑
- 設定獨立的環境變數
5. **更新文件**
- 更新本文檔 Registry 表格
- 新增應用的安裝文檔
- 更新監控配置
### 隔離原則
| 原則 | 說明 |
|------|------|
| **完整路徑** | 使用 `/opt/homebrew/opt/node@XX/bin/node` 而非 `node` |
| **獨立 PATH** | plist 中設定隔離的 PATH 環境變數 |
| **獨立目錄** | 不同版本的 node 安裝在各自目錄 |
| **獨立全局套件** | 每個版本有自己的 node_modules |
---
## 版本衝突處理
### 常見衝突場景
| 場景 | 原因 | 解決方案 |
|------|------|----------|
| n8n 顯示版本警告 | 使用 Node.js 25.x 啟動 | 確認 plist 使用 node@22 路徑 |
| task-runner 執行錯誤 | 子進程繼承錯誤版本 | 在 plist 中設定正確的 PATH |
| 全域套件找不到 | 跨版本安裝 | 使用正確版本的 npm |
| Port 被佔用 | 多個應用使用相同 Port | 分配獨立 Port |
### 診斷命令
```bash
# 1. 查看程序使用的 node 版本
ps aux | grep node
# 2. 查看程序 PID 使用的執行檔
lsof -p <PID> | grep bin/node
# 3. 確認路徑優先順序
echo $PATH
# 4. 查看 launchctl 服務的環境變數
sudo launchctl list | grep <service-name>
# 5. 檢查 Port 佔用
lsof -i :<port-number>
```
### 預防措施
1. **每次新增服務都更新 Registry**
2. **使用完整路徑而非 PATH**
3. **建立服務時指定版本**
4. **定期檢查執行中的程序**
---
## 故障排除
### 問題:n8n 顯示 Node.js 版本警告
**原因**n8n 檢測到 Node.js 版本不是 22.x
**解決方案**
1. 確認 plist 中 ProgramArguments 使用正確路徑
2. 確認 PATH 環境變數中 node@22 在前面
3. 重載服務:
```bash
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
### 問題:Task Runner 使用錯誤版本
**原因**:PATH 環境變數設定不正確
**診斷**
```bash
# 查看 task-runner 進程使用的 node
ps aux | grep "task-runner"
lsof -p <PID> | grep "txt" | grep node
```
**解決方案**
更新 plist 中的 PATH,確保 `/opt/homebrew/opt/node@22/bin` 在最前面。
### 問題:npm 全域套件找不到
**原因**:使用錯誤的 node 版本安裝
**解決方案**
```bash
# 確認使用的是哪個 node
which node
node --version
# 使用正確版本重新安裝
/opt/homebrew/opt/node@22/bin/npm install -g <package>
```
---
## 版本切換腳本
建立快速切換腳本 `~/bin/switch-node.sh`
```bash
#!/bin/bash
VERSION=$1
case $VERSION in
22)
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
echo "切換到 Node.js 22.x"
;;
system|25)
export PATH="/opt/homebrew/bin:$PATH"
echo "切換到系統 Node.js 25.x"
;;
*)
echo "用法: $0 {22|system}"
echo " 22 - Node.js 22.x (n8n)"
echo " system - Node.js 25.x (系統預設)"
exit 1
;;
esac
node --version
```
賦予執行權限:
```bash
chmod +x ~/bin/switch-node.sh
```
使用方式:
```bash
~/bin/switch-node.sh 22 # 切換到 22.x
~/bin/switch-node.sh system # 切換到系統預設
```
---
## 常用指令
```bash
# 查看 node 版本
node --version
# 查看 npm 版本
npm --version
# 查看 node 安裝位置
which node
# 查看 node@22 版本
/opt/homebrew/opt/node@22/bin/node --version
# 安裝全域套件 (使用 node@22)
/opt/homebrew/opt/node@22/bin/npm install -g n8n
# 檢查 n8n 進程
ps aux | grep n8n | grep -v grep
# 檢查 task-runner
ps aux | grep task-runner | grep -v grep
# 查看 task-runner 使用的 node 版本
lsof -p <PID> | grep "txt" | grep node
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| node (系統) | /opt/homebrew/bin/node | 預設 node |
| node@22 | /opt/homebrew/opt/node@22/bin/node | n8n 專用 |
| npm (系統) | /opt/homebrew/bin/npm | 預設 npm |
| npm@22 | /opt/homebrew/opt/node@22/bin/npm | n8n 專用 |
| n8n 安裝 | /opt/homebrew/lib/node_modules/n8n/ | n8n 目錄 |
| n8n main plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist | 開機啟動 |
| n8n worker plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist | 開機啟動 |
---
## 版本速查
| 版本 | 用途 | 路徑 |
|------|------|------|
| 22.22.1 | n8n 專用 | /opt/homebrew/opt/node@22/bin/node |
| 25.x | 系統預設 | /opt/homebrew/bin/node |
---
## 相關文件
- [INSTALL_N8N.md](./INSTALL_N8N.md) - n8n 安裝指南 (包含 Node.js 配置範例)
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
- [node_monitor.sh](../monitor/service/node_monitor.sh) - Node.js 監控腳本
-376
View File
@@ -1,376 +0,0 @@
# Playground Binary Implementation Plan
## Overview
Create separate `momentry_playground` binary with distinct configuration from `momentry` (production).
| Aspect | Production (`momentry`) | Development (`momentry_playground`) |
|--------|------------------------|-------------------------------------|
| **Port** | 3002 | 3003 |
| **Redis Prefix** | `momentry:` | `momentry_dev:` |
| **Worker** | Enabled | Disabled |
| **Purpose** | Production deployment | Testing/Development |
---
## Files to Modify
```
Files Changed: 6 files (+1 new)
├── src/core/config.rs ← Add server_port(), redis_key_prefix()
├── src/core/db/redis_client.rs ← Replace hardcoded prefixes
├── src/core/cache/redis_cache.rs ← Use configurable prefix
├── src/main.rs ← Update CLI defaults
├── src/playground.rs ← NEW: Development binary
├── Cargo.toml ← Add new binary
└── .env.development ← NEW: Dev environment config
```
---
## Implementation Steps
### Step 1: Update `src/core/config.rs`
Add after line 51 (after `MEDIA_BASE_URL`):
```rust
pub static SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("MOMENTRY_SERVER_PORT")
.unwrap_or_else(|_| "3002".to_string())
.parse()
.unwrap_or(3002)
});
pub static REDIS_KEY_PREFIX: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_REDIS_PREFIX")
.unwrap_or_else(|_| "momentry:".to_string())
});
```
---
### Step 2: Update `src/core/db/redis_client.rs`
Replace all hardcoded `momentry:` prefixes with configurable prefix.
**Import at top:**
```rust
use crate::core::config::REDIS_KEY_PREFIX;
```
**Pattern for each method:**
```rust
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}job:{}", prefix, uuid);
```
**Affected lines:**
| Line | Key Pattern |
|------|-------------|
| 47 | `job:{uuid}` |
| 81, 109 | `job:{uuid}:processor:{processor}` |
| 136, 146 | `progress:{uuid}` |
| 172 | `jobs:active` |
| 179 | `jobs:active``jobs:completed` |
| 187 | `jobs:active``jobs:failed` |
| 194 | `jobs:active` |
| 201, 208 | `health:momentry_core` |
| 214 | `monitor:job:{uuid}` |
| 242, 300 | `errors:{uuid}` |
| 258, 281 | `anomaly:alerts`, `anomaly:key:{key_id}` |
| 317, 346, 364, 392, 397 | `worker:job:{uuid}...` |
| 406, 410 | `worker:job:*` |
---
### Step 3: Update `src/core/cache/redis_cache.rs`
**Import:**
```rust
use crate::core::config::REDIS_KEY_PREFIX;
```
**Replace line 10:**
```rust
// Remove: const KEY_PREFIX: &str = "momentry:cache:";
```
**Update `prefixed_key` method (line 24):**
```rust
fn prefixed_key(&self, key: &str) -> String {
format!("{}cache:{}", REDIS_KEY_PREFIX.as_str(), key)
}
```
**Update tests (lines 161-162):**
```rust
#[test]
fn test_prefixed_key() {
// Note: This test will use the configured prefix
let cache = RedisCache::new().unwrap();
// With default prefix "momentry:"
assert_eq!(cache.prefixed_key("test"), "momentry:cache:test");
assert_eq!(cache.prefixed_key("video:abc"), "momentry:cache:video:abc");
}
```
---
### Step 4: Update `src/main.rs`
**Change CLI defaults (Lines 691-695):**
```rust
// Before:
#[arg(long, default_value = "3000")]
port: u16,
// After:
#[arg(long)]
port: Option<u16>,
```
**Update Server match arm (around line 2398):**
```rust
Commands::Server { host, port } => {
let port = port.unwrap_or_else(|| *crate::core::config::SERVER_PORT);
momentry_core::api::start_server(&host, port).await?;
Ok(())
}
```
**Update Redis key usage (Line 1098):**
```rust
// Before:
let key = format!("momentry:job:{}:processor:{}", uuid, processor);
// After:
let key = format!(
"{}job:{}:processor:{}",
crate::core::config::REDIS_KEY_PREFIX.as_str(),
uuid,
processor
);
```
---
### Step 5: Create `src/playground.rs`
```rust
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
// ... same imports as main.rs ...
fn main() -> Result<()> {
// Load development environment first
dotenv::from_filename(".env.development").ok();
tracing_subscriber::fmt::init();
tracing::info!("Starting momentry_playground (development binary)");
tracing::info!("Port: {}", *momentry_core::core::config::SERVER_PORT);
tracing::info!("Redis prefix: {}", *momentry_core::core::config::REDIS_KEY_PREFIX);
let cli = Cli::parse();
// ... rest identical to main.rs ...
}
```
---
### Step 6: Update `Cargo.toml`
**Add after line 90:**
```toml
[[bin]]
name = "momentry_playground"
path = "src/playground.rs"
```
**Add dependency (if not present):**
```toml
dotenv = "0.15"
```
---
### Step 7: Create `.env.development`
```bash
# Development Environment Configuration
# Used by: momentry_playground binary
# Server Configuration
MOMENTRY_SERVER_PORT=3003
MOMENTRY_REDIS_PREFIX=momentry_dev:
# Worker Configuration (disabled for development)
MOMENTRY_WORKER_ENABLED=false
MOMENTRY_MAX_CONCURRENT=1
MOMENTRY_POLL_INTERVAL=10
# Database (can use separate dev database)
DATABASE_URL=postgres://accusys@localhost:5432/momentry
MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
# Redis
REDIS_URL=redis://:accusys@localhost:6379
```
---
### Step 8: Update `.env` (Production)
Add these lines:
```bash
# Production Environment Configuration
# Used by: momentry binary
# Server Configuration
MOMENTRY_SERVER_PORT=3002
MOMENTRY_REDIS_PREFIX=momentry:
# Worker Configuration
MOMENTRY_WORKER_ENABLED=true
MOMENTRY_MAX_CONCURRENT=2
MOMENTRY_POLL_INTERVAL=5
```
---
## Testing Checklist
### 1. Build and Run Production Binary
```bash
cargo build --release --bin momentry
cargo run --bin momentry -- server
# Expected: Listening on http://127.0.0.1:3002
cargo run --bin momentry -- worker
# Expected: Worker started with momentry: prefix
```
### 2. Build and Run Development Binary
```bash
cargo build --bin momentry_playground
cargo run --bin momentry_playground -- server
# Expected: Listening on http://127.0.0.1:3003
```
### 3. Verify Redis Key Isolation
```bash
# Production data
redis-cli KEYS "momentry:*"
# Development data
redis-cli KEYS "momentry_dev:*"
# Should be separate
```
### 4. Run Both Simultaneously
```bash
# Terminal 1: Production
cargo run --bin momentry -- server
# Terminal 2: Development
cargo run --bin momentry_playground -- server
# Both should run without port conflicts
```
### 5. Unit Tests
```bash
cargo test --lib
# All tests should pass
```
---
## Redis Key Structure
### Production (`momentry:`)
```
momentry:job:{uuid} # Job status
momentry:job:{uuid}:processor:{name} # Processor progress
momentry:progress:{uuid} # Progress pub/sub
momentry:jobs:active # Active job set
momentry:jobs:completed # Completed job set
momentry:jobs:failed # Failed job set
momentry:health:momentry_core # Health status
momentry:cache:{key} # Cache entries
momentry:worker:job:{uuid} # Worker job
momentry:worker:job:{uuid}:processor:{name}
```
### Development (`momentry_dev:`)
```
momentry_dev:job:{uuid}
momentry_dev:job:{uuid}:processor:{name}
momentry_dev:progress:{uuid}
momentry_dev:jobs:active
momentry_dev:jobs:completed
momentry_dev:jobs:failed
momentry_dev:health:momentry_core
momentry_dev:cache:{key}
momentry_dev:worker:job:{uuid}
momentry_dev:worker:job:{uuid}:processor:{name}
```
---
## Potential Issues & Solutions
| Issue | Solution |
|-------|----------|
| `dotenv` crate not in dependencies | Add to Cargo.toml |
| Tests use hardcoded prefix | Update tests to use config, or use `#[cfg(test)]` defaults |
| Worker starts in playground | Check `MOMENTRY_WORKER_ENABLED=false` in `.env.development` |
| Port already in use | Graceful error message with suggestion to use `--port` flag |
| Mixed data in Redis | Ensure prefix is loaded before any Redis operations |
---
## Files Summary
| File | Lines Changed | Purpose |
|------|---------------|---------|
| `src/core/config.rs` | +15 | Add SERVER_PORT and REDIS_KEY_PREFIX |
| `src/core/db/redis_client.rs` | ~50 | Replace hardcoded prefixes |
| `src/core/cache/redis_cache.rs` | ~10 | Use configurable prefix |
| `src/main.rs` | ~15 | Update CLI defaults, Redis key usage |
| `src/playground.rs` | NEW (~2800) | Development binary |
| `Cargo.toml` | +4 | Add binary definition |
| `.env.development` | NEW (~20) | Development environment |
**Total**: ~60 lines modified + ~2800 lines new file
---
## Reference Documents
| Document | Purpose |
|----------|---------|
| `docs/SERVICES.md` | Port allocations |
| `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis key design |
| `AGENTS.md` | Code style and conventions |
---
## Version History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2025-03-25 | OpenCode | Initial implementation plan |
-494
View File
@@ -1,494 +0,0 @@
# Python 開發規範
## 概述
本文檔定義 Momentry 專案中 Python 程式碼的開發標準與最佳實踐。
---
## 版本管理
### 鎖定版本
| 版本 | 用途 | 路徑 |
|------|------|------|
| Python 3.11.14 | Momentry venv | /Users/accusys/momentry_core_0.1/venv/bin/python |
| Python 3.11.14 | 系統安裝 | /opt/homebrew/bin/python3.11 |
| Python 3.14.3 | 系統預設 | /opt/homebrew/bin/python3 |
| Python 3.9.6 | 系統預設 (備用) | /usr/bin/python3 |
### 版本選擇原則
- **Momentry 專案**:使用 venv 中的 Python 3.11.14
- **新專案**:建議使用 venv 管理
- **系統工具**:可使用系統預設版本
---
## 腳本規範
### Shebang 宣告
所有 Momentry Python 腳本必須在第一行宣告明確的 Python 路徑:
```python
#!/opt/homebrew/bin/python3.11
```
**錯誤範例**
```python
#!/usr/bin/env python3 # 會解析到系統預設 (3.14.3)
#!/usr/bin/python3 # 會使用系統 Python (3.9.6)
```
**正確範例**
```python
#!/opt/homebrew/bin/python3.11
import sys
...
```
### 檔案結構
```
scripts/
├── asr_processor.py # ASR 處理腳本
├── thumbnail_extractor.py # 縮圖提取腳本
└── new_script.py # 新腳本模板
```
### 腳本模板
```python
#!/opt/homebrew/bin/python3.11
"""
腳本名稱
簡短描述腳本功能
用法:
python3.11 script.py <args>
作者: Momentry Team
版本: 1.0.0
"""
import argparse
import json
import logging
import sys
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger(__name__)
def main():
parser = argparse.ArgumentParser(description="腳本功能描述")
parser.add_argument("input", help="輸入檔案或參數")
parser.add_argument("-o", "--output", default="output.json", help="輸出檔案")
parser.add_argument("-v", "--verbose", action="store_true", help="詳細輸出")
parser.add_argument("-c", "--count", type=int, default=10, help="數量")
args = parser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)
# 業務邏輯
result = process_data(args.input, args.count)
# 輸出 JSON結果
print(json.dumps(result))
def process_data(input_path: str, count: int) -> dict:
"""處理資料並返回結果"""
logger.info(f"Processing: {input_path}")
# TODO: 實作業務邏輯
return {
"status": "success",
"input": input_path,
"count": count,
}
if __name__ == "__main__":
main()
```
---
## 與 Rust 整合
### 使用 venv (目前採用)
Momentry 使用 venv 管理 Python 環境,避免與系統其他程式衝突。
#### 建立 venv
```bash
# 建立虛擬環境
cd /Users/accusys/momentry_core_0.1
/opt/homebrew/bin/python3.11 -m venv venv
# 啟用虛擬環境
source venv/bin/activate
# 安裝依賴
pip install -r requirements.txt
```
#### 從 Rust 呼叫 venv Python
```rust
use std::path::Path;
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("scripts")
.join("asr_processor.py");
// 使用 venv 中的 Python
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("venv")
.join("bin")
.join("python");
let output = Command::new(venv_python)
.arg(script_path)
.arg(video_path)
.output()
.context("Failed to run processor")?;
```
**優點**
- 專案依賴隔離
- 不同專案可使用不同 Python 版本
- 易於重現環境
- 不影響系統其他程式
---
## 依賴管理
### venv 目錄結構
```
momentry_core_0.1/
├── venv/ # 虛擬環境
│ ├── bin/
│ │ ├── python # Python 3.11.14
│ │ ├── pip
│ │ └── ...
│ └── lib/python3.11/ # 安裝的套件
├── requirements.txt # 依賴列表
├── scripts/ # Python 腳本
│ ├── asr_processor.py
│ └── thumbnail_extractor.py
└── src/ # Rust 程式碼
```
### 使用虛擬環境
```bash
# 啟用虛擬環境
source venv/bin/activate
# 安裝依賴
pip install faster-whisper
# 退出虛擬環境
deactivate
```
# 退出虛擬環境
deactivate
```
### 依賴列表格式
建立 `requirements.txt`
```
faster-whisper>=1.0.0
ffmpeg-python>=0.2.0
Pillow>=10.0.0
```
### 安裝專案依賴
```bash
# 使用 python3.11 安裝
/opt/homebrew/bin/python3.11 -m pip install -r requirements.txt
```
---
## 程式碼規範
### Import 排序
```python
# 1. 標準庫
import sys
import os
import json
import logging
from pathlib import Path
from typing import Optional
# 2. 第三方庫
import numpy as np
import pandas as pd
from faster_whisper import WhisperModel
# 3. 本地模組
from . import local_module
from ..package import module
```
### 命名規範
| 類型 | 規範 | 範例 |
|------|------|------|
| 模組/檔案 | snake_case | `asr_processor.py` |
| 類別 | PascalCase | `class VideoProcessor` |
| 函數/變數 | snake_case | `def process_video()` |
| 常量 | UPPER_SNAKE_CASE | `MAX_WORKERS = 4` |
| 私有成員 | _leading_underscore | `_private_method()` |
### 類型提示
```python
from typing import Optional, List, Dict
def process_video(
video_path: str,
options: Optional[Dict[str, int]] = None,
) -> List[Dict[str, float]]:
"""處理影片並返回結果"""
...
```
### 錯誤處理
```python
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
def process_video(video_path: str) -> dict:
path = Path(video_path)
if not path.exists():
logger.error(f"Video file not found: {video_path}")
raise FileNotFoundError(f"Video not found: {video_path}")
try:
result = _do_process(path)
logger.info(f"Processed successfully: {path}")
return result
except Exception as e:
logger.exception(f"Processing failed: {e}")
raise
```
### 日誌規範
```python
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger(__name__)
# 使用說明
logger.info("Starting process...")
logger.debug(f"Input: {input_path}")
logger.warning(f"Using fallback: {reason}")
logger.error(f"Failed: {error}")
```
---
## 測試規範
### 測試結構
```
tests/
├── __init__.py
├── test_asr_processor.py
└── test_thumbnail_extractor.py
```
### 測試範例
```python
import pytest
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from asr_processor import run_asr
def test_asr_processor_with_valid_video(tmp_path):
video_path = tmp_path / "test.mp4"
output_path = tmp_path / "output.json"
# 建立測試影片
video_path.write_text("dummy")
# 執行
result = run_asr(str(video_path), str(output_path))
# 斷言
assert output_path.exists()
assert result["segments"]
def test_asr_processor_with_invalid_video():
with pytest.raises(FileNotFoundError):
run_asr("/nonexistent/video.mp4", "/tmp/output.json")
```
### 執行測試
```bash
# 使用 python3.11 執行測試
/opt/homebrew/bin/python3.11 -m pytest tests/ -v
# 包含覆蓋率
/opt/homebrew/bin/python3.11 -m pytest tests/ --cov=scripts
```
---
## 監控配置
### 監控腳本
`monitor/config/monitor_config.yaml` 中配置:
```yaml
service:
- name: "python"
type: "process"
process_name: "python3"
enabled: true
check_interval: 60
version_lock: "3.11.14"
scripts:
- "/Users/accusys/momentry_core_0.1/scripts/asr_processor.py"
- "/Users/accusys/momentry_core_0.1/scripts/thumbnail_extractor.py"
```
### 檢查版本
```bash
# 執行 Python 監控
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check python
# 查看資料庫記錄
psql -U accusys -h localhost -d momentry -c "SELECT * FROM python_version_baseline;"
```
---
## CI/CD 考量
### GitHub Actions 範例
```yaml
name: Python Tests
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run tests
run: |
python -m pytest tests/ -v
```
---
## 常見問題
### Q: 為什麼腳本要用 `#!/opt/homebrew/bin/python3.11` 而不是 `#!/usr/bin/env python3`
A: `#!/usr/bin/env python3` 會解析 PATH 中的第一個 `python3`,在 macOS 上可能是:
- `/opt/homebrew/bin/python3` (3.14.3)
- `/usr/bin/python3` (3.9.6)
明確指定路徑可確保使用正確版本。
### Q: Rust 呼叫 Python 腳本時如何確保版本正確?
A: 有三種方式:
1. Rust 程式碼中使用明確路徑:`Command::new("/opt/homebrew/bin/python3.11")`
2. 設定環境變數 PATH
3. 建立系統別名(不推薦,影響其他程式)
### Q: 如何管理多個 Python 版本?
A: 建議使用:
- **pyenv**:管理多個 Python 版本
- **venv**:隔離專案依賴
- **Docker**:容器化環境
---
## 檢查清單
新增 Python 腳本時確認:
- [ ] 使用 `#!/opt/homebrew/bin/python3.11` shebang
- [ ] 包含 docstring 說明功能
- [ ] 使用 argparse 處理命令行參數
- [ ] 使用 logging 進行日誌輸出
- [ ] 錯誤處理適當
- [ ] 類型提示完整
- [ ] 更新監控配置
- [ ] 建立測試案例
---
## 版本速查
| 版本 | 用途 | 路徑 |
|------|------|------|
| 3.11.14 | Momentry venv | /Users/accusys/momentry_core_0.1/venv/bin/python |
| 3.11.14 | 系統安裝 | /opt/homebrew/bin/python3.11 |
| 3.14.3 | 系統預設 | /opt/homebrew/bin/python3 |
---
## 相關文件
- [NODEJS.md](./NODEJS.md) - Node.js 開發指南
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
- [python_monitor.sh](../monitor/service/python_monitor.sh) - Python 監控腳本
-931
View File
@@ -1,931 +0,0 @@
# Rust 開發規範 - Momentry Core
本規範定義 Momentry Core 專案的 Rust 開發標準,確保程式碼品質與一致性。
## 1. 專案結構
### 1.1 目錄架構
```
src/
├── main.rs # CLI 入口點
├── lib.rs # 函式庫導出
├── cli/
│ ├── mod.rs
│ └── commands/ # CLI 命令模組
├── core/
│ ├── mod.rs
│ ├── chunk/ # 影片分段邏輯
│ │ ├── mod.rs
│ │ ├── splitter.rs
│ │ └── types.rs
│ ├── db/ # 資料庫抽象層
│ │ ├── mod.rs
│ │ ├── postgres_db.rs
│ │ ├── mongodb_db.rs
│ │ ├── redis_db.rs
│ │ └── qdrant_db.rs
│ ├── processor/ # 影片處理器
│ │ ├── mod.rs
│ │ ├── asr.rs # 語音識別
│ │ ├── asrx.rs # 說話者分離
│ │ ├── ocr.rs # 文字辨識
│ │ ├── yolo.rs # 物件偵測
│ │ ├── face.rs # 人臉偵測
│ │ └── pose.rs # 姿態估計
│ ├── embedding/ # 向量嵌入
│ ├── probe/ # ffprobe 整合
│ ├── storage/ # 檔案管理
│ └── thumbnail/ # 縮圖生成
├── api/ # HTTP API
│ ├── mod.rs
│ └── routes/
├── player/ # 影片播放
└── watcher/ # 檔案監控
```
### 1.2 模組設計原則
- **單一職責**: 每個模組專注於一項功能
- **介面抽象**: 使用 trait 定義資料庫、操作器等介面
- **依賴注入**: 透過建構函式注入依賴
```rust
pub trait VideoProcessor: Send + Sync {
async fn process(&self, video_path: &str) -> Result<ProcessResult>;
}
```
## 2. 程式碼風格
### 2.1 命名規範
| 類型 | 規範 | 範例 |
|------|------|------|
| 結構體/列舉 | PascalCase | `VideoRecord`, `ChunkType` |
| 函式/變數 | snake_case | `get_video_by_uuid` |
| Trait | PascalCase + er 尾碼 | `Database`, `ChunkStore` |
| 檔案 | snake_case | `postgres_db.rs` |
| 常量 | SCREAMING_SNAKE_CASE | `MAX_CHUNK_SIZE` |
| 模組 | snake_case | `chunk`, `processor` |
### 2.2 匯入順序
```rust
// 1. 標準庫
use std::path::Path;
use std::process::Command;
// 2. 外部庫
use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::fs;
// 3. 內部模組
use crate::core::chunk::Chunk;
use crate::core::db::PostgresDb;
```
### 2.3 行寬與格式
- 最大行寬: 100 字元
- 使用 4 空格縮排
- 啟用 clippy 與 fmt
```bash
# 格式化
cargo fmt
# 檢查格式
cargo fmt -- --check
# Lint
cargo clippy --all-features
```
## 3. 錯誤處理
### 3.1 錯誤類型選擇
| 情境 | 錯誤類型 | 原因 |
|------|----------|------|
| 應用程式 | `anyhow::Result<T>` | 提供靈活的錯誤傳播 |
| 函式庫 | `thiserror` | 定義明確的錯誤類型 |
| API 錯誤 | 自定義 Error enum | 提供客戶端錯誤碼 |
### 3.2 錯誤處理範例
```rust
use anyhow::{Context, Result, bail};
fn process_video(video_path: &str) -> Result<VideoMetadata> {
// 使用 context 提供錯誤上下文
let output = Command::new("ffprobe")
.args(["-v", "quiet", "-print_format", "json", "-show_format", video_path])
.output()
.context("Failed to run ffprobe")?;
// 使用 bail 進行早期返回
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("ffprobe failed: {}", stderr);
}
// 解析輸出
let metadata: Metadata = serde_json::from_slice(&output.stdout)
.context("Failed to parse ffprobe output")?;
Ok(metadata)
}
```
### 3.3 自定義錯誤 (適用於函式庫)
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum VideoError {
#[error("Video not found: {0}")]
NotFound(String),
#[error("Invalid codec: {0}")]
InvalidCodec(String),
#[error("Processing failed: {0}")]
ProcessingError(#[from] std::io::Error),
}
```
## 4. 异步編程
### 4.1 Tokio 配置
```rust
// Cargo.toml
tokio = { version = "1", features = ["full"] }
```
### 4.2 Async Trait
```rust
use async_trait::async_trait;
#[async_trait]
pub trait Database: Send + Sync {
async fn init() -> Result<Self>
where Self: Sized;
async fn get_video(&self, uuid: &str) -> Result<Option<VideoRecord>>;
async fn store_chunk(&self, chunk: &Chunk) -> Result<()>;
}
```
### 4.3 避免常見陷阱
```rust
// ❌ 錯誤: 在同步上下文中調用 async 函式
fn bad_example() {
let result = db.get_video("xxx"); // 編譯錯誤
}
// ✅ 正確: 使用 #[tokio::main]
#[tokio::main]
async fn main() {
let result = db.get_video("xxx").await;
}
// ❌ 錯誤: 阻塞執行緒池
async fn bad_practice() {
let data = std::fs::read_to_string("file.txt").unwrap(); // 阻塞
}
// ✅ 正確: 使用 tokio::fs
async fn good_practice() {
let data = tokio::fs::read_to_string("file.txt").await.unwrap();
}
```
## 5. 外部程序整合
當需要使用 Python 生態系工具 (如 faster-whisper, YOLO) 時:
```rust
pub async fn process_asr(video_path: &str, output_path: &str) -> Result<AsrResult> {
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("scripts")
.join("asr_processor.py");
// 使用 venv 中的 Python,確保版本隔離
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("venv")
.join("bin")
.join("python");
// 執行腳本
let output = Command::new(venv_python)
.arg(script_path)
.arg(video_path)
.arg(output_path)
.output()
.context("Failed to run ASR processor")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("ASR failed: {}", stderr);
}
// 讀取輸出
let json_str = std::fs::read_to_string(output_path)
.context("Failed to read ASR output")?;
let result: AsrResult = serde_json::from_str(&json_str)
.context("Failed to parse ASR output")?;
Ok(result)
}
```
### 5.2 進度回報
透過 stderr 回報進度,供 Rust 端解析:
```python
# Python 腳本
import sys
print(f"ASR_START", file=sys.stderr)
print(f"ASR_LANGUAGE:{detected_lang}", file=sys.stderr)
print(f"ASR_PROGRESS:{count}", file=sys.stderr)
print(f"ASR_COMPLETE:{total}", file=sys.stderr)
```
```rust
// Rust 端解析
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stderr.lines() {
if line.starts_with("ASR_PROGRESS:") {
let count = line.trim_start_matches("ASR_PROGRESS:");
println!("[ASR] Processed {} segments...", count);
}
}
```
---
## 6. Python 與 Node.js 混用規範
本專案同時使用 Python 和 Node.js (n8n),需建立明確的版本隔離與管理規範。
### 6.1 架構概述
```
┌─────────────────────────────────────────────────────────────┐
│ Momentry Core │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Rust │ │ Python │ │ Node.js │ │
│ │ (Core) │───▶ │ (Scripts) │ │ (n8n) │ │
│ │ │ │ │ │ │ │
│ │ - CLI │ │ - ASR │ │ - Workflow │ │
│ │ - DB │ │ - Thumb │ │ - API │ │
│ │ - Storage │ │ - OCR │ │ - Webhooks │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 資料庫 / 檔案系統 / Qdrant │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 6.2 Python 版本管理
#### 6.2.1 版本鎖定
| 版本 | 用途 | 路徑 |
|------|------|------|
| 3.11.14 | 影片處理腳本 | `/opt/homebrew/bin/python3.11` |
#### 6.2.2 虛擬環境
使用專案隔離的 venv
```bash
# 建立虛擬環境
cd /Users/accusys/momentry_core_0.1
python3.11 -m venv venv
# 啟用
source venv/bin/activate
# 安裝依賴
pip install faster-whisper opencv-python python-dotenv
```
#### 6.2.3 Rust 呼叫 Python
```rust
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("venv")
.join("bin")
.join("python");
let output = Command::new(venv_python)
.arg(script_path)
.arg(video_path)
.output()
.context("Failed to run Python script")?;
```
### 6.3 Node.js 版本管理
#### 6.3.1 版本鎖定
參考 `docs/NODEJS.md`
| 版本 | 用途 | 路徑 |
|------|------|------|
| 22.22.1 | n8n | `/opt/homebrew/opt/node@22/bin/node` |
#### 6.3.2 n8n 服務配置
使用 launchd plist 隔離:
```xml
<!-- com.momentry.n8n.main.plist -->
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/node@22/bin/node</string>
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:...</string>
</dict>
```
### 6.4 Python + Node.js 共存原則
#### 6.4.1 隔離原則
| 原則 | 說明 |
|------|------|
| **獨立路徑** | Python 用 venv 路徑,Node.js 用 node@22 路徑 |
| **獨立環境** | n8n 服務使用 launchd plist,不與 Rust 共享環境 |
| **明確版本** | 所有腳本明確指定直譯器路徑 |
| **PORT 分配** | n8n: 5678/5690, API: 另行分配 |
#### 6.4.2 環境變數隔離
```bash
# Rust 專案 .env
DATABASE_URL=postgres://...
# n8n plist
N8N_ENCRYPTION_KEY=xxx
N8N_BASIC_AUTH_ACTIVE=true
# 勿混用,避免 Rust 讀到 n8n 環境變數
```
### 6.5 工作流程整合
#### 6.5.1 Rust → Python
```
Rust CLI ──▶ Python Script ──▶ JSON Output ──▶ Rust Parse
│ │
└── venv/bin/python └── faster-whisper
```
#### 6.5.2 Rust → n8n Webhook
```rust
// 觸發 n8n workflow
use reqwest;
pub async fn trigger_n8n_webhook(webhook_url: &str, payload: &str) -> Result<()> {
let client = reqwest::Client::new();
client.post(webhook_url)
.json(payload)
.send()
.await
.context("Failed to trigger n8n webhook")?;
Ok(())
}
```
#### 6.5.3 n8n → Rust API
```
n8n Workflow ──▶ HTTP Request Node ──▶ Rust API Server
┌───────┴───────┐
│ axum server │
│ /api/webhook │
└───────────────┘
```
### 6.6 監控配置
#### 6.6.1 獨立監控腳本
```bash
# monitor/service/node_monitor.sh
# 監控 n8n Node.js 版本
# monitor/service/python_monitor.sh
# 監控 Python 腳本執行狀態
```
#### 6.6.2 健康檢查
```yaml
# monitor_config.yaml
services:
- name: "n8n"
type: "http"
port: 5678
check_url: "http://localhost:5678/"
- name: "Python Scripts"
type: "process"
check: "pgrep -f asr_processor.py"
```
### 6.7 排程管理
#### 6.7.1 備份排程 (Python 腳本)
```bash
# crontab
0 3 * * * /Users/accusys/momentry/scripts/backup_all.sh
```
#### 6.7.2 n8n 工作流排程
- 由 n8n 內建排程節點管理
- 不與 crontab 衝突
### 6.8 故障排除
#### 6.8.1 常見問題
| 問題 | 原因 | 解決方案 |
|------|------|----------|
| n8n 版本警告 | 使用 Node 25.x | 確認 plist 使用 node@22 |
| Python 腳本找不到模組 | 未啟用 venv | 使用 venv/bin/python |
| 執行權限錯誤 | shebang 錯誤 | 確認 #!/opt/homebrew/bin/python3.11 |
| Port 被佔用 | 多個服務使用相同 port | 分配獨立 port |
#### 6.8.2 診斷命令
```bash
# 檢查 Python 版本
which python
/opt/homebrew/bin/python3.11 --version
# 檢查 Node.js 版本
/opt/homebrew/opt/node@22/bin/node --version
# 檢查 n8n 程序
ps aux | grep n8n
# 檢查 Python 程序
ps aux | grep python
# 檢查 Port 佔用
lsof -i :5678 # n8n
```
### 6.9 新增服務決策
```
新服務需要哪種執行環境?
├─ Python 腳本 ──▶ 使用專案 venv
│ (路徑: venv/bin/python)
├─ Node.js 工具 ──▶ 評估版本需求
│ │
│ ├─ 支援 Node 22 ──▶ 使用 node@22
│ │
│ └─ 需要其他版本 ──▶ 安裝新版本 (如 node@20)
└─ 現有服務依賴 ──▶ 根據現有服務配置
```
### 6.10 文件維護
當新增 Python 或 Node.js 服務時:
1. 更新本文檔的版本表格
2. 建立對應的監控腳本
3. 如需 launchd plist,建立並加入 `momentry_runtime/plist/`
4. 更新 `docs/NODEJS.md``docs/PYTHON.md`
### 5.2 進度回報
透過 stderr 回報進度,供 Rust 端解析:
```python
# Python 腳本
import sys
print(f"ASR_START", file=sys.stderr)
print(f"ASR_LANGUAGE:{detected_lang}", file=sys.stderr)
print(f"ASR_PROGRESS:{count}", file=sys.stderr)
print(f"ASR_COMPLETE:{total}", file=sys.stderr)
```
```rust
// Rust 端解析
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stderr.lines() {
if line.starts_with("ASR_PROGRESS:") {
let count = line.trim_start_matches("ASR_PROGRESS:");
println!("[ASR] Processed {} segments...", count);
}
}
```
## 7. 測試策略
### 6.1 單元測試
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunk_creation() {
let chunk = Chunk::new(
"test-uuid".to_string(),
0,
ChunkType::Sentence,
0.0,
10.0,
serde_json::json!({"text": "Hello"}),
);
assert_eq!(chunk.uuid, "test-uuid");
assert_eq!(chunk.chunk_type, ChunkType::Sentence);
}
}
```
### 6.2 整合測試
```rust
#[cfg(test)]
mod integration {
use super::*;
#[tokio::test]
async fn test_database_connection() {
let db = PostgresDb::init().await.unwrap();
let videos = db.list_videos().await.unwrap();
assert!(videos.len() >= 0);
}
}
```
### 6.3 測試資料
- 使用測試資料庫隔離測試環境
- 避免在測試中使用真實敏感資料
- 使用 mock 物件模擬外部依賴
```rust
#[cfg(test)]
mod mocks {
pub struct MockVideoProcessor {
pub result: AsrResult,
}
impl VideoProcessor for MockVideoProcessor {
async fn process(&self, _video_path: &str) -> Result<AsrResult> {
Ok(self.result.clone())
}
}
}
```
## 8. 日誌與監控
### 7.1 日誌規範
- **使用 tracing**: 不要使用 `println!`
- **結構化日誌**: 使用訊息 + 欄位
```rust
use tracing::{info, warn, error};
fn process_video(uuid: &str) -> Result<()> {
info!(uuid = uuid, "Starting video processing");
match do_processing(uuid) {
Ok(_) => info!(uuid = uuid, "Processing completed"),
Err(e) => {
error!(uuid = uuid, error = %e, "Processing failed");
return Err(e);
}
}
}
```
### 7.2 初始化日誌
```rust
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
fn init_logging() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "momentry_core=info,tokio=warn".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
}
```
## 9. 性能優化
### 8.1 避免拷貝
```rust
// ❌ 拷貝
let data = record.clone();
// ✅ 引用
fn process(data: &Data) { }
// ✅ 或使用 Arc 共用
use std::sync::Arc;
let shared = Arc::new(data);
```
### 8.2 批量操作
```rust
// ❌ 逐筆插入
for item in items {
db.insert(&item).await?;
}
// ✅ 批量插入
db.insert_batch(&items).await?;
```
### 8.3 連線池
```rust
// 使用 sqlx 連線池
let pool = SqlxPool::connect(&DATABASE_URL).await?;
let db = PostgresDb::new(pool);
```
## 10. 安全考量
### 9.1 敏感資訊
- **不要**將密碼、API Key 寫入程式碼
- 使用環境變數或設定檔
- .env 檔案加入 .gitignore
```rust
// ❌ 硬編碼密碼
let password = "secret123";
// ✅ 使用環境變數
let password = std::env::var("DATABASE_PASSWORD")
.context("DATABASE_PASSWORD must be set")?;
```
### 9.2 命令注入
```rust
// ❌ 危險: 直接使用使用者輸入
let cmd = format!("ffprobe {}", user_input);
// ✅ 安全: 使用參數化
Command::new("ffprobe")
.arg(user_input) // 自動轉義
.output();
```
## 11. 文件編寫
### 10.1 結構體/函式文件
```rust
/// 代表一個影片記錄
///
/// # Fields
/// * `id` - 資料庫 ID
/// * `uuid` - 唯一識別碼
/// * `duration` - 影片時長 (秒)
///
/// # Example
/// ```
/// let video = VideoRecord {
/// id: 1,
/// uuid: "abc123".to_string(),
/// duration: 120.5,
/// };
/// ```
pub struct VideoRecord {
pub id: i64,
pub uuid: String,
pub duration: f64,
}
```
### 10.2 API 文件
```rust
/// 取得影片記錄
///
/// # Arguments
/// * `uuid` - 影片的 UUID
///
/// # Returns
/// * `Ok(Some(VideoRecord))` - 找到影片
/// * `Ok(None)` - 影片不存在
/// * `Err` - 資料庫錯誤
///
/// # Errors
/// 如果資料庫連線失敗,返回資料庫錯誤
pub async fn get_video(&self, uuid: &str) -> Result<Option<VideoRecord>>;
```
## 12. CLI 命令設計
### 11.1 命令結構
使用 clap derive
```rust
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "momentry")]
#[command(about = "Digital asset management system")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Register a video file
Register {
/// Path to video file
path: String,
},
/// Process video
Process {
/// UUID or path
target: String,
},
/// Generate chunks
Chunk {
/// Video UUID
uuid: String,
},
}
```
### 11.2 錯誤處理
```rust
match &cli.command {
Commands::Register { path } => {
if !Path::new(path).exists() {
eprintln!("Error: File not found: {}", path);
std::process::exit(1);
}
// ...
}
}
```
## 13. 依賴管理
### 12.1 版本約束
```toml
# Cargo.toml
[dependencies]
anyhow = "1.0" # 精確版本
tokio = { version = "1", features = ["full"] } # 範圍版本
serde = "1.0" # 精確版本
```
### 12.2 避免依賴地獄
- 審查依賴數量
- 優先使用標準庫
- 選擇維護良好的套件
## 14. 建構與部署
### 13.1 建構命令
```bash
# 開發建構
cargo build
# 發布建構
cargo build --release
# 單一二進制
cargo build --bin momentry
```
### 13.2 檢查清單
```bash
# 格式化
cargo fmt -- --check
# Lint
cargo clippy --all-features -- -D warnings
# 類型檢查
cargo check --all-features
# 測試
cargo test
```
## 15. 版本控制
### 14.1 提交訊息規範
```
<type>(<scope>): <subject>
<body>
<footer>
```
類型:
- `feat`: 新功能
- `fix`: 錯誤修復
- `docs`: 文件變更
- `style`: 格式調整
- `refactor`: 重構
- `test`: 測試變更
- `chore`: 建構/工具變更
範例:
```
feat(processor): Add ASR progress reporting
- Add stderr parsing for progress updates
- Support ASR_START, ASR_PROGRESS, ASR_COMPLETE markers
Closes #123
```
### 14.2 分支策略
- `main`: 穩定版本
- `feature/*`: 新功能開發
- `fix/*`: 錯誤修復
- `refactor/*`: 重構
---
## 附錄: 快速參考
### 建構
```bash
cargo build --release
cargo run -- --help
```
### 品質檢查
```bash
cargo fmt -- --check
cargo clippy --all-features
cargo check --all-features
cargo test
```
### 依賴
```bash
cargo add <package>
cargo tree
cargo outdated
```
-1074
View File
File diff suppressed because it is too large Load Diff
-659
View File
@@ -1,659 +0,0 @@
# Momentry 服務添加規範 v2.0
## 一、概述
本文檔定義在 Momentry 系統中添加新服務的標準流程和規範。
**重要原則**
- 使用 `launchctl` 管理服務,勿使用 `brew services`
- 所有服務使用 `com.momentry.*` 作為 plist Label
- 數據存放於 `/Users/accusys/momentry/` 目錄
- 每個服務需提供完整的監控腳本
- 所有服務 Plist 存放於 `/Library/LaunchDaemons/`
- 所有服務以 `accusys` 用戶運行,確保 accusys 可以管理
---
## 二、服務運行方式
### 2.1 運行分類
| 類型 | 說明 | 示例 |
|------|------|------|
| **開機自動運行** | 電腦開機後立即自動啟動 | PostgreSQL, Redis, n8n, Caddy 等核心服務 |
| **登入時運行** | 用戶登入後才啟動 | 開發工具、臨時服務 |
**當前所有服務**:均為開機自動運行
### 2.2 Plist 存放位置
所有 Momentry 服務統一存放於:
```
/Library/LaunchDaemons/com.momentry.{service_name}.plist
```
---
## 三、服務命名規範
### 3.1 Plist 文件命名
```
com.momentry.{service_name}.plist
```
示例:
- `com.momentry.redis.plist`
- `com.momentry.n8n.main.plist`
- `com.momentry.rustdesk.hbbs.plist`
### 3.2 目錄命名
服務相關目錄統一放置於:
```
/Users/accusys/momentry/
├── var/{service_name}/ # 服務數據
├── etc/{service_name}/ # 服務配置
├── log/{service_name}.log # 服務日誌 (stdout)
└── log/{service_name}.error.log # 錯誤日誌 (stderr)
```
---
## 四、Plist 文件模板
### 4.1 標準服務模板
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.{service_name}</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/{service_name}</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/executable</string>
<string>--arg1</string>
<string>value1</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<!-- 其他環境變數 -->
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/{service_name}.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/{service_name}.error.log</string>
</dict>
</plist>
```
### 4.2 日誌文件規範
每個服務必須創建兩個日誌文件:
| 文件 | 說明 | 路徑 |
|------|------|------|
| StandardOutPath | 標準輸出日誌 | `/Users/accusys/momentry/log/{service_name}.log` |
| StandardErrorPath | 錯誤輸出日誌 | `/Users/accusys/momentry/log/{service_name}.error.log` |
**創建日誌文件**
```bash
touch /Users/accusys/momentry/log/{service_name}.log
touch /Users/accusys/momentry/log/{service_name}.error.log
chmod 644 /Users/accusys/momentry/log/{service_name}.log
chmod 644 /Users/accusys/momentry/log/{service_name}.error.log
```
---
## 五、添加服務步驟
### 步驟 1:創建目錄結構
```bash
# 創建服務目錄
mkdir -p /Users/accusys/momentry/var/{service_name}
mkdir -p /Users/accusys/momentry/etc/{service_name}
# 創建日誌文件
touch /Users/accusys/momentry/log/{service_name}.log
touch /Users/accusys/momentry/log/{service_name}.error.log
```
### 步驟 2:創建 Plist 文件
```bash
# 複製模板並編輯
cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/template.service.plist \
/Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.{service_name}.plist
# 編輯 plist 文件
vim /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.{service_name}.plist
```
### 步驟 3:複製到系統 LaunchDaemons
```bash
# 複製到 /Library/LaunchDaemons/
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.{service_name}.plist \
/Library/LaunchDaemons/
```
### 步驟 4:載入服務
```bash
# 載入服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service_name}.plist
# 驗證服務狀態
launchctl list | grep momentry
```
### 步驟 5:添加監控
`monitor/config/monitor_config.yaml` 中添加服務配置:
```yaml
service:
services:
- name: "{service_name}"
type: "http" # 或 "process", "tcp"
port: {port_number}
host: "localhost"
check_url: "http://localhost:{port}/health"
timeout: 5
enabled: true
```
### 步驟 6:添加文檔
`docs/INSTALL_{SERVICE_NAME}.md` 中記錄:
- 安裝步驟
- 配置說明
- 健康檢查命令
- 故障排除
---
## 六、服務分類
### 按功能分類
| 類別 | 服務 |
|------|------|
| 資料庫 | PostgreSQL, Redis, MariaDB, MongoDB |
| 應用 | n8n, Gitea, SFTPGo |
| 網頁 | Caddy, PHP-FPM |
| AI/ML | Ollama, Qdrant |
| 遠程 | RustDesk |
### 按運行方式分類
| 運行方式 | 數量 | 服務 |
|----------|------|------|
| 開機自動運行 | 15 | PostgreSQL, Redis, n8n, Caddy, Gitea, SFTPGo, Ollama, Qdrant, MariaDB, PHP-FPM, RustDesk, MongoDB, Agent |
| 登入時運行 | 0 | (暫無) |
---
## 七、監控要求
每個服務必須提供:
### 7.1 健康檢查
`monitor/service/health_check.sh` 中添加檢查函數:
```bash
check_{service_name}() {
local start=$(date +%s%N)
if nc -z localhost {port} > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} {service_name} ({port}) - ${ms}ms"
record_service "{service_name}" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} {service_name} ({port}) - Down"
record_service "{service_name}" "down" "0" "Connection failed"
return 1
fi
}
```
### 7.2 數據庫記錄
```sql
-- 添加服務監控記錄函數
record_service() {
local service=$1
local status=$2
local response_time=$3
local error_msg=$4
psql -U accusys -h localhost -d momentry << EOF
INSERT INTO monitor_services (service_name, service_type, status, response_time_ms, error_message, checked_at)
VALUES ('$service', 'service', '$status', $response_time, '$error_msg', NOW());
EOF
}
```
---
## 八、服務管理命令
### 8.1 基本操作
```bash
# 啟動服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 停止服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
# 重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 查看服務狀態
launchctl list | grep momentry
# 查看服務日誌
tail -f /Users/accusys/momentry/log/{service}.log
tail -f /Users/accusys/momentry/log/{service}.error.log
```
### 8.2 故障排除
```bash
# 檢查服務是否運行
pgrep -f "{service_process_name}"
# 檢查端口是否監聽
lsof -i :{port}
# 檢查錯誤日誌
tail -100 /Users/accusys/momentry/log/{service}.error.log
```
---
## 九、服務備份作業
### 9.1 備份內容
每個服務需要備份的內容:
| 類別 | 路徑 | 說明 |
|------|------|------|
| 數據 | `/Users/accusys/momentry/var/{service}/` | 服務運行數據 |
| 配置 | `/Users/accusys/momentry/etc/{service}/` | 服務配置文件 |
| Plist | `/Library/LaunchDaemons/com.momentry.{service}.plist` | 啟動配置 |
| 日誌 | `/Users/accusys/momentry/log/{service}.log` | 運行日誌 |
### 9.2 備份命名規範
**格式**: `{service}_{type}_{YYYYMMDD}_{HHMMSS}[_{suffix}].{ext}`
**組成部分**:
| 位置 | 說明 | 範例 |
|------|------|------|
| `{service}` | 服務名稱 (小寫) | `postgresql`, `redis`, `n8n` |
| `{type}` | 備份類型 | `full`, `db`, `cfg`, `data` |
| `{YYYYMMDD}` | 備份日期 | `20260315` |
| `{HHMMSS}` | 備份時間 (24小時制) | `030000` |
| `{suffix}` | 可選標記 | `incremental`, `verified` |
| `{ext}` | 檔案擴展名 | `sql.gz`, `tar.gz`, `rdb`, `zip` |
**類型說明**:
| 類型 | 說明 | 包含內容 |
|------|------|---------|
| `full` | 完整備份 | 數據 + 配置 + 日誌 |
| `db` | 數據庫備份 | 資料庫導出 (sql, rdb) |
| `cfg` | 配置備份 | 配置文件 |
| `data` | 數據備份 | var 目錄 |
**範例**:
```
postgresql_db_20260315_030000.sql.gz # PostgreSQL 完整資料庫 (壓縮)
redis_rdb_20260315_030000.rdb # Redis RDB 快照
n8n_full_20260315_030000.tar.gz # n8n 完整備份
mariadb_db_wordpress_20260315_030000.sql.gz # MariaDB WP 資料庫
gitea_full_20260315_030000.zip # Gitea 完整備份
qdrant_snapshot_20260315_030000.tar.gz # Qdrant 向量庫
ollama_cfg_20260315_030000.tar.gz # Ollama 配置
caddy_cfg_20260315_030000.tar.gz # Caddy 配置
```
**可信斷點標記**:
- 檔名本身即為可信時間點
- 還原時直接使用檔名中的時間戳
- 建議配合 `backup_registry` 資料庫記錄完整元數據
**校驗和命名**:
```
postgresql_db_20260315_030000.sql.gz.sha256
```
### 9.3 備份腳本
```bash
#!/bin/bash
# 標準化備份腳本範本
# 遵循命名規範: {service}_{type}_{YYYYMMDD}_{HHMMSS}.{ext}
set -e
SERVICE_NAME="{service_name}"
BACKUP_TYPE="{type}" # full, db, cfg, data
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/Users/accusys/momentry/backup/${SERVICE_NAME}"
mkdir -p "$BACKUP_DIR"
# 根據類型執行備份
case "$BACKUP_TYPE" in
full)
echo "[$TIMESTAMP] 執行 $SERVICE_NAME 完整備份..."
tar -czf "$BACKUP_DIR/${SERVICE_NAME}_full_${TIMESTAMP}.tar.gz" \
/Users/accusys/momentry/var/${SERVICE_NAME}/ \
/Users/accusys/momentry/etc/${SERVICE_NAME}/ 2>/dev/null
;;
db)
echo "[$TIMESTAMP] 執行 $SERVICE_NAME 資料庫備份..."
if [ "$SERVICE_NAME" = "postgresql" ]; then
pg_dump -U accusys ${SERVICE_NAME} | gzip > \
"$BACKUP_DIR/${SERVICE_NAME}_db_${TIMESTAMP}.sql.gz"
elif [ "$SERVICE_NAME" = "mariadb" ]; then
mysqldump -u root -p --all-databases | gzip > \
"$BACKUP_DIR/${SERVICE_NAME}_db_${TIMESTAMP}.sql.gz"
elif [ "$SERVICE_NAME" = "redis" ]; then
redis-cli -a accusys SAVE
cp /opt/homebrew/var/db/redis/dump.rdb \
"$BACKUP_DIR/${SERVICE_NAME}_rdb_${TIMESTAMP}.rdb"
fi
;;
cfg)
echo "[$TIMESTAMP] 執行 $SERVICE_NAME 配置備份..."
tar -czf "$BACKUP_DIR/${SERVICE_NAME}_cfg_${TIMESTAMP}.tar.gz" \
/Users/accusys/momentry/etc/${SERVICE_NAME}/ 2>/dev/null
;;
data)
echo "[$TIMESTAMP] 執行 $SERVICE_NAME 數據備份..."
tar -czf "$BACKUP_DIR/${SERVICE_NAME}_data_${TIMESTAMP}.tar.gz" \
/Users/accusys/momentry/var/${SERVICE_NAME}/ 2>/dev/null
;;
esac
# 生成校驗和
if [ -f "$BACKUP_DIR/${SERVICE_NAME}_${BACKUP_TYPE}_${TIMESTAMP}"* ]; then
sha256sum "$BACKUP_DIR/${SERVICE_NAME}_${BACKUP_TYPE}_${TIMESTAMP}"* > \
"$BACKUP_DIR/${SERVICE_NAME}_${BACKUP_TYPE}_${TIMESTAMP}.sha256"
fi
# 清理舊備份 (保留 30 天)
find "$BACKUP_DIR" -name "*_${TIMESTAMP%%_*}_*.tar.gz" -mtime +30 -delete 2>/dev/null
find "$BACKUP_DIR" -name "*_${TIMESTAMP%%_*}_*.sql.gz" -mtime +30 -delete 2>/dev/null
find "$BACKUP_DIR" -name "*_${TIMESTAMP%%_*}_*.rdb" -mtime +30 -delete 2>/dev/null
find "$BACKUP_DIR" -name "*.sha256" -mtime +30 -delete 2>/dev/null
echo "備份完成: ${SERVICE_NAME}_${BACKUP_TYPE}_${TIMESTAMP}"
```
### 9.4 備份排程
建議使用 cron 進行自動備份:
```bash
# 編輯 crontab
crontab -e
# 添加備份任務 (每天凌晨 3 點)
0 3 * * * /Users/accusys/momentry/scripts/backup_{service}.sh >> /Users/accusys/momentry/log/backup.log 2>&1
# 每週日凌晨 3 點執行完整備份
0 3 * * 0 /Users/accusys/momentry/scripts/backup_{service}.sh full >> /Users/accusys/momentry/log/backup.log 2>&1
```
### 9.5 備份驗證
```bash
# 查看備份列表 (按時間排序)
ls -lt /Users/accusys/momentry/backup/{service}/
# 驗證備份完整性
# 1. 檢查校驗和
sha256sum -c /Users/accusys/momentry/backup/{service}/*.sha256
# 2. 驗證 tar 壓縮
tar -tzf /Users/accusys/momentry/backup/{service}/{service}_full_20260315_030000.tar.gz
# 3. 驗證 SQL 備份
zcat /Users/accusys/momentry/backup/{service}/{service}_db_20260315_030000.sql.gz | head -5
# 驗證備份完整性
tar -tzf /Users/accusys/momentry/backup/{service}/{service}_var_20260315.tar.gz
```
---
## 十、服務完整刪除作業
### 10.1 刪除前確認
**警告**:此操作不可逆,請確保已完成備份!
- [ ] 確認服務已停止運行
- [ ] 確認數據已備份
- [ ] 確認無其他服務依賴此服務
### 10.2 刪除步驟
**步驟 1:停止服務**
```bash
# 停止服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
# 驗證服務已停止
launchctl list | grep momentry.{service}
```
**步驟 2:刪除 Plist**
```bash
# 刪除系統 Plist
sudo rm /Library/LaunchDaemons/com.momentry.{service}.plist
# 刪除專案 Plist
rm /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.{service}.plist
```
**步驟 3:刪除數據和配置**
```bash
# 刪除數據目錄
sudo rm -rf /Users/accusys/momentry/var/{service}/
# 刪除配置目錄
sudo rm -rf /Users/accusys/momentry/etc/{service}/
# 刪除日誌
rm -f /Users/accusys/momentry/log/{service}.log
rm -f /Users/accusys/momentry/log/{service}.error.log
```
**步驟 4:清理監控配置**
```bash
# 從監控配置中移除服務
vim /Users/accusys/momentry_core_0.1/monitor/config/monitor_config.yaml
# 刪除該服務的監控配置
# 從監控腳本中移除
vim /Users/accusys/momentry_core_0.1/monitor/service/health_check.sh
# 移除該服務的檢查函數
```
**步驟 5:清理監控數據(可選)**
```bash
# 保留歷史數據還是刪除?
# 刪除監控數據
psql -U accusys -h localhost -d momentry -c "
DELETE FROM monitor_services WHERE service_name = '{service}';
"
```
### 10.3 驗證刪除
```bash
# 確認服務已停止
launchctl list | grep momentry.{service}
# 確認目錄已刪除
ls /Users/accusys/momentry/var/{service}/ 2>/dev/null || echo "已刪除"
# 確認 Plist 已刪除
ls /Library/LaunchDaemons/com.momentry.{service}.plist 2>/dev/null || echo "已刪除"
```
### 10.4 完整刪除腳本
```bash
#!/bin/bash
SERVICE_NAME="{service_name}"
echo "========== 服務完整刪除 =========="
echo "服務: $SERVICE_NAME"
echo "警告:此操作不可逆!"
read -p "確認繼續 (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo "取消刪除"
exit 0
fi
# 停止服務
echo "[1/6] 停止服務..."
sudo launchctl unload /Library/LaunchDaemons/com.momentry.${SERVICE_NAME}.plist 2>/dev/null
# 刪除 Plist
echo "[2/6] 刪除 Plist..."
sudo rm -f /Library/LaunchDaemons/com.momentry.${SERVICE_NAME}.plist
rm -f /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.${SERVICE_NAME}.plist
# 刪除數據
echo "[3/6] 刪除數據..."
sudo rm -rf /Users/accusys/momentry/var/${SERVICE_NAME}/
# 刪除配置
echo "[4/6] 刪除配置..."
sudo rm -rf /Users/accusys/momentry/etc/${SERVICE_NAME}/
# 刪除日誌
echo "[5/6] 刪除日誌..."
rm -f /Users/accusys/momentry/log/${SERVICE_NAME}.log
rm -f /Users/accusys/momentry/log/${SERVICE_NAME}.error.log
# 清理監控數據
echo "[6/6] 清理監控數據..."
psql -U accusys -h localhost -d momentry -c "
DELETE FROM monitor_services WHERE service_name = '${SERVICE_NAME}';
" 2>/dev/null
echo "========== 刪除完成 =========="
```
---
## 十一、檢查清單
添加新服務時,請確認以下項目:
- [ ] 創建服務目錄 (`var/`, `etc/`)
- [ ] 配置日誌文件 (`.log` + `.error.log`)
- [ ] 創建 plist 文件,UserName 設為 `accusys`
- [ ] 複製到 `/Library/LaunchDaemons/`
- [ ] 使用 launchctl 載入服務
- [ ] 驗證服務運行
- [ ] 添加監控配置
- [ ] 測試監控腳本
- [ ] 創建安裝文檔
- [ ] 更新 SERVICES.md 服務清單
- [ ] 更新 MOMENTRY_INTEGRATION_GUIDE.md
---
## 十二、模板文件
### Plist 模板位置
```
/Users/accusys/momentry_core_0.1/momentry_runtime/plist/
├── template.service.plist # 服務模板
├── com.momentry.redis.plist # 服務示例
└── com.momentry.n8n.main.plist # 複雜服務示例
```
### 創建模板命令
```bash
# 創建服務模板
cat > /Users/accusys/momentry_core_0.1/momentry_runtime/plist/template.service.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.SERVICE_NAME</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/SERVICE_NAME</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/executable</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/SERVICE_NAME.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/SERVICE_NAME.error.log</string>
</dict>
</plist>
EOF
```
---
## 十一、版本歷史
| 版本 | 日期 | 內容 |
|------|------|------|
| 1.0 | 2026-03-15 | 初始版本 |
| 2.0 | 2026-03-15 | 統一 Plist 位置、移除 root/用戶區分、加入運行方式分類 |
| 2.1 | 2026-03-15 | 新增服務備份作業、服務完整刪除作業 |
-233
View File
@@ -1,233 +0,0 @@
# Video Registration
## 概述
影片註冊 API (`POST /api/v1/register`) 用於將影片加入 Momentry Core 系統進行處理。
## 路徑格式
### 支援的路徑格式
| 格式 | 範例 | 說明 |
|------|------|------|
| 相對路徑 | `./demo/video.mp4` | 推薦格式 |
| 相對路徑(無 ./ | `demo/video.mp4` | 自動加上 `./` |
| 絕對路徑 | `/Users/.../sftpgo/data/demo/video.mp4` | 支援但不推薦 |
### 路徑結構
```
./username/filepath
│ │ │
│ │ └── 檔案路徑(可以是多層目錄)
│ └── 使用者名稱(SFTPgo 用戶目錄名稱)
└── 相對路徑前綴
```
**範例**
- `./demo/video.mp4` → username=`demo`, filepath=`video.mp4`
- `./demo/movies/2024/video.mp4` → username=`demo`, filepath=`movies/2024/video.mp4`
- `./warren/project1/interview.mp4` → username=`warren`, filepath=`project1/interview.mp4`
## UUID 計算
### 計算規則
```
UUID = SHA256(username/filepath)[0:16]
```
**範例**
```rust
// 路徑: ./demo/video.mp4
// username: "demo"
// filepath: "video.mp4"
// key: "demo/video.mp4"
// UUID: SHA256("demo/video.mp4")[0:16]
```
### 特性
| 特性 | 說明 |
|------|------|
| 用戶隔離 | 不同用戶的相同檔名會產生不同 UUID |
| 一致性 | 相同相對路徑一定產生相同 UUID |
| 遷移安全 | SFTPgo 資料路徑變更後 UUID 保持一致 |
### 範例
```rust
// 用戶 demo 的影片
compute_uuid_from_relative_path("./demo/video.mp4")
// → "9760d0820f0cf9a7"
// 用戶 warren 的相同檔名影片
compute_uuid_from_relative_path("./warren/video.mp4")
// → "a1b2c3d4e5f6g7h8" (不同的 UUID)
```
## 重複註冊檢查
### 行為
1. 系統檢查 UUID 是否已存在於資料庫
2. 如果存在,返回 `already_exists: true` 和現有影片資訊
3. 如果不存在,創建新的影片記錄
### API 回應
**新註冊**
```json
{
"uuid": "9760d0820f0cf9a7",
"video_id": 18,
"job_id": 2,
"file_name": "video.mp4",
"duration": 159.637188,
"width": 640,
"height": 360,
"already_exists": false
}
```
**重複註冊**
```json
{
"uuid": "9760d0820f0cf9a7",
"video_id": 18,
"job_id": 2,
"file_name": "video.mp4",
"duration": 159.637188,
"width": 640,
"height": 360,
"already_exists": true
}
```
## SFTPgo 整合
### 目錄結構
SFTPgo 的用戶目錄結構:
```
/Users/accusys/momentry/var/sftpgo/data/
├── demo/ ← 用戶目錄
│ ├── video.mp4
│ └── movies/
│ └── movie1.mp4
├── warren/ ← 用戶目錄
│ └── project1/
│ └── interview.mp4
└── momentry/ ← 用戶目錄
└── presentation.mp4
```
### 註冊流程
1. SFTPgo 用戶上傳檔案到各自的目錄
2. n8n 或其他服務調用註冊 API
3. 使用相對路徑格式:`./username/filepath`
4. 系統計算 UUID 並檢查重複
5. 創建處理任務
## 程式碼範例
### 註冊影片
```bash
# 使用相對路徑註冊
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-d '{"path": "./demo/video.mp4"}'
# 或使用多層目錄
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-d '{"path": "./demo/movies/2024/video.mp4"}'
```
### UUID 計算函數
```rust
// 使用相對路徑計算 UUID
pub fn compute_uuid_from_relative_path(relative_path: &str) -> String {
let (username, filepath) = extract_user_from_relative_path(relative_path);
compute_uuid(&username, &filepath)
}
// 從相對路徑提取用戶名和檔案路徑
pub fn extract_user_from_relative_path(relative_path: &str) -> (String, String) {
let path = relative_path.strip_prefix("./").unwrap_or(relative_path);
let path_buf = PathBuf::from(path);
let mut components = path_buf.components();
let username = components
.next()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.unwrap_or_default();
let filepath: String = components
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect::<Vec<_>>()
.join("/");
(username, filepath)
}
```
## 相關 API
### Probe API(僅探測,不註冊)
如果只需要取得影片資訊而不註冊,可以使用 Probe API:
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-d '{"path": "./demo/video.mp4"}'
```
**回應範例**
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0,
"cached": false,
"format": {...},
"streams": [...]
}
```
**與 Register API 的差異**
| 功能 | Probe API | Register API |
|------|-----------|---------------|
| 計算 UUID | ✓ | ✓ |
| 執行 ffprobe | ✓ | ✓ |
| 儲存 probe.json | ✓ | ✓ |
| 寫入 videos 表 | ✗ | ✓ |
| 建立 monitor_job | ✗ | ✓ |
| 返回 job_id | ✗ | ✓ |
| 適用場景 | 預覽影片資訊 | 註冊並處理影片 |
## 相關檔案
| 檔案 | 說明 |
|------|------|
| `src/core/storage/uuid.rs` | UUID 計算邏輯 |
| `src/api/server.rs` | 註冊與 Probe API 實現 |
| `src/core/probe/ffprobe.rs` | ffprobe 整合 |
| `docs/SFTPGO_DEMO_USER.md` | SFTPgo 用戶設置 |
| `docs/API_ENDPOINTS.md` | API 端點總覽 |
## 歷史
| 日期 | 變更 |
|------|------|
| 2026-03-25 | 初始版本 - 新增 UUID 計算規則和重複註冊檢查 |
| 2026-03-25 | 新增 Probe API 說明 |
-32
View File
@@ -1,32 +0,0 @@
#!/bin/bash
set -e
# Momentry Build Script
cd /Users/accusys/momentry_core_0.1
echo "========================================"
echo "Building Momentry"
echo "========================================"
# Check if cargo is available
if ! command -v cargo > /dev/null 2>&1; then
echo "ERROR: Rust/Cargo not found. Please install Rust first."
echo "Visit: https://rustup.rs"
exit 1
fi
# Build in release mode
echo "Building release version..."
cargo build --release
# Also build video_player
cd /Users/accusys/video_player
echo "Building video_player..."
cargo build --release
echo "========================================"
echo "Build Complete!"
echo "========================================"
echo "Momentry binary: /Users/accusys/momentry_core_0.1/target/release/momentry"
echo "Video player: /Users/accusys/video_player/target/release/video_player"
-86
View File
@@ -1,86 +0,0 @@
#!/bin/bash
# Momentry Health Check Script
echo "========================================"
echo "Momentry Health Check"
echo "========================================"
echo ""
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
check() {
local name="$1"
local cmd="$2"
if eval "$cmd" > /dev/null 2>&1; then
echo -e "${GREEN}${NC} $name"
return 0
else
echo -e "${RED}${NC} $name"
return 1
fi
}
total=0
passed=0
# PostgreSQL
total=$((total + 1))
check "PostgreSQL (localhost:5432)" "pg_isready -h localhost -p 5432 -U accusys" && passed=$((passed + 1))
# Redis
total=$((total + 1))
check "Redis (localhost:6379)" "redis-cli -a accusys ping" && passed=$((passed + 1))
# MongoDB - Port
total=$((total + 1))
check "MongoDB Port (27017)" "lsof -i :27017 > /dev/null 2>&1" && passed=$((passed + 1))
# MongoDB - Process
total=$((total + 1))
check "MongoDB Process" "pgrep -f mongod" && passed=$((passed + 1))
# MongoDB - Connection
total=$((total + 1))
check "MongoDB Connection" "mongosh --eval 'db.adminCommand(\"ping\")' > /dev/null 2>&1" && passed=$((passed + 1))
# Ollama
total=$((total + 1))
check "Ollama (localhost:11434)" "curl -s http://localhost:11434/api/tags > /dev/null" && passed=$((passed + 1))
# Momentry API
total=$((total + 1))
check "Momentry API (localhost:3000)" "curl -s http://localhost:3000/health > /dev/null" && passed=$((passed + 1))
# ffprobe
total=$((total + 1))
check "ffprobe" "which ffprobe" && passed=$((passed + 1))
# Check video_player binary
total=$((total + 1))
check "video_player binary" "test -f /Users/accusys/video_player/target/release/video_player" && passed=$((passed + 1))
# Check momentry binary
total=$((total + 1))
check "momentry binary" "test -f /Users/accusys/momentry_core_0.1/target/release/momentry" && passed=$((passed + 1))
echo ""
echo "========================================"
echo "Result: $passed/$total checks passed"
echo "========================================"
# Show status if failures
if [ $passed -lt $total ]; then
echo ""
echo "Failed services:"
[ $(pg_isready -h localhost -p 5432 -U accusys > /dev/null 2>&1; echo $?) -ne 0 ] && echo " - PostgreSQL: brew services start postgresql@18"
[ $(redis-cli -a accusys ping > /dev/null 2>&1; echo $?) -ne 0 ] && echo " - Redis: launchctl load ~/Library/LaunchAgents/com.momentry.redis.plist"
[ $(curl -s http://localhost:11434/api/tags > /dev/null 2>&1; echo $?) -ne 0 ] && echo " - Ollama: launchctl load ~/Library/LaunchAgents/com.momentry.ollama.plist"
fi
exit $([ $passed -eq $total ] && echo 0 || echo 1)
-65
View File
@@ -1,65 +0,0 @@
# Momentry Environment Variables Template
# Copy this file to .env and update with your values
# ===================
# Database Configuration
# ===================
DATABASE_URL=postgres://accusys@localhost:5432/momentry
# ===================
# Redis Configuration
# ===================
REDIS_URL=redis://accusys:accusys@localhost:6379
REDIS_PASSWORD=accusys
# ===================
# MongoDB (Optional)
# ===================
# MONGODB_URL=mongodb://localhost:27017
# MONGODB_DATABASE=momentry
# ===================
# Qdrant (Optional - Vector Database)
# ===================
# QDRANT_URL=http://localhost:6333
# QDRANT_COLLECTION=momentry_chunks
# ===================
# API Server
# ===================
API_HOST=127.0.0.1
API_PORT=3000
# ===================
# Watch Directories
# ===================
WATCH_DIRECTORIES=~/Videos,~/momentry_core_project/test_video
# ===================
# Ollama (LLM)
# ===================
OLLAMA_HOST=http://localhost:11434
# ===================
# Email Notification (SMTP)
# ===================
# SMTP_HOST=smtp.gmail.com
# SMTP_PORT=587
# SMTP_USER=your_email@gmail.com
# SMTP_PASSWORD=your_app_password
# SMTP_FROM=momentry@example.com
# SMTP_TO=admin@example.com
# ===================
# Backup Configuration
# ===================
BACKUP_PATH=~/momentry_core_0.1/backups
BACKUP_AUTO_ENABLED=true
BACKUP_SCHEDULE="0 2 * * *" # Daily at 2am
BACKUP_RETENTION_DAYS=7
# ===================
# Model Paths (Optional)
# ===================
# EMBEDDING_MODEL_PATH=./models/comic-embed-text
# LLM_MODEL_PATH=./models/mistral-7b
@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.agent</string>
<key>ProgramArguments</key>
<array>
<string>/Users/accusys/momentry_core_0.1/momentry_runtime/start.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/accusys</string>
<key>USER</key>
<string>accusys</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/Users/accusys/.cargo/bin</string>
<key>DATABASE_URL</key>
<string>postgres://accusys@localhost:5432/momentry</string>
<key>REDIS_URL</key>
<string>redis://accusys:accusys@localhost:6379</string>
<key>OLLAMA_HOST</key>
<string>http://localhost:11434</string>
</dict>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry_core_0.1/momentry_runtime/logs/stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry_core_0.1/momentry_runtime/logs/stderr.log</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry_core_0.1</string>
<key>ProcessType</key>
<string>Interactive</string>
</dict>
</plist>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.caddy</string>
<key>UserName</key>
<string>root</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/opt/homebrew/var/lib/caddy</string>
<key>XDG_DATA_HOME</key>
<string>/opt/homebrew/var/lib</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/caddy</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/caddy/bin/caddy</string>
<string>run</string>
<string>--config</string>
<string>/Users/accusys/momentry/etc/Caddyfile</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/caddy.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/caddy.log</string>
</dict>
</plist>
@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.gitea</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/gitea</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/gitea/bin/gitea</string>
<string>web</string>
<string>--config</string>
<string>/Users/accusys/momentry/etc/gitea/app.ini</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/accusys</string>
<key>GITEA_WORK_DIR</key>
<string>/Users/accusys/momentry/var/gitea</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/gitea.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/gitea.error.log</string>
</dict>
</plist>
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.mariadb</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/mariadb</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/mariadb/bin/mariadbd-safe</string>
<string>--datadir=/Users/accusys/momentry/var/mariadb</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/mariadb.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/mariadb.log</string>
</dict>
</plist>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.mongodb</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/mongod</string>
<string>--dbpath</string>
<string>/Users/accusys/momentry/var</string>
<string>--logpath</string>
<string>/Users/accusys/momentry/log/mongodb.log</string>
<string>--port</string>
<string>27017</string>
<string>--bind_ip</string>
<string>0.0.0.0</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>UserName</key>
<string>accusys</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/mongodb.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/mongodb.log</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/mongodb/</string>
</dict>
</plist>
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.monitor</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry_core_0.1/monitor</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh</string>
<string>check</string>
<string>all</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<false/>
<key>StartInterval</key>
<integer>300</integer>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/monitor/monitor.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/monitor/monitor.error.log</string>
<key>ProcessType</key>
<string>Background</string>
</dict>
</plist>
@@ -1,102 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.n8n.main</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/n8n</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/node@22/bin/node</string>
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>N8N_ENCRYPTION_KEY</key>
<string>Test3200Test3200Test3200</string>
<key>DOMAIN_NAME</key>
<string>n8n.momentry.ddns.net</string>
<key>WEBHOOK_URL</key>
<string>https://n8n.momentry.ddns.net/</string>
<key>N8N_PORT</key>
<string>5678</string>
<key>N8N_LISTEN_ADDRESS</key>
<string>0.0.0.0</string>
<key>N8N_USER_MANAGEMENT_JWT_DURATION</key>
<string>1h</string>
<key>GENERIC_TIMEZONE</key>
<string>Asia/Taipei</string>
<key>TZ</key>
<string>Asia/Taipei</string>
<key>N8N_LOG_LEVEL</key>
<string>info</string>
<key>DB_TYPE</key>
<string>postgresdb</string>
<key>DB_POSTGRESDB_HOST</key>
<string>127.0.0.1</string>
<key>DB_POSTGRESDB_PORT</key>
<string>5432</string>
<key>DB_POSTGRESDB_DATABASE</key>
<string>n8n</string>
<key>DB_POSTGRESDB_USER</key>
<string>n8n</string>
<key>DB_POSTGRESDB_PASSWORD</key>
<string>accusys</string>
<key>EXECUTIONS_MODE</key>
<string>queue</string>
<key>QUEUE_BULL_REDIS_HOST</key>
<string>127.0.0.1</string>
<key>QUEUE_BULL_REDIS_PORT</key>
<string>6379</string>
<key>QUEUE_BULL_REDIS_PASSWORD</key>
<string>accusys</string>
<key>N8N_USER_FOLDER</key>
<string>/Users/accusys/momentry/var/n8n</string>
<key>N8N_METRICS</key>
<string>true</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/n8n-main.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/n8n-main.error.log</string>
</dict>
</plist>
@@ -1,105 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.n8n.worker</string>
<key>UserName</key>
<string>accusys</string>
<key>GroupName</key>
<string>staff</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/n8n</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/node@22/bin/node</string>
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
<string>worker</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>N8N_ENCRYPTION_KEY</key>
<string>Test3200Test3200Test3200</string>
<key>DOMAIN_NAME</key>
<string>n8n.momentry.ddns.net</string>
<key>WEBHOOK_URL</key>
<string>https://n8n.momentry.ddns.net/</string>
<key>N8N_LISTEN_ADDRESS</key>
<string>0.0.0.0</string>
<key>GENERIC_TIMEZONE</key>
<string>Asia/Taipei</string>
<key>TZ</key>
<string>Asia/Taipei</string>
<key>N8N_LOG_LEVEL</key>
<string>info</string>
<key>DB_TYPE</key>
<string>postgresdb</string>
<key>DB_POSTGRESDB_HOST</key>
<string>127.0.0.1</string>
<key>DB_POSTGRESDB_PORT</key>
<string>5432</string>
<key>DB_POSTGRESDB_DATABASE</key>
<string>n8n</string>
<key>DB_POSTGRESDB_USER</key>
<string>n8n</string>
<key>DB_POSTGRESDB_PASSWORD</key>
<string>accusys</string>
<key>EXECUTIONS_MODE</key>
<string>queue</string>
<key>QUEUE_BULL_REDIS_HOST</key>
<string>127.0.0.1</string>
<key>QUEUE_BULL_REDIS_PORT</key>
<string>6379</string>
<key>QUEUE_BULL_REDIS_PASSWORD</key>
<string>accusys</string>
<key>N8N_USER_FOLDER</key>
<string>/Users/accusys/momentry/var/n8n</string>
<key>N8N_RUNNERS_BROKER_PORT</key>
<string>5690</string>
<key>N8N_RUNNERS_LAUNCHER_HEALTH_CHECK_PORT</key>
<string>5691</string>
<key>N8N_METRICS</key>
<string>true</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/n8n-worker.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/n8n-worker.error.log</string>
</dict>
</plist>
@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.ollama</string>
<key>UserName</key>
<string>accusys</string>
<key>EnvironmentVariables</key>
<dict>
<key>OLLAMA_HOST</key>
<string>0.0.0.0:11434</string>
<key>OLLAMA_MODELS</key>
<string>/Users/accusys/momentry/var/ollama/models</string>
<key>OLLAMA_FLASH_ATTENTION</key>
<string>1</string>
<key>OLLAMA_KV_CACHE_TYPE</key>
<string>q8_0</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/ollama</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/ollama/bin/ollama</string>
<string>serve</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/ollama.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/ollama.log</string>
</dict>
</plist>
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.php</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/php/sbin/php-fpm</string>
<string>--nodaemonize</string>
<string>--fpm-config</string>
<string>/Users/accusys/momentry/etc/php/8.5/php-fpm.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/php.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/php.log</string>
</dict>
</plist>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.postgresql</string>
<key>UserName</key>
<string>accusys</string>
<key>EnvironmentVariables</key>
<dict>
<key>LC_ALL</key>
<string>en_US.UTF-8</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/postgresql</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/postgresql@18/bin/postgres</string>
<string>-D</string>
<string>/Users/accusys/momentry/var/postgresql</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/postgresql.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/postgresql.log</string>
</dict>
</plist>
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.qdrant</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/qdrant/</string>
<key>ProgramArguments</key>
<array>
<string>/Users/accusys/.cargo/bin/qdrant</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>QDRANT__STORAGE__STORAGE_PATH</key>
<string>/Users/accusys/momentry/var/qdrant/</string>
<key>QDRANT__SERVICE__HOST</key>
<string>0.0.0.0</string>
<key>QDRANT__SERVICE__HTTP_PORT</key>
<string>6333</string>
<key>HOME</key>
<string>/Users/accusys</string>
<key>QDRANT__SERVICE__API_KEY</key>
<string>Test3200Test3200Test3200</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/qdrant.log</string>
<key>StandardErrorPath</key>
<string>/opt/homebrew/var/log/qdrant.error.log</string>
</dict>
</plist>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.redis</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/redis</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/redis/bin/redis-server</string>
<string>--port</string>
<string>6379</string>
<string>--bind</string>
<string>0.0.0.0</string>
<string>--requirepass</string>
<string>accusys</string>
<string>--dir</string>
<string>/Users/accusys/momentry/var/redis</string>
<string>--logfile</string>
<string>/Users/accusys/momentry/log/redis.log</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/redis.error.log</string>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/redis.log</string>
</dict>
</plist>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.rustdesk.hbbr</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/rustdesk</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/hbbr</string>
<string>-k</string>
<string>_</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/rustdesk-hbbr.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/rustdesk-hbbr.error.log</string>
</dict>
</plist>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.rustdesk.hbbs</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/rustdesk</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/hbbs</string>
<string>-k</string>
<string>_</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/rustdesk-hbbs.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/rustdesk-hbbs.error.log</string>
</dict>
</plist>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.sftpgo</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/workspace/sftpgo</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/sftpgo/bin/sftpgo</string>
<string>serve</string>
<string>--config-file</string>
<string>/Users/accusys/momentry/etc/sftpgo/sftpgo.json</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/accusys</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/sftpgo.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/sftpgo.error.log</string>
</dict>
</plist>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.SERVICE_NAME</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/SERVICE_NAME</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/executable</string>
<string>--arg1</string>
<string>value1</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/SERVICE_NAME.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/SERVICE_NAME.error.log</string>
</dict>
</plist>
-59
View File
@@ -1,59 +0,0 @@
#!/bin/bash
set -e
# Momentry Runtime Start Script
# This script starts the momentry services
echo "========================================"
echo "Momentry Runtime Starting"
echo "========================================"
# Set environment
export HOME=/Users/accusys
export USER=accusys
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/Users/accusys/.cargo/bin:$PATH"
# Database configuration
export DATABASE_URL="postgres://accusys@localhost:5432/momentry"
export REDIS_URL="redis://accusys:accusys@localhost:6379"
export OLLAMA_HOST="http://localhost:11434"
# Change to project directory
cd /Users/accusys/momentry_core_0.1
# Check dependencies
echo "Checking dependencies..."
# Check PostgreSQL
if ! pg_isready -h localhost -p 5432 -U accusys > /dev/null 2>&1; then
echo "WARNING: PostgreSQL is not ready"
fi
# Check Redis
if ! redis-cli -a accusys ping > /dev/null 2>&1; then
echo "WARNING: Redis is not ready"
fi
# Check Ollama
if ! curl -s http://localhost:11434/api/tags > /dev/null 2>&1; then
echo "WARNING: Ollama is not ready"
fi
echo "Dependencies checked."
# Start API server in background
echo "Starting API server..."
./target/release/momentry server --host 127.0.0.1 --port 3000 >> momentry_runtime/logs/stdout.log 2>&1 &
API_PID=$!
echo "API server started (PID: $API_PID)"
# Save PID to file
echo $API_PID > momentry_runtime/logs/momentry.pid
echo "========================================"
echo "Momentry Runtime Started"
echo "========================================"
echo "API Server: http://127.0.0.1:3000"
echo "Log file: momentry_runtime/logs/stdout.log"
echo ""
echo "To stop: kill \$(cat momentry_runtime/logs/momentry.pid)"
-25
View File
@@ -1,25 +0,0 @@
#!/bin/bash
set -e
# Momentry Runtime Stop Script
echo "Stopping Momentry Runtime..."
PID_FILE="/Users/accusys/momentry_core_0.1/momentry_runtime/logs/momentry.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if kill -0 "$PID" 2>/dev/null; then
echo "Stopping API server (PID: $PID)..."
kill "$PID"
rm -f "$PID_FILE"
echo "Momentry Runtime stopped."
else
echo "Process not running."
rm -f "$PID_FILE"
fi
else
echo "PID file not found. Trying to find and kill momentry processes..."
pkill -f "momentry server" || true
echo "Momentry Runtime stopped."
fi
-366
View File
@@ -1,366 +0,0 @@
# Momentry 監控系統
## 概述
Momentry 監控系統採用七層架構,涵蓋從外部服務到本地存儲的全部監控需求。
---
## 監控架構 (七層)
```
Layer 1: External 監控 - DDNS、網關、互聯網連接
Layer 2: Service 監控 - 15 個 momentry 服務
Layer 3: Workflow 監控 - n8n Workflow 狀態
Layer 4: Portal 監控 - WordPress 頁面與帳號
Layer 5: Database 監控 - PostgreSQL/Redis/Qdrant/MariaDB
Layer 6: 使用者監控 - 連線/本機使用者/異常檢測
Layer 7: Storage 監控 - 冷溫熱分層/歸檔/檔案註冊
```
---
## 快速開始
### 查看監控狀態
```bash
cd /Users/accusys/momentry_core_0.1/monitor
./control/monitor_control.sh status
```
### 執行全面檢查
```bash
./control/monitor_control.sh check all
```
### 查看特定層級
```bash
./control/monitor_control.sh check service # Layer 2
./control/monitor_control.sh check workflow # Layer 3
./control/monitor_control.sh check portal # Layer 4
./control/monitor_control.sh check database # Layer 5
./control/monitor_control.sh check users # Layer 6
./control/monitor_control.sh check storage # Layer 7
```
---
## 各層監控說明
### Layer 1: External 監控
監控外部依賴服務的可用性。
**監控項目**:
- DDNS 域名解析 (momentry.ddns.net)
- 網關連通性
- 外部 API 連接
**腳本**: `service/external_monitor.sh`
### Layer 2: Service 監控
監控 15 個 momentry 服務的運行狀態。
**監控項目**:
| 服務 | Port | 狀態檢查 |
|------|------|----------|
| PostgreSQL | 5432 | pg_isready |
| Redis | 6379 | redis-cli ping |
| MariaDB | 3306 | mysql ping |
| n8n | 5678 | HTTP GET |
| Caddy | 443 | HTTPS |
| Gitea | 3000 | HTTP |
| SFTPGo | 2222 | SSH |
| Ollama | 11434 | API |
| Qdrant | 6333 | API |
| PHP-FPM | - | Process |
| RustDesk | 21116 | Port |
| MongoDB | 27017 | Mongo ping |
**腳本**: `service/health_check.sh`
### Layer 3: n8n Workflow 監控
監控 n8n Workflow 的執行狀態和閒置分析。
**監控項目**:
- Workflow 數量與狀態
- 執行次數與結果
- 閒置 Workflow 識別
- 改善建議生成
**閒置定義**: 無排程 AND 無 API 觸發 AND 超過 30 天未執行
**腳本**:
- `workflow/n8n_workflow_monitor.sh`
- `workflow/idle_analyzer.sh`
### Layer 4: WordPress Portal 監控
監控 WordPress 頁面可訪問性和內部帳號。
**監控項目**:
- 首頁/登入頁可訪問性
- 響應時間
- 用戶列表與角色
- 新增/刪除用戶
**腳本**:
- `portal/page_monitor.sh`
- `portal/account_monitor.sh`
### Layer 5: Database 監控
監控所有資料庫的健康狀態和性能指標。
**PostgreSQL**:
- 表數量、行數、大小
- 死元組、慢查詢
- 表結構變更
**Redis**:
- 連線數、內存使用
- 命中率、操作數
- 客戶端列表
**Qdrant**:
- Collection 列表
- Points 數、向量維度
- 磁盤使用
**MariaDB**:
- 連線數、緩衝池
- WordPress 表結構
**腳本**:
- `database/postgres_monitor.sh`
- `database/redis_monitor.sh`
- `database/qdrant_monitor.sh`
- `database/mariadb_monitor.sh`
### Layer 6: 使用者監控
監控連線使用者和本機使用者的活動。
**連線使用者**:
- SSH 登入與命令
- Web 服務登入 (n8n, Gitea, WP)
- 資料庫連線
- SFTP 傳輸
**本機使用者**:
- 系統登入
- sudo 使用記錄
- 服務帳戶活動
- 異常檢測
**腳本**:
- `users/session_tracker.sh`
- `users/login_monitor.sh`
- `users/sudo_tracker.sh`
- `users/anomaly_detector.sh`
### Layer 7: Storage 架構
管理數據的冷溫熱分層和歸檔策略。
**目錄結構**:
```
/Users/accusys/momentry/
├── var/ # 服務數據 (熱)
├── etc/ # 配置 (溫)
├── log/ # 日誌 (溫)
├── data/ # 用戶數據
│ ├── family/ # 家庭集群
│ ├── work/ # 工作集群
│ ├── wordpress/ # WP 隔離
│ └── shared/ # 共享
├── backup/ # 備份
│ ├── daily/ # 每日備份 (保留 30 天)
│ ├── weekly/ # 每週備份 (保留 12 週)
│ ├── monthly/ # 每月備份 (保留 12 個月)
│ └── archive/ # 歸檔 (保留 12 個月+)
└── tmp/ # 臨時
```
**分層標準**:
| 等級 | 條件 | 存放 |
|------|------|------|
| 熱 | 7天內訪問 > 10次 | NVMe |
| 溫 | 30天內訪問 > 1次 | RAID |
| 冷 | 90天未訪問 | Object Storage |
**備份溫冷分層**:
| 等級 | 保留時間 | 用途 |
|------|---------|------|
| daily | 7天 | 快速恢復 |
| weekly | 30天 | 標準恢復 |
| monthly | 365天 | 長期歸檔 |
| archive | >365天 | 法規遵循 |
**腳本**:
- `storage/storage_manager.sh` - 存儲管理
- `storage/backup_monitor.sh` - 備份監控與溫冷轉移
- `storage/migration_engine.sh` - 數據遷移
- `storage/file_registry.py` - 檔案註冊
---
## 配置
### 主配置
編輯 `config/monitor_config.yaml`:
```yaml
monitoring:
enabled: true
check_interval: 300 # 秒
service:
enabled: true
services:
- postgresql
- redis
- mariadb
- n8n
- caddy
- gitea
- sftpgo
- ollama
- qdrant
- php
- rustdesk
workflow:
enabled: true
idle_threshold_days: 30
portal:
enabled: true
database:
enabled: true
users:
enabled: true
storage:
enabled: false # 獨立實現
```
---
## 數據庫表
監控數據存儲在 PostgreSQL `momentry` 資料庫。
**主要表**:
- `monitor_services` - 服務健康狀態
- `monitor_workflows` - n8n Workflow 監控
- `monitor_databases` - 資料庫指標
- `monitor_sessions` - 使用者會話
- `monitor_logins` - 登入歷史
- `monitor_anomalies` - 異常檢測
- `file_registry` - 檔案註冊
**創建表**: `database/schema.sql`
---
## 報警規則
| 層級 | 異常類型 | 等級 | 處理 |
|------|----------|------|------|
| Service | 服務宕機 | Critical | 記錄 |
| Service | 響應過慢 | Warning | 記錄 |
| Workflow | 閒置 > 30天 | Info | 記錄 |
| Workflow | 連續失敗 | Critical | 記錄 |
| Portal | 頁面不可訪問 | Critical | 記錄 |
| Database | 表結構變更 | Critical | 記錄 |
| Database | 連線過載 | Warning | 記錄 |
| Users | 暴力破解 | Critical | 記錄 |
| Users | 異常登入 | Warning | 記錄 |
**異常處理**: 僅記錄到資料庫,後續分析
---
## 維護
### 手動執行監控
```bash
# 單次檢查
./control/monitor_control.sh check service
# 持續監控 (每 5 分鐘)
./control/monitor_control.sh monitor
```
### 查看歷史
```bash
# 查看服務狀態
psql -U accusys -h localhost -d momentry -c "SELECT * FROM monitor_services ORDER BY checked_at DESC LIMIT 10;"
# 查看異常
psql -U accusys -h localhost -d momentry -c "SELECT * FROM monitor_anomalies WHERE detected_at > NOW() - INTERVAL '24 hours';"
```
### 清理歷史數據
```bash
# 保留 30 天
psql -U accusys -h localhost -d momentry -c "DELETE FROM monitor_services WHERE checked_at < NOW() - INTERVAL '30 days';"
```
---
## 文件結構
```
monitor/
├── MONITORING.md # 本文件
├── config/
│ └── monitor_config.yaml # 配置文件
├── control/
│ └── monitor_control.sh # 控制腳本
├── service/
│ ├── health_check.sh # 服務健康檢查
│ └── external_monitor.sh # 外部監控
├── workflow/
│ ├── n8n_workflow_monitor.sh
│ └── idle_analyzer.sh
├── portal/
│ ├── page_monitor.sh
│ └── account_monitor.sh
├── database/
│ ├── schema.sql # 數據庫表
│ ├── postgres_monitor.sh
│ ├── redis_monitor.sh
│ ├── qdrant_monitor.sh
│ └── mariadb_monitor.sh
├── users/
│ ├── session_tracker.sh
│ ├── login_monitor.sh
│ ├── sudo_tracker.sh
│ └── anomaly_detector.sh
├── storage/
│ ├── storage_manager.sh
│ └── migration_engine.sh
└── docs/
└── TROUBLESHOOTING.md
```
---
## 相關文檔
- [Storage 架構設計規範](./storage/STORAGE_SPEC.md)
- [WordPress 監控](./wordpress/MONITORING.md)
- [異常檢測規則](./users/ANOMALY_RULES.md)
-388
View File
@@ -1,388 +0,0 @@
# Momentry 服務故障排除 Skill
## 概述
此 Skill 提供常見服務問題的快速診斷參考,包含問題類型、檢查命令、日誌位置和常見解決方案。
---
## 快速檢查清單
### 1. 服務狀態檢查
```bash
# 查看所有 momentry 服務狀態
launchctl list | grep com.momentry
# 或執行健康檢查
/Users/accusys/momentry_core_0.1/monitor/service/health_check.sh
```
### 2. 端口佔用檢查
```bash
# 檢查特定端口
lsof -i :<PORT>
# 常用端口對照
# PostgreSQL: 5432
# Redis: 6379
# MariaDB: 3306
# n8n: 8085 (內部), 5678 (舊配置)
# Caddy Admin: 2019
# Ollama: 11434
# Qdrant: 6333
# SFTPGo: 8080 (HTTP), 2022 (SFTP)
# Gitea: 3000
# PHP-FPM: 9000
# RustDesk: 21115-21119
# MongoDB: 27017
```
---
## 服務詳細診斷
### PostgreSQL
**配置文件**: `/Users/accusys/momentry/etc/postgresql/`
**數據目錄**: `/Users/accusys/momentry/var/postgresql/`
**日誌**: `/Users/accusys/momentry/log/postgresql.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 連線失敗 | `pg_isready -h localhost -p 5432 -U accusys` | 檢查 plist 是否載入 |
| 認證錯誤 | `psql -U accusys -h localhost -d momentry -c "SELECT 1"` | 檢查 pg_hba.conf |
| 效能問題 | `psql -U accusys -h localhost -d momentry -c "SELECT * FROM pg_stat_activity"` | 檢查連線數 |
| 資料庫不存在 | `psql -U accusys -h localhost -l` | 創建數據庫 |
```bash
# 創建數據庫
createdb -U accusys momentry
```
---
### Redis
**配置文件**: `/opt/homebrew/etc/redis.conf`
**數據目錄**: `/Users/accusys/momentry/var/redis/`
**日誌**: `/Users/accusys/momentry/log/redis.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 連線失敗 | `redis-cli -a accusys ping` | 檢查密碼是否正確 |
| 認證錯誤 | `redis-cli -a accusys AUTH accusys` | 檢查密碼配置 |
| 記憶體過高 | `redis-cli -a accusys INFO memory` | 檢查 keys 數量 |
| 持久化失敗 | `redis-cli -a accusys LASTSAVE` | 檢查 RDB 配置 |
```bash
# 常用操作
redis-cli -a accusys SAVE # 觸發保存
redis-cli -a accusys FLUSHALL # 清空所有 keys
redis-cli -a accusys KEYS '*' # 查看所有 keys
```
---
### MariaDB
**配置文件**: `/Users/accusys/momentry/etc/mariadb/`
**數據目錄**: `/Users/accusys/momentry/var/mariadb/`
**日誌**: `/Users/accusys/momentry/log/mariadb.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 連線失敗 | `mysql -u accusys -e "SELECT 1"` | 檢查用戶權限 |
| 拒絕訪問 | `mysql -u root -e "SELECT user FROM mysql.user"` | 檢查用戶配置 |
| 效能問題 | `mysql -u accusys -e "SHOW PROCESSLIST"` | 檢查慢查詢 |
```bash
# 常用操作
mysql -u accusys -e "SHOW DATABASES"
mysql -u accusys -e "SHOW TABLES" momentry
```
---
### n8n
**配置文件**: `/Users/accusys/momentry/etc/n8n/`
**數據目錄**: `/Users/accusys/momentry/var/n8n/`
**日誌**: `/Users/accusys/momentry/log/n8n-main.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 網頁無法訪問 | `curl -s http://localhost:8085/` | 檢查端口是否正確 |
| API 錯誤 | `curl -s http://localhost:8085/healthz` | 檢查服務狀態 |
| Workflow 不執行 | 檢查 n8n log | 檢查 queue (Redis) 連線 |
| 資料庫連線失敗 | `psql -U n8n -h localhost -d n8n -c "SELECT 1"` | 檢查 PostgreSQL |
**重要**: n8n 使用 PostgreSQL (非 SQLite),端口為 8085
```bash
# 數據庫連線
psql -U n8n -h localhost -d n8n
# 查看 users
psql -U n8n -h localhost -d n8n -c "SELECT id, email, \"firstName\", \"roleSlug\" FROM \"user\";"
# 查看 workflows
psql -U n8n -h localhost -d n8n -c "SELECT id, name, active FROM workflow_entity;"
```
---
### Ollama
**配置文件**: `/Users/accusys/momentry/etc/ollama/`
**模型目錄**: `/Users/accusys/momentry/var/ollama/`
**日誌**: `/Users/accusys/momentry/log/ollama.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| API 無回應 | `curl -s http://localhost:11434/api/tags` | 檢查服務是否啟動 |
| 模型下載失敗 | `ollama list` | 重新下載模型 |
| 記憶體不足 | `ollama list` | 檢查已加載模型 |
```bash
# 常用操作
ollama list # 列出模型
ollama pull <model> # 下載模型
ollama run <model> <prompt> # 運行模型
```
---
### Qdrant
**配置文件**: `/Users/accusys/momentry/etc/qdrant/`
**數據目錄**: `/Users/accusys/momentry/var/qdrant/`
**日誌**: `/Users/accusys/momentry/log/qdrant.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| API 無回應 | `curl -s http://localhost:6333/collections` | 需要 API Key |
| 認證失敗 | `curl -s -H "api-key: Test3200Test3200" http://localhost:6333/collections` | 檢查 API Key |
| 效能問題 | `curl -s http://localhost:6333/cluster` | 檢查叢集狀態 |
```bash
# 需要認證的指令
curl -s -H "api-key: Test3200Test3200" http://localhost:6333/collections
curl -s -H "api-key: Test3200Test3200" http://localhost:6333/points/count
```
---
### Caddy
**配置文件**: `/Users/accusys/momentry/etc/caddy/`
**數據目錄**: `/Users/accusys/momentry/var/caddy/`
**日誌**: `/Users/accusys/momentry/log/caddy.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| Admin API 無回應 | `curl -s http://localhost:2019/config/` | 檢查 Caddy 進程 |
| 網站無法訪問 | `curl -s -I https://localhost:443/` | 檢查證書配置 |
| 代理失敗 | `curl -s http://localhost:2019/config/` | 檢查反向代理配置 |
```bash
# 重新載入配置
sudo launchctl unload /Library/LaunchDaemons/com.momentry.caddy.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.caddy.plist
```
---
### Gitea
**配置文件**: `/Users/accusys/momentry/etc/gitea/app.ini`
**數據目錄**: `/Users/accusys/momentry/var/gitea/`
**日誌**: `/Users/accusys/momentry/log/gitea.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 網頁無法訪問 | `curl -s http://localhost:3000/` | 檢查服務狀態 |
| Git 推送失敗 | `ssh git@localhost -p 22` | 檢查 SSH 配置 |
| 資料庫連線 | `mysql -u gitea -p gitea_db -e "SELECT 1"` | 檢查 PostgreSQL |
---
### SFTPGo
**配置文件**: `/Users/accusys/momentry/etc/sftpgo/sftpgo.json`
**數據目錄**: `/Users/accusys/momentry/var/sftpgo/`
**日誌**: `/Users/accusys/momentry/log/sftpgo.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| Web UI 無法訪問 | `curl -s http://localhost:8080/` | 檢查 HTTP 端口 |
| SFTP 連線失敗 | `sftp -P 2022 user@localhost` | 檢查 SSH 密鑰 |
| 認證失敗 | `curl -s http://localhost:8080/api/v2/info` | 檢查用戶配置 |
---
### PHP-FPM
**配置文件**: `/Users/accusys/momentry/etc/php/`
**日誌**: `/Users/accusys/momentry/log/php.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| PHP 無法執行 | `php -v` | 檢查 PHP 版本 |
| FPM 無回應 | `curl -s http://localhost:9000/` | 檢查 FPM 端口 |
| 500 錯誤 | `tail -100 /Users/accusys/momentry/log/php.error.log` | 檢查錯誤日誌 |
---
### RustDesk
**配置文件**: `/Users/accusys/momentry/etc/rustdesk/`
**日誌**: `/Users/accusys/momentry/log/rustdesk.*.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 連線失敗 | `nc -z localhost 21116 && nc -z localhost 21117` | 檢查端口 |
| ID 伺服器錯誤 | `pgrep -a hbbs` | 檢查 hbbs 進程 |
| 中繼伺服器錯誤 | `pgrep -a hbbr` | 檢查 hbbr 進程 |
---
### MongoDB
**配置文件**: `/Users/accusys/momentry/etc/mongodb/`
**數據目錄**: `/Users/accusys/momentry/var/mongodb/`
**日誌**: `/Users/accusys/momentry/log/mongodb.log`
| 問題 | 檢查命令 | 解決方案 |
|------|----------|----------|
| 連線失敗 | `mongosh --quiet --eval "db.adminCommand('ping')"` | 需要認證 |
| 認證錯誤 | `mongosh -u accusys -p Test3200Test3200 --authenticationDatabase admin` | 檢查用戶 |
| 效能問題 | `mongosh -u accusys -p Test3200Test3200 --eval "db.serverStatus()"` | 檢查狀態 |
---
## 服務管理命令
### 啟動服務
```bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.<service>.plist
```
### 停止服務
```bash
sudo launchctl unload /Library/LaunchDaemons/com.momentry.<service>.plist
```
### 重啟服務
```bash
sudo launchctl unload /Library/LaunchDaemons/com.momentry.<service>.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.<service>.plist
```
### 查看服務日誌
```bash
tail -f /Users/accusys/momentry/log/<service>.log
tail -f /Users/accusys/momentry/log/<service>.error.log
```
---
## 服務端口對照表
| 服務 | 內部端口 | 外部端口 | Caddy 域名 |
|------|---------|---------|------------|
| PostgreSQL | 5432 | 5432 | - |
| Redis | 6379 | 6379 | - |
| MariaDB | 3306 | 3306 | - |
| n8n | 8085 | 443 | n8n.momentry.ddns.net |
| Ollama | 11434 | 11434 | - |
| Qdrant | 6333 | 443 | qdrant.momentry.ddns.net |
| Caddy Admin | 2019 | 2019 | - |
| Gitea | 3000 | 443 | gitea.momentry.ddns.net |
| SFTPGo | 8080/2022 | 443 | sftpgo.momentry.ddns.net |
| PHP-FPM | 9000 | - | - |
| MongoDB | 27017 | 27017 | - |
| RustDesk | 21115-21119 | 21115-21119 | - |
---
## 常見問題快速修復
### 1. 服務無法啟動
```bash
# 1. 檢查進程
pgrep -a <service_name>
# 2. 檢查端口
lsof -i :<PORT>
# 3. 檢查日誌
tail -100 /Users/accusys/momentry/log/<service>.error.log
# 4. 重新載入
sudo launchctl unload /Library/LaunchDaemons/com.momentry.<service>.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.<service>.plist
```
### 2. 認證失敗
```bash
# 檢查用戶是否存在
psql -U <db_user> -h localhost -d <db_name> -c "SELECT 1"
# 檢查密碼
# 參考各服務的 INSTALL_*.md 文檔
```
### 3. 效能問題
```bash
# 檢查系統資源
top -o cpu
top -o mem
# 檢查磁盤空間
df -h
# 檢查網絡連線
netstat -an | grep ESTABLISHED
```
---
## 監控腳本位置
- 健康檢查: `/Users/accusys/momentry_core_0.1/monitor/service/health_check.sh`
- PostgreSQL: `/Users/accusys/momentry_core_0.1/monitor/database/postgres_monitor.sh`
- Redis: `/Users/accusys/momentry_core_0.1/monitor/database/redis_monitor.sh`
- Qdrant: `/Users/accusys/momentry_core_0.1/monitor/database/qdrant_monitor.sh`
- MongoDB: `/Users/accusys/momentry_core_0.1/monitor/database/mongodb_monitor.sh`
- n8n Workflow: `/Users/accusys/momentry_core_0.1/monitor/workflow/n8n_workflow_monitor.sh`
---
## 密碼參考 (請修改為真實密碼)
| 服務 | 用戶 | 密碼 |
|------|------|------|
| PostgreSQL | accusys | (无密码) |
| PostgreSQL | n8n | accusys |
| Redis | - | accusys |
| MariaDB | accusys | - |
| n8n | - | (Web UI 配置) |
| Qdrant | - | Test3200Test3200 |
| MongoDB | accusys | Test3200Test3200 |
---
## 文檔位置
- 安裝文檔: `/Users/accusys/momentry_core_0.1/docs/INSTALL_*.md`
- 服務管理: `/Users/accusys/momentry_core_0.1/docs/SERVICE_ADDITION_GUIDE.md`
- Plist 模板: `/Users/accusys/momentry_core_0.1/momentry_runtime/plist/`
-503
View File
@@ -1,503 +0,0 @@
# Momentry 監控系統配置文件
# 路徑: /Users/accusys/momentry_core_0.1/monitor/config/monitor_config.yaml
monitoring:
enabled: true
check_interval: 300 # 秒 (5分鐘)
# 數據庫連接
database:
host: "localhost"
port: 5432
username: "accusys"
password: "" # 使用 psql 連接
name: "momentry"
# ============================================================
# Layer 1: External 監控
# ============================================================
external:
enabled: true
check_interval: 60 # 秒
targets:
- name: "ddns"
type: "ddns"
host: "momentry.ddns.net"
enabled: true
- name: "gateway"
type: "gateway"
host: "192.168.110.1"
enabled: true
- name: "internet"
type: "internet"
host: "8.8.8.8"
enabled: true
# ============================================================
# Layer 2: Service 監控
# ============================================================
service:
enabled: true
check_interval: 300 # 秒
services:
- name: "postgresql"
type: "postgres"
port: 5432
host: "localhost"
check_cmd: "pg_isready -h localhost -p 5432 -U accusys"
timeout: 5
enabled: true
- name: "redis"
type: "redis"
port: 6379
host: "localhost"
password: "accusys"
check_cmd: "redis-cli -a accusys ping"
timeout: 5
enabled: true
- name: "mariadb"
type: "mariadb"
port: 3306
host: "localhost"
check_cmd: "mysql -u root -e 'SELECT 1'"
timeout: 5
enabled: true
- name: "n8n"
type: "http"
port: 5678
host: "localhost"
check_url: "http://localhost:5678/"
timeout: 10
enabled: true
- name: "caddy"
type: "http"
port: 2019
host: "localhost"
check_url: "http://localhost:2019/config/"
timeout: 10
enabled: true
- name: "gitea"
type: "http"
port: 3000
host: "localhost"
check_url: "http://localhost:3000/"
timeout: 10
enabled: true
- name: "sftpgo"
type: "http"
port: 8080
host: "localhost"
timeout: 10
enabled: true
- name: "ollama"
type: "http"
port: 11434
host: "localhost"
check_url: "http://localhost:11434/api/tags"
timeout: 10
enabled: true
- name: "qdrant"
type: "http"
port: 6333
host: "localhost"
check_url: "http://localhost:6333/collections"
timeout: 10
enabled: true
- name: "mongodb"
type: "mongodb"
port: 27017
host: "localhost"
timeout: 10
enabled: true
- name: "php"
type: "process"
process_name: "php-fpm"
enabled: true
- name: "node"
type: "process"
process_name: "node"
enabled: true
check_interval: 60
version_lock: "22.x"
locked_processes:
- "n8n"
description: "Node.js 運行環境 (n8n 專用)"
- name: "python"
type: "process"
process_name: "python3"
enabled: true
check_interval: 60
version_lock: "3.11.14"
scripts:
- "/Users/accusys/momentry_core_0.1/scripts/asr_processor.py"
- "/Users/accusys/momentry_core_0.1/scripts/thumbnail_extractor.py"
description: "Python 運行環境 (Momentry 腳本專用)"
- name: "rustdesk_hbbs"
type: "process"
process_name: "hbbs"
port: 21116
enabled: true
- name: "rustdesk_hbbr"
type: "process"
process_name: "hbbr"
port: 21117
enabled: true
# ============================================================
# Layer 3: n8n Workflow 監控
# ============================================================
workflow:
enabled: true
check_interval: 300 # 秒
n8n:
host: "http://localhost:5678"
api_key: "" # 從環境變數或 n8n 獲取
idle_threshold_days: 30
suggestions:
- type: "disable_idle"
threshold_days: 30
- type: "delete_unused"
threshold_days: 90
- type: "optimize_failures"
failure_rate_threshold: 0.2
# ============================================================
# Layer 4: WordPress Portal 監控
# ============================================================
portal:
enabled: true
check_interval: 300 # 秒
wordpress:
site_url: "https://wp.momentry.ddns.net"
db_host: "localhost"
db_name: "wordpress"
db_user: "wp_user"
db_password: "wp_password_123"
page_monitoring:
enabled: true
pages:
- url: "/"
name: "homepage"
- url: "/wp-login.php"
name: "login_page"
response_time_threshold_ms: 3000
account_monitoring:
enabled: true
check_interval: 3600 # 小時
alert_on_new_admin: true
# ============================================================
# Layer 5: Database 監控
# ============================================================
database:
enabled: true
check_interval: 300 # 秒
postgres:
enabled: true
databases:
- name: "momentry"
- name: "gitea"
- name: "n8n"
- name: "video_register"
schema_monitoring: true
redis:
enabled: true
password: "accusys"
alert_thresholds:
memory_percent: 80
connected_clients: 100
qdrant:
enabled: true
collections_watch: ["*"] # 監控所有
mariadb:
enabled: true
databases:
- name: "wordpress"
mongodb:
enabled: true
databases:
- name: "momentry"
- name: "admin"
# ============================================================
# Layer 6: 使用者監控
# ============================================================
users:
enabled: true
check_interval: 60 # 秒
session_tracking:
enabled: true
track_ssh: true
track_web: true
track_db: true
track_sftp: true
login_monitoring:
enabled: true
track_system: true
track_wordpress: true
track_n8n: true
track_gitea: true
sudo_tracking:
enabled: true
anomaly_detection:
enabled: true
rules:
- type: "brute_force"
threshold: 5
window_seconds: 60
severity: "critical"
- type: "unusual_time"
severity: "medium"
allowed_hours: "08:00-22:00"
- type: "idle_session"
threshold_hours: 24
severity: "low"
# ============================================================
# Layer 7: Storage 監控 (獨立配置)
# ============================================================
storage:
enabled: false # 獨立實現
paths:
hot: "/Users/accusys/momentry/data"
warm: "/Volumes/RAID System/momentry/warm"
cold: "/Volumes/Object Storage/momentry/archive"
temp: "/Users/accusys/momentry/tmp"
backup: "/Users/accusys/momentry/backup"
clusters:
- name: "family"
path: "data/family"
quota: "1TB"
- name: "work"
path: "data/work"
quota: "2TB"
- name: "wordpress"
path: "data/wordpress"
quota: "500GB"
- name: "shared"
path: "data/shared"
quota: "1TB"
migration:
hot_to_warm_days: 7
warm_to_cold_days: 90
# ============================================================
# Layer 7: 備份監控
# ============================================================
backup:
enabled: true
check_interval: 3600 # 秒 (每小時檢查一次)
# 備份根目錄
backup_root: "/Users/accusys/momentry/backup"
# 服務列表
services:
- name: "postgresql"
enabled: true
backup_type: "database"
method: "pg_dump"
schedule: "daily"
retention:
daily: 7
weekly: 4
monthly: 12
- name: "redis"
enabled: true
backup_type: "database"
method: "rdb"
schedule: "daily"
retention:
daily: 7
weekly: 4
- name: "mariadb"
enabled: true
backup_type: "database"
method: "mysqldump"
schedule: "daily"
retention:
daily: 7
weekly: 4
- name: "n8n"
enabled: true
backup_type: "full"
method: "tar"
schedule: "daily"
retention:
daily: 7
weekly: 4
- name: "qdrant"
enabled: true
backup_type: "database"
method: "snapshot"
schedule: "daily"
retention:
daily: 7
weekly: 4
- name: "gitea"
enabled: true
backup_type: "full"
method: "gitea_dump"
schedule: "weekly"
retention:
weekly: 4
monthly: 12
- name: "mongodb"
enabled: true
backup_type: "database"
method: "mongodump"
schedule: "daily"
retention:
daily: 7
weekly: 4
- name: "ollama"
enabled: true
backup_type: "config"
method: "tar"
schedule: "weekly"
retention:
weekly: 4
monthly: 12
- name: "caddy"
enabled: true
backup_type: "config"
method: "file"
schedule: "weekly"
retention:
weekly: 4
- name: "sftpgo"
enabled: true
backup_type: "config"
method: "file"
schedule: "weekly"
retention:
weekly: 4
- name: "mongodb"
enabled: true
backup_type: "config"
method: "file"
schedule: "weekly"
retention:
weekly: 4
- name: "php"
enabled: true
backup_type: "config"
method: "file"
schedule: "weekly"
retention:
weekly: 4
# 溫冷轉移配置
tiering:
enabled: true
tiering_interval: 86400 # 秒 (每天)
rules:
- from: "daily"
to: "weekly"
after_days: 7
- from: "weekly"
to: "monthly"
after_days: 30
- from: "monthly"
to: "archive"
after_days: 90
# 存儲閾值
thresholds:
backup_age_warning_days: 7
backup_age_critical_days: 14
storage_percent_warning: 80
storage_percent_critical: 90
# 驗證
verify:
enabled: true
verify_on_completion: true
test_restore: false # 僅測試還原,不實際執行
# ============================================================
# 通知配置
# ============================================================
notifications:
enabled: true
log_only: true # 僅記錄,不發送
# 日誌記錄
log:
enabled: true
path: "/Users/accusys/momentry/log/monitor"
# n8n webhook (預設不啟用)
n8n:
enabled: false
webhook_url: "http://localhost:5678/webhook/monitor-alert"
# Telegram (預設不啟用)
telegram:
enabled: false
bot_token: ""
chat_id: ""
# Email (預設不啟用)
email:
enabled: false
smtp_host: ""
smtp_port: 587
smtp_user: ""
smtp_password: ""
from_address: ""
to_addresses: []
# ============================================================
# 數據保留
# ============================================================
retention:
history_days: 30
anomaly_days: 90
session_days: 7
login_days: 30
# ============================================================
# 報警閾值
# ============================================================
thresholds:
service_response_time_ms: 3000
database_memory_percent: 80
disk_percent: 90
cpu_percent: 90
login_failures_per_user: 3
brute_force_per_minute: 5
-362
View File
@@ -1,362 +0,0 @@
#!/bin/bash
# Momentry 監控系統控制腳本
# 路徑: /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
CONFIG_DIR="$MONITOR_DIR/config"
LOG_DIR="/Users/accusys/momentry/log/monitor"
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ============================================================
# 幫助信息
# ============================================================
show_help() {
cat << EOF
Momentry 監控系統控制腳本
用法: $0 <command> [options]
命令:
status 查看監控狀態
check <layer> 執行特定層級檢查
layers: service, workflow, portal, database, users, storage, external, backup, node, python, all
monitor 持續監控 (每 5 分鐘)
init 初始化監控數據庫表
logs <layer> [lines] 查看日誌
clean 清理歷史數據
help 顯示幫助
示例:
$0 status 查看所有監控狀態
$0 check service 檢查服務狀態
$0 check backup 檢查備份狀態
$0 check all 執行全面檢查
$0 logs anomaly 50 查看最近 50 條異常
$0 init 初始化數據庫表
EOF
}
# ============================================================
# 初始化
# ============================================================
init_monitor() {
echo -e "${BLUE}初始化監控系統...${NC}"
# 創建日誌目錄
mkdir -p "$LOG_DIR"
# 創建數據庫表
echo "創建監控數據庫表..."
psql -U accusys -h localhost -d momentry -f "$MONITOR_DIR/database/schema.sql" 2>/dev/null || \
echo "數據庫表可能已存在"
echo -e "${GREEN}初始化完成${NC}"
}
# ============================================================
# 狀態查看
# ============================================================
show_status() {
echo ""
echo "========================================"
echo -e "${BLUE}Momentry 監控系統狀態${NC}"
echo "========================================"
echo ""
# 服務狀態
echo -e "${YELLOW}Layer 2: 服務狀態${NC}"
local service_count=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM monitor_services WHERE checked_at > NOW() - INTERVAL '5 minutes' AND status = 'up';" 2>/dev/null || echo "0")
local service_total=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(DISTINCT service_name) FROM monitor_services;" 2>/dev/null || echo "0")
echo " 服務: $service_count / $service_total 正常"
echo ""
# Workflow 狀態
echo -e "${YELLOW}Layer 3: Workflow 狀態${NC}"
local active_wf=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM monitor_workflows WHERE is_active = true;" 2>/dev/null || echo "0")
local idle_wf=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM monitor_workflows WHERE idle_days > 30;" 2>/dev/null || echo "0")
echo " 啟用 Workflow: $active_wf"
echo " 閒置 Workflow (>30天): $idle_wf"
echo ""
# Database 狀態
echo -e "${YELLOW}Layer 5: Database 狀態${NC}"
local db_count=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(DISTINCT db_type) FROM monitor_databases WHERE checked_at > NOW() - INTERVAL '5 minutes';" 2>/dev/null || echo "0")
echo " 監控資料庫: $db_count"
echo ""
# 異常狀態
echo -e "${YELLOW}Layer 6: 異常狀態${NC}"
local critical=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM monitor_anomalies WHERE severity = 'critical' AND detected_at > NOW() - INTERVAL '24 hours';" 2>/dev/null || echo "0")
local warning=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM monitor_anomalies WHERE severity IN ('medium', 'high') AND detected_at > NOW() - INTERVAL '24 hours';" 2>/dev/null || echo "0")
echo " Critical: $critical"
echo " Warning: $warning"
echo ""
# Node.js 狀態
echo -e "${YELLOW}Node.js 運行環境${NC}"
local node_compliant=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM node_version_baseline WHERE is_compliant = true;" 2>/dev/null || echo "0")
local node_total=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM node_version_baseline;" 2>/dev/null || echo "0")
echo " 版本合規: $node_compliant / $node_total"
echo ""
# Python 狀態
echo -e "${YELLOW}Python 運行環境${NC}"
local python_compliant=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM python_version_baseline WHERE is_compliant = true;" 2>/dev/null || echo "0")
local python_total=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM python_version_baseline;" 2>/dev/null || echo "0")
echo " 版本合規: $python_compliant / $python_total"
echo ""
# 備份狀態
echo -e "${YELLOW}Layer 7: 備份狀態${NC}"
local total_backups=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM backup_registry WHERE created_at > NOW() - INTERVAL '7 days';" 2>/dev/null || echo "0")
local failed_backups=$(psql -U accusys -h localhost -d momentry -t -A -c "SELECT COUNT(*) FROM backup_registry WHERE status = 'failed' AND created_at > NOW() - INTERVAL '7 days';" 2>/dev/null || echo "0")
echo " 本週備份: $total_backups"
echo " 失敗: $failed_backups"
echo ""
echo "========================================"
echo "使用 '$0 check <layer>' 執行檢查"
echo ""
}
# ============================================================
# 執行檢查
# ============================================================
check_layer() {
local layer=$1
case $layer in
service)
echo -e "${BLUE}執行 Layer 2: 服務監控...${NC}"
bash "$MONITOR_DIR/service/health_check.sh"
;;
workflow)
echo -e "${BLUE}執行 Layer 3: Workflow 監控...${NC}"
bash "$MONITOR_DIR/workflow/n8n_workflow_monitor.sh"
;;
portal)
echo -e "${BLUE}執行 Layer 4: Portal 監控...${NC}"
bash "$MONITOR_DIR/portal/page_monitor.sh"
;;
database)
echo -e "${BLUE}執行 Layer 5: Database 監控...${NC}"
bash "$MONITOR_DIR/database/postgres_monitor.sh"
bash "$MONITOR_DIR/database/redis_monitor.sh"
bash "$MONITOR_DIR/database/qdrant_monitor.sh"
;;
users)
echo -e "${BLUE}執行 Layer 6: 使用者監控...${NC}"
bash "$MONITOR_DIR/users/session_tracker.sh"
;;
storage)
echo -e "${BLUE}執行 Layer 7: Storage 監控...${NC}"
bash "$MONITOR_DIR/storage/storage_manager.sh" status
;;
backup)
echo -e "${BLUE}執行 Layer 7: 備份監控...${NC}"
bash "$MONITOR_DIR/storage/backup_monitor.sh" status
;;
external)
echo -e "${BLUE}執行 Layer 1: External 監控...${NC}"
bash "$MONITOR_DIR/service/external_monitor.sh"
;;
node)
echo -e "${BLUE}執行 Node.js 版本監控...${NC}"
bash "$MONITOR_DIR/service/node_monitor.sh"
;;
python)
echo -e "${BLUE}執行 Python 版本監控...${NC}"
bash "$MONITOR_DIR/service/python_monitor.sh"
;;
all)
echo -e "${BLUE}執行全面監控檢查...${NC}"
check_layer external
check_layer service
check_layer node
check_layer python
check_layer workflow
check_layer portal
check_layer database
check_layer users
echo -e "${GREEN}全面檢查完成${NC}"
;;
*)
echo -e "${RED}未知層級: $layer${NC}"
show_help
exit 1
;;
esac
}
# ============================================================
# 持續監控
# ============================================================
run_monitor() {
echo -e "${BLUE}開始持續監控 (每 5 分鐘)${NC}"
echo "按 Ctrl+C 停止"
echo ""
while true; do
local start_time=$(date +%s)
check_layer all
local end_time=$(date +%s)
local elapsed=$((end_time - start_time))
if [ $elapsed -lt 300 ]; then
sleep $((300 - elapsed))
fi
done
}
# ============================================================
# 查看日誌
# ============================================================
show_logs() {
local layer=${1:-anomaly}
local lines=${2:-20}
case $layer in
anomaly)
echo -e "${BLUE}最近異常記錄:${NC}"
psql -U accusys -h localhost -d momentry -c "
SELECT
TO_CHAR(detected_at, 'YYYY-MM-DD HH24:MI:SS') as time,
severity,
anomaly_type,
username,
LEFT(description, 50) as desc
FROM monitor_anomalies
ORDER BY detected_at DESC
LIMIT $lines;
" 2>/dev/null || echo "無法連接資料庫"
;;
service)
echo -e "${BLUE}最近服務狀態:${NC}"
psql -U accusys -h localhost -d momentry -c "
SELECT
service_name,
status,
response_time_ms,
TO_CHAR(checked_at, 'YYYY-MM-DD HH24:MI:SS') as time
FROM monitor_services
ORDER BY checked_at DESC
LIMIT $lines;
" 2>/dev/null || echo "無法連接資料庫"
;;
workflow)
echo -e "${BLUE}最近 Workflow 狀態:${NC}"
psql -U accusys -h localhost -d momentry -c "
SELECT
workflow_name,
is_active,
idle_days,
suggestion,
TO_CHAR(checked_at, 'YYYY-MM-DD HH24:MI:SS') as time
FROM monitor_workflows
ORDER BY checked_at DESC
LIMIT $lines;
" 2>/dev/null || echo "無法連接資料庫"
;;
*)
echo -e "${RED}未知日誌類型: $layer${NC}"
;;
esac
}
# ============================================================
# 清理歷史數據
# ============================================================
clean_history() {
echo -e "${YELLOW}清理歷史數據...${NC}"
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null || true
-- 保留 30 天
DELETE FROM monitor_services WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM monitor_workflows WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM monitor_databases WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM monitor_external WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM monitor_portal_pages WHERE checked_at < NOW() - INTERVAL '30 days';
-- 保留 30 天版本基線
DELETE FROM node_version_baseline WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM node_process_tracking WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM python_version_baseline WHERE checked_at < NOW() - INTERVAL '30 days';
DELETE FROM python_script_tracking WHERE checked_at < NOW() - INTERVAL '30 days';
-- 保留 7 天會話
DELETE FROM monitor_sessions WHERE connected_at < NOW() - INTERVAL '7 days';
-- 保留 30 天登入
DELETE FROM monitor_logins WHERE login_at < NOW() - INTERVAL '30 days';
-- 保留 90 天異常
DELETE FROM monitor_anomalies WHERE detected_at < NOW() - INTERVAL '90 days';
-- 清理空閒空間
VACUUM ANALYZE;
EOF
echo -e "${GREEN}清理完成${NC}"
}
# ============================================================
# 主程序
# ============================================================
main() {
# 確保日誌目錄存在
mkdir -p "$LOG_DIR"
local command=${1:-status}
case $command in
status)
show_status
;;
check)
check_layer ${2:-all}
;;
monitor)
run_monitor
;;
init)
init_monitor
;;
logs)
show_logs ${2:-anomaly} ${3:-20}
;;
clean)
clean_history
;;
help|--help|-h)
show_help
;;
*)
echo -e "${RED}未知命令: $command${NC}"
show_help
exit 1
;;
esac
}
main "$@"
-82
View File
@@ -1,82 +0,0 @@
#!/bin/bash
# Momentry MongoDB 監控 (Layer 5)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/database/mongodb_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/mongodb_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
MONGO_USER="accusys"
MONGO_PASS="Test3200Test3200"
record_metric() {
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_databases (db_type, db_name, metric_name, metric_value, checked_at)
VALUES ('mongodb', 'mongodb', '$1', '$2', NOW());
EOF
}
get_status() {
mongosh --quiet --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "JSON.stringify(db.adminCommand({ replSetGetStatus: 1 }))" 2>/dev/null || echo "{}"
}
get_server_status() {
mongosh --quiet --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "JSON.stringify(db.serverStatus()))" 2>/dev/null || echo "{}"
}
get_databases() {
mongosh --quiet --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "JSON.stringify(db.adminCommand({ listDatabases: 1 }))" 2>/dev/null || echo "{}"
}
echo "========================================"
echo "Layer 5: MongoDB Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
if ! mongosh --quiet --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.adminCommand('ping')" > /dev/null 2>&1; then
echo "MongoDB 不可用"
log "MongoDB unavailable"
exit 1
fi
echo "✓ MongoDB 連接正常"
echo ""
echo "資料庫:"
echo "----------------------------------------"
databases=$(get_databases)
echo "$databases" | jq -r '.databases[] | " \(.name): \(.sizeOnDisk / 1024 / 1024 | floor)MB"' 2>/dev/null || echo " 無法獲取資料庫列表"
echo ""
echo "伺服器狀態:"
echo "----------------------------------------"
server_status=$(get_server_status)
connections=$(echo "$server_status" | jq -r '.connections.current' 2>/dev/null || echo "N/A")
active_connections=$(echo "$server_status" | jq -r '.connections.active' 2>/dev/null || echo "N/A")
uptime=$(echo "$server_status" | jq -r '.uptime' 2>/dev/null || echo "N/A")
mem_resident=$(echo "$server_status" | jq -r '.mem.resident' 2>/dev/null || echo "N/A")
echo " 當前連接: $connections"
echo " 活躍連接: $active_connections"
echo " 運行時間: ${uptime}"
echo " 記憶體使用: ${mem_resident}MB"
record_metric "connections" "$connections"
record_metric "active_connections" "$active_connections"
record_metric "uptime" "$uptime"
record_metric "mem_resident" "$mem_resident"
echo ""
log "MongoDB check completed: connections=$connections"
echo "========================================"
echo "完成"
echo "========================================"
-130
View File
@@ -1,130 +0,0 @@
#!/bin/bash
# Momentry PostgreSQL 監控 (Layer 5)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/database/postgres_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/postgres_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 記錄指標
record_metric() {
local db_type="postgresql"
local db_name=$1
local metric_name=$2
local value=$3
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_databases (db_type, db_name, metric_name, metric_value, checked_at)
VALUES ('$db_type', '$db_name', '$metric_name', '$value', NOW());
EOF
}
# 獲取資料庫列表
get_databases() {
psql -U accusys -h localhost -t -A -c "SELECT datname FROM pg_database WHERE datistemplate = false;" 2>/dev/null
}
# 獲取表大小
get_table_sizes() {
local db=$1
psql -U accusys -h localhost -d "$db" -t -A -c "
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size,
n_live_tup as rows
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;
" 2>/dev/null
}
# 獲取連線數
get_connections() {
psql -U accusys -h localhost -t -A -c "
SELECT state, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state;
" 2>/dev/null
}
# 獲取慢查詢
get_slow_queries() {
psql -U accusys -h localhost -t -A -c "
SELECT query, calls, mean_time
FROM pg_stat_statements
WHERE query NOT LIKE '%pg_stat_statements%'
ORDER BY mean_time DESC
LIMIT 5;
" 2>/dev/null
}
# 主程序
echo "========================================"
echo "Layer 5: PostgreSQL Database Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
# 檢查 PostgreSQL 是否可用
if ! pg_isready -h localhost -p 5432 -U accusys > /dev/null 2>&1; then
echo "PostgreSQL 不可用"
log "PostgreSQL unavailable"
exit 1
fi
# 記錄連線數
connections=$(get_connections)
echo "連線狀態:"
echo "$connections"
echo ""
# 記錄指標
record_metric "postgres" "connections" "'$connections'"
# 檢查各資料庫
echo "資料庫表:"
echo "----------------------------------------"
for db in $(get_databases); do
echo ""
echo "資料庫: $db"
table_count=$(psql -U accusys -h localhost -d "$db" -t -A -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" 2>/dev/null || echo "0")
echo " 表數量: $table_count"
record_metric "$db" "table_count" "$table_count"
# 顯示大表
echo " 大表:"
get_table_sizes "$db" | while read -r schema table size rows; do
[ -z "$table" ] && continue
echo " - $table: $size ($rows rows)"
done
done
# 檢查慢查詢(如果 pg_stat_statements 可用)
echo ""
echo "慢查詢 (如有):"
slow_queries=$(get_slow_queries)
if [ -n "$slow_queries" ]; then
echo "$slow_queries" | while read -r query calls time; do
[ -z "$query" ] && continue
echo " - ${time}ms (調用 $calls 次)"
done
else
echo " (pg_stat_statements 未啟用)"
fi
echo ""
echo "========================================"
log "PostgreSQL check completed"
-124
View File
@@ -1,124 +0,0 @@
#!/bin/bash
# Momentry Qdrant 監控 (Layer 5)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/database/qdrant_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/qdrant_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
QDRANT_HOST="http://localhost:6333"
QDRANT_API_KEY="Test3200Test3200Test3200"
# 記錄指標
record_metric() {
local collection=$1
local metric_name=$2
local value=$3
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_databases (db_type, db_name, metric_name, metric_value, checked_at)
VALUES ('qdrant', '$collection', '$metric_name', '$value', NOW());
EOF
}
# 記錄 Collection
record_collection() {
local name=$1
local vectors=$2
local points=$3
local disk_size=$4
local status=$5
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_qdrant_collections (collection_name, vectors_count, points_count, disk_size_bytes, status, snapshot_at)
VALUES ('$name', $vectors, $points, $disk_size, '$status', NOW())
ON CONFLICT (collection_name) DO UPDATE SET
vectors_count = EXCLUDED.vectors_count,
points_count = EXCLUDED.points_count,
disk_size_bytes = EXCLUDED.disk_size_bytes,
status = EXCLUDED.status,
snapshot_at = NOW();
EOF
}
# 主程序
echo "========================================"
echo "Layer 5: Qdrant Vector Database Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
# 檢查 Qdrant 是否可用
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
-H "api-key: $QDRANT_API_KEY" \
"$QDRANT_HOST/collections" 2>/dev/null || echo "000")
if [ "$http_code" = "000" ]; then
echo "Qdrant 不可用"
log "Qdrant unavailable"
exit 1
fi
echo "Qdrant 狀態: HTTP $http_code"
echo ""
# 獲取 Collection 列表
collections=$(curl -s -H "api-key: $QDRANT_API_KEY" "$QDRANT_HOST/collections" 2>/dev/null)
if [ "$http_code" = "200" ]; then
collection_count=$(echo "$collections" | jq '.result.collections | length' 2>/dev/null || echo "0")
echo "Collection 數量: $collection_count"
echo ""
# 遍歷每個 Collection
echo "Collections:"
echo "----------------------------------------"
for i in $(seq 0 $((collection_count - 1))); do
name=$(echo "$collections" | jq -r ".result.collections[$i].name")
# 獲取 Collection 詳情
details=$(curl -s -H "api-key: $QDRANT_API_KEY" "$QDRANT_HOST/collections/$name" 2>/dev/null)
vectors_count=$(echo "$details" | jq -r '.result.indexed_vectors_count // 0' 2>/dev/null || echo "0")
points_count=$(echo "$details" | jq -r '.result.points_count // 0' 2>/dev/null || echo "0")
disk_size=$(echo "$details" | jq -r '.result.disk_size_bytes // 0' 2>/dev/null || echo "0")
status=$(echo "$details" | jq -r '.result.status // "unknown"' 2>/dev/null || echo "unknown")
# 格式化大小
if [ "$disk_size" -gt 1073741824 ]; then
size_str="$((disk_size / 1073741824))GB"
elif [ "$disk_size" -gt 1048576 ]; then
size_str="$((disk_size / 1048576))MB"
elif [ "$disk_size" -gt 1024 ]; then
size_str="$((disk_size / 1024))KB"
else
size_str="${disk_size}B"
fi
echo " - $name"
echo " 狀態: $status"
echo " Vectors: $vectors_count"
echo " Points: $points_count"
echo " 大小: $size_str"
# 記錄到資料庫
record_collection "$name" "$vectors_count" "$points_count" "$disk_size" "$status"
record_metric "$name" "vectors_count" "$vectors_count"
record_metric "$name" "points_count" "$points_count"
record_metric "$name" "disk_size" "$disk_size"
done
else
echo "無法獲取 Collection 列表"
log "Failed to get collections: HTTP $http_code"
fi
echo ""
echo "========================================"
log "Qdrant check completed"
-111
View File
@@ -1,111 +0,0 @@
#!/bin/bash
# Momentry Redis 監控 (Layer 5)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/database/redis_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/redis_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
REDIS_PASS="accusys"
# 記錄指標
record_metric() {
local value=$1
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_databases (db_type, db_name, metric_name, metric_value, checked_at)
VALUES ('redis', 'redis', '$1', '$2', NOW());
EOF
}
# 獲取 Redis INFO
get_info() {
redis-cli -a "$REDIS_PASS" INFO 2>/dev/null
}
# 主程序
echo "========================================"
echo "Layer 5: Redis Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
# 檢查 Redis 是否可用
if ! redis-cli -a "$REDIS_PASS" ping > /dev/null 2>&1; then
echo "Redis 不可用"
log "Redis unavailable"
exit 1
fi
info=$(get_info)
# 提取關鍵指標
echo "關鍵指標:"
echo "----------------------------------------"
# 內存使用
used_memory=$(echo "$info" | grep "^used_memory_human:" | cut -d: -f2 | tr -d '\r')
echo " 內存使用: $used_memory"
# 連線數
connected_clients=$(echo "$info" | grep "^connected_clients:" | cut -d: -f2 | tr -d '\r')
echo " 客戶端連線: $connected_clients"
# 命中率
keyspace_hits=$(echo "$info" | grep "^keyspace_hits:" | cut -d: -f2 | tr -d '\r')
keyspace_misses=$(echo "$info" | grep "^keyspace_misses:" | cut -d: -f2 | tr -d '\r')
total_ops=$((keyspace_hits + keyspace_misses))
if [ $total_ops -gt 0 ]; then
hit_rate=$((keyspace_hits * 100 / total_ops))
echo " 命中率: ${hit_rate}%"
else
echo " 命中率: N/A"
fi
# 持久化
rdb_changes=$(echo "$info" | grep "^rdb_changes_since_last_save:" | cut -d: -f2 | tr -d '\r')
echo " RDB 變更: $rdb_changes"
# 總鍵數
echo ""
echo "鍵數據庫:"
db0_info=$(echo "$info" | grep "^db0:" | head -1)
if [ -n "$db0_info" ]; then
keys=$(echo "$db0_info" | sed 's/.*keys=\([0-9]*\).*/\1/')
expires=$(echo "$db0_info" | sed 's/.*expires=\([0-9]*\).*/\1/')
echo " db0: $keys keys, $expires 有過期時間"
fi
# 記錄到資料庫
record_metric "used_memory" "'$used_memory'"
record_metric "connected_clients" "$connected_clients"
record_metric "keyspace_hits" "$keyspace_hits"
record_metric "keyspace_misses" "$keyspace_misses"
# 檢查閾值
echo ""
echo "閾值檢查:"
memory_percent=$(echo "$info" | grep "^used_memory:" | cut -d: -f2)
maxmemory=$(redis-cli -a "$REDIS_PASS" CONFIG GET maxmemory 2>/dev/null | tail -1)
if [ -n "$maxmemory" ] && [ "$maxmemory" -gt 0 ]; then
mem_pct=$((memory_percent * 100 / maxmemory))
echo " 內存使用: ${mem_pct}%"
if [ $mem_pct -gt 80 ]; then
echo " ⚠️ 內存使用超過 80%"
fi
fi
if [ $connected_clients -gt 100 ]; then
echo " ⚠️ 客戶端連線過多"
fi
echo ""
echo "========================================"
log "Redis check completed"
-492
View File
@@ -1,492 +0,0 @@
-- Momentry 監控系統數據庫表
-- 使用方式: psql -U accusys -h localhost -d momentry -f schema.sql
-- ============================================================
-- Layer 2: Service 監控
-- ============================================================
CREATE TABLE IF NOT EXISTS monitor_services (
id SERIAL PRIMARY KEY,
service_name VARCHAR(50) NOT NULL,
service_type VARCHAR(20),
port INTEGER,
status VARCHAR(20) CHECK (status IN ('up', 'down', 'degraded', 'unknown')),
response_time_ms INTEGER,
error_message TEXT,
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_services_name ON monitor_services(service_name);
CREATE INDEX idx_monitor_services_time ON monitor_services(checked_at);
-- ============================================================
-- Layer 3: n8n Workflow 監控
-- ============================================================
CREATE TABLE IF NOT EXISTS monitor_workflows (
id SERIAL PRIMARY KEY,
workflow_id VARCHAR(50) NOT NULL,
workflow_name VARCHAR(255),
workflow_type VARCHAR(50),
is_active BOOLEAN DEFAULT FALSE,
last_executed_at TIMESTAMP,
execution_count INTEGER DEFAULT 0,
success_count INTEGER DEFAULT 0,
failure_count INTEGER DEFAULT 0,
avg_duration_ms INTEGER,
has_schedule BOOLEAN DEFAULT FALSE,
has_webhook BOOLEAN DEFAULT FALSE,
idle_days INTEGER,
suggestion VARCHAR(100),
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_workflows_id ON monitor_workflows(workflow_id);
CREATE INDEX idx_monitor_workflows_active ON monitor_workflows(is_active);
CREATE INDEX idx_monitor_workflows_idle ON monitor_workflows(idle_days);
-- ============================================================
-- Layer 4: WordPress Portal 監控
-- ============================================================
CREATE TABLE IF NOT EXISTS monitor_portal_pages (
id SERIAL PRIMARY KEY,
page_url VARCHAR(500) NOT NULL,
page_type VARCHAR(20),
is_accessible BOOLEAN,
response_time_ms INTEGER,
http_status INTEGER,
error_message TEXT,
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS monitor_portal_users (
id SERIAL PRIMARY KEY,
user_id BIGINT,
username VARCHAR(100),
email VARCHAR(255),
role VARCHAR(50),
is_active BOOLEAN,
last_login TIMESTAMP,
created_at TIMESTAMP,
detected_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_portal_pages_url ON monitor_portal_pages(page_url);
CREATE INDEX idx_monitor_portal_users_username ON monitor_portal_users(username);
-- ============================================================
-- Layer 5: Database 監控
-- ============================================================
CREATE TABLE IF NOT EXISTS monitor_databases (
id SERIAL PRIMARY KEY,
db_type VARCHAR(20) NOT NULL CHECK (db_type IN ('postgresql', 'redis', 'qdrant', 'mariadb', 'mongodb')),
db_name VARCHAR(50),
metric_name VARCHAR(50) NOT NULL,
metric_value JSONB,
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_databases_type ON monitor_databases(db_type);
CREATE INDEX idx_monitor_databases_time ON monitor_databases(checked_at);
-- PostgreSQL 表結構快照
CREATE TABLE IF NOT EXISTS monitor_pg_tables (
id SERIAL PRIMARY KEY,
database_name VARCHAR(50),
schema_name VARCHAR(50),
table_name VARCHAR(100),
table_type VARCHAR(20),
row_count BIGINT,
table_size_bytes BIGINT,
index_size_bytes BIGINT,
snapshot_at TIMESTAMP DEFAULT NOW()
);
-- 表結構變更記錄
CREATE TABLE IF NOT EXISTS monitor_pg_schema_changes (
id SERIAL PRIMARY KEY,
database_name VARCHAR(50),
schema_name VARCHAR(50),
table_name VARCHAR(100),
change_type VARCHAR(20) CHECK (change_type IN ('table_created', 'table_dropped', 'column_added', 'column_removed', 'column_type_changed')),
column_name VARCHAR(100),
old_value TEXT,
new_value TEXT,
detected_at TIMESTAMP DEFAULT NOW()
);
-- Qdrant Collection 監控
CREATE TABLE IF NOT EXISTS monitor_qdrant_collections (
id SERIAL PRIMARY KEY,
collection_name VARCHAR(100),
vectors_count BIGINT,
points_count BIGINT,
disk_size_bytes BIGINT,
status VARCHAR(20),
snapshot_at TIMESTAMP DEFAULT NOW()
);
-- ============================================================
-- Layer 6: 使用者監控
-- ============================================================
-- 連線會話追蹤
CREATE TABLE IF NOT EXISTS monitor_sessions (
id SERIAL PRIMARY KEY,
session_type VARCHAR(20) CHECK (session_type IN ('ssh', 'web', 'db', 'sftp', 'rdp')),
service_name VARCHAR(50),
username VARCHAR(100),
source_ip VARCHAR(45),
source_port INTEGER,
connected_at TIMESTAMP,
last_activity_at TIMESTAMP,
disconnected_at TIMESTAMP,
bytes_sent BIGINT,
bytes_received BIGINT,
status VARCHAR(20) CHECK (status IN ('active', 'disconnected', 'timeout'))
);
-- 登入歷史
CREATE TABLE IF NOT EXISTS monitor_logins (
id SERIAL PRIMARY KEY,
user_type VARCHAR(20) CHECK (user_type IN ('system', 'wordpress', 'n8n', 'gitea', 'sftpgo', 'database')),
username VARCHAR(100),
source_ip VARCHAR(45),
user_agent TEXT,
login_method VARCHAR(20),
success BOOLEAN,
failure_reason VARCHAR(200),
login_at TIMESTAMP DEFAULT NOW()
);
-- sudo 命令記錄
CREATE TABLE IF NOT EXISTS monitor_sudo_history (
id SERIAL PRIMARY KEY,
username VARCHAR(100),
command TEXT,
run_as VARCHAR(100),
tty VARCHAR(50),
source_ip VARCHAR(45),
exit_code INTEGER,
executed_at TIMESTAMP DEFAULT NOW()
);
-- 資源使用追蹤
CREATE TABLE IF NOT EXISTS monitor_resource_usage (
id SERIAL PRIMARY KEY,
user_type VARCHAR(20),
username VARCHAR(100),
service_name VARCHAR(50),
cpu_percent DECIMAL(5,2),
memory_mb INTEGER,
disk_io_read_mb BIGINT,
disk_io_write_mb BIGINT,
network_rx_mb BIGINT,
network_tx_mb BIGINT,
recorded_at TIMESTAMP DEFAULT NOW()
);
-- 異常檢測記錄
CREATE TABLE IF NOT EXISTS monitor_anomalies (
id SERIAL PRIMARY KEY,
anomaly_type VARCHAR(50) CHECK (anomaly_type IN ('brute_force', 'privilege_escalation', 'unusual_access', 'unusual_time', 'excessive_queries', 'idle_session', 'schema_change')),
severity VARCHAR(20) CHECK (severity IN ('low', 'medium', 'high', 'critical')),
source_type VARCHAR(20),
username VARCHAR(100),
source_ip VARCHAR(45),
description TEXT,
details JSONB,
detected_at TIMESTAMP DEFAULT NOW(),
resolved BOOLEAN DEFAULT FALSE,
resolved_at TIMESTAMP
);
CREATE INDEX idx_monitor_sessions_type ON monitor_sessions(session_type);
CREATE INDEX idx_monitor_sessions_username ON monitor_sessions(username);
CREATE INDEX idx_monitor_logins_type ON monitor_logins(user_type);
CREATE INDEX idx_monitor_logins_time ON monitor_logins(login_at);
CREATE INDEX idx_monitor_anomalies_type ON monitor_anomalies(anomaly_type);
CREATE INDEX idx_monitor_anomalies_severity ON monitor_anomalies(severity);
CREATE INDEX idx_monitor_anomalies_time ON monitor_anomalies(detected_at);
-- ============================================================
-- Layer 7: Storage 監控
-- ============================================================
-- 檔案註冊表
CREATE TABLE IF NOT EXISTS file_registry (
file_uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_name VARCHAR(255) NOT NULL,
file_path TEXT NOT NULL,
file_path_hash VARCHAR(64) NOT NULL,
file_size BIGINT NOT NULL,
file_hash VARCHAR(64),
mime_type VARCHAR(100),
user_cluster VARCHAR(50) CHECK (user_cluster IN ('family', 'work', 'wordpress', 'shared', 'system')),
owner_id VARCHAR(100),
storage_tier VARCHAR(20) DEFAULT 'hot' CHECK (storage_tier IN ('hot', 'warm', 'cold')),
storage_location VARCHAR(500),
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'temporary', 'archived', 'deleted')),
is_registered BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
last_accessed_at TIMESTAMP,
access_count INTEGER DEFAULT 0,
archived_at TIMESTAMP,
archive_location VARCHAR(500),
retention_until TIMESTAMP,
UNIQUE(file_path_hash)
);
-- 存儲使用統計
CREATE TABLE IF NOT EXISTS storage_usage_stats (
id SERIAL PRIMARY KEY,
user_cluster VARCHAR(50),
storage_tier VARCHAR(20),
file_count BIGINT,
total_size_bytes BIGINT,
record_time TIMESTAMP DEFAULT NOW()
);
-- 文件訪問日誌
CREATE TABLE IF NOT EXISTS storage_access_logs (
id SERIAL PRIMARY KEY,
user_cluster VARCHAR(50),
owner_id VARCHAR(100),
file_path TEXT,
access_type VARCHAR(20) CHECK (access_type IN ('read', 'write', 'delete', 'download', 'move')),
access_time TIMESTAMP DEFAULT NOW(),
client_ip VARCHAR(45),
access_method VARCHAR(20)
);
-- 文件生命週期
CREATE TABLE IF NOT EXISTS file_lifecycle (
id SERIAL PRIMARY KEY,
file_uuid UUID REFERENCES file_registry(file_uuid),
file_path TEXT,
user_cluster VARCHAR(50),
storage_tier VARCHAR(20),
created_at TIMESTAMP,
last_accessed_at TIMESTAMP,
last_modified_at TIMESTAMP,
access_count INTEGER DEFAULT 0,
current_status VARCHAR(20) DEFAULT 'active',
tier_migration_count INTEGER DEFAULT 0,
migrated_at TIMESTAMP
);
CREATE INDEX idx_file_registry_cluster ON file_registry(user_cluster);
CREATE INDEX idx_file_registry_tier ON file_registry(storage_tier);
CREATE INDEX idx_file_registry_status ON file_registry(status);
CREATE INDEX idx_storage_usage_cluster ON storage_usage_stats(user_cluster);
CREATE INDEX idx_storage_usage_time ON storage_usage_stats(record_time);
-- ============================================================
-- 外部監控 (Layer 1)
-- ============================================================
CREATE TABLE IF NOT EXISTS monitor_external (
id SERIAL PRIMARY KEY,
target_name VARCHAR(50) NOT NULL,
target_type VARCHAR(20) CHECK (target_type IN ('ddns', 'gateway', 'internet', 'api')),
target_host VARCHAR(255),
is_reachable BOOLEAN,
response_time_ms INTEGER,
dns_resolved_ip VARCHAR(45),
error_message TEXT,
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_monitor_external_name ON monitor_external(target_name);
CREATE INDEX idx_monitor_external_time ON monitor_external(checked_at);
-- ============================================================
-- 監控配置表
-- ============================================================
CREATE TABLE IF NOT EXISTS monitor_config (
id SERIAL PRIMARY KEY,
config_key VARCHAR(50) UNIQUE NOT NULL,
config_value TEXT,
description VARCHAR(255),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 插入默認配置
INSERT INTO monitor_config (config_key, config_value, description) VALUES
('check_interval', '300', '監控檢查間隔(秒)'),
('retention_days', '30', '歷史數據保留天數'),
('idle_threshold_days', '30', 'Workflow 閒置天數閾值'),
('alert_threshold_bruteforce', '5', '暴力破解嘗試次數閾值'),
('alert_threshold_slow_response', '3000', '響應時間閾值(毫秒)')
ON CONFLICT (config_key) DO NOTHING;
-- ============================================================
-- 視圖定義
-- ============================================================
-- 服務健康狀態視圖
CREATE OR REPLACE VIEW v_service_health AS
SELECT
service_name,
status,
COUNT(*) as check_count,
COUNT(*) FILTER (WHERE status = 'up') as up_count,
COUNT(*) FILTER (WHERE status = 'down') as down_count,
AVG(response_time_ms) as avg_response_time,
MAX(checked_at) as last_check
FROM monitor_services
WHERE checked_at > NOW() - INTERVAL '24 hours'
GROUP BY service_name, status;
-- 最近異常視圖
CREATE OR REPLACE VIEW v_recent_anomalies AS
SELECT
anomaly_type,
severity,
username,
source_ip,
description,
detected_at
FROM monitor_anomalies
WHERE detected_at > NOW() - INTERVAL '24 hours'
ORDER BY detected_at DESC;
-- 閒置 Workflow 視圖
CREATE OR REPLACE VIEW v_idle_workflows AS
SELECT
workflow_name,
idle_days,
suggestion,
last_executed_at
FROM monitor_workflows
WHERE idle_days > 30 AND is_active = TRUE
ORDER BY idle_days DESC;
-- 存儲使用概況視圖
CREATE OR REPLACE VIEW v_storage_overview AS
SELECT
user_cluster,
storage_tier,
COUNT(*) as file_count,
SUM(file_size) as total_size
FROM file_registry
WHERE status = 'active'
GROUP BY user_cluster, storage_tier;
-- ============================================================
-- 備份監控 (Layer 7 Extension)
-- ============================================================
-- 備份註冊表
CREATE TABLE IF NOT EXISTS backup_registry (
id SERIAL PRIMARY KEY,
service_name VARCHAR(50) NOT NULL,
backup_file VARCHAR(500) NOT NULL,
backup_size_bytes BIGINT,
backup_type VARCHAR(20) CHECK (backup_type IN ('daily', 'weekly', 'monthly', 'archive', 'full', 'incremental')),
backup_method VARCHAR(20) CHECK (backup_method IN ('pg_dump', 'mysqldump', 'tar', 'snapshot', 'dump')),
status VARCHAR(20) CHECK (status IN ('pending', 'running', 'completed', 'failed', 'verified')),
compression_ratio DECIMAL(5,2),
verification_result BOOLEAN,
error_message TEXT,
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
-- 備份存儲統計
CREATE TABLE IF NOT EXISTS backup_storage_stats (
id SERIAL PRIMARY KEY,
tier VARCHAR(20) CHECK (tier IN ('daily', 'weekly', 'monthly', 'archive', 'total')),
file_count BIGINT,
total_size_bytes BIGINT,
record_time TIMESTAMP DEFAULT NOW()
);
-- 備份歷史
CREATE TABLE IF NOT EXISTS backup_history (
id SERIAL PRIMARY KEY,
service_name VARCHAR(50) NOT NULL,
operation VARCHAR(20) CHECK (operation IN ('backup', 'restore', 'tier_migration', 'cleanup', 'verify')),
backup_file VARCHAR(500),
backup_tier VARCHAR(20),
source_tier VARCHAR(20),
dest_tier VARCHAR(20),
file_count BIGINT,
size_bytes BIGINT,
duration_seconds INTEGER,
status VARCHAR(20) CHECK (status IN ('success', 'failed', 'partial')),
error_message TEXT,
executed_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_backup_registry_service ON backup_registry(service_name);
CREATE INDEX idx_backup_registry_time ON backup_registry(created_at);
CREATE INDEX idx_backup_storage_stats_tier ON backup_storage_stats(tier);
CREATE INDEX idx_backup_storage_stats_time ON backup_storage_stats(record_time);
CREATE INDEX idx_backup_history_service ON backup_history(service_name);
CREATE INDEX idx_backup_history_time ON backup_history(executed_at);
-- ============================================================
-- Node.js 版本基線監控
-- ============================================================
CREATE TABLE IF NOT EXISTS node_version_baseline (
id SERIAL PRIMARY KEY,
runtime_name VARCHAR(50) NOT NULL,
required_version VARCHAR(20) NOT NULL,
current_version VARCHAR(20),
process_name VARCHAR(100),
process_path TEXT,
is_compliant BOOLEAN,
locked_path VARCHAR(500),
checked_at TIMESTAMP DEFAULT NOW()
);
-- Node.js 進程追蹤
CREATE TABLE IF NOT EXISTS node_process_tracking (
id SERIAL PRIMARY KEY,
process_name VARCHAR(100) NOT NULL,
pid INTEGER,
command VARCHAR(500),
node_version VARCHAR(20),
is_managed BOOLEAN DEFAULT FALSE,
started_at TIMESTAMP,
checked_at TIMESTAMP DEFAULT NOW()
);
-- ============================================================
-- Python 版本基線監控
-- ============================================================
CREATE TABLE IF NOT EXISTS python_version_baseline (
id SERIAL PRIMARY KEY,
runtime_name VARCHAR(50) NOT NULL,
required_version VARCHAR(20) NOT NULL,
current_version VARCHAR(20),
interpreter_path VARCHAR(500),
is_compliant BOOLEAN,
checked_at TIMESTAMP DEFAULT NOW()
);
-- Python 腳本追蹤
CREATE TABLE IF NOT EXISTS python_script_tracking (
id SERIAL PRIMARY KEY,
script_path TEXT NOT NULL,
shebang_version VARCHAR(20),
actual_version VARCHAR(20),
is_compliant BOOLEAN DEFAULT FALSE,
last_run_at TIMESTAMP,
exit_code INTEGER,
error_output TEXT,
checked_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_node_version_name ON node_version_baseline(runtime_name);
CREATE INDEX idx_node_process_name ON node_process_tracking(process_name);
CREATE INDEX idx_python_version_name ON python_version_baseline(runtime_name);
CREATE INDEX idx_python_script_path ON python_script_tracking(script_path);
-175
View File
@@ -1,175 +0,0 @@
#!/bin/bash
# Momentry WordPress Portal 監控 (Layer 4)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/portal/page_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/portal_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# WordPress 配置
WP_SITE="https://wp.momentry.ddns.net"
WP_DB_HOST="localhost"
WP_DB_NAME="wordpress"
WP_DB_USER="wp_user"
WP_DB_PASS="wp_password_123"
# 顏色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 記錄頁面檢查結果
record_page() {
local url=$1
local page_type=$2
local accessible=$3
local response_time=$4
local http_status=$5
local error=$6
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_portal_pages (page_url, page_type, is_accessible, response_time_ms, http_status, error_message, checked_at)
VALUES ('$url', '$page_type', $accessible, $response_time, $http_status, '$error', NOW());
EOF
}
# 記錄用戶
record_user() {
local user_id=$1
local username=$2
local email=$3
local role=$4
local is_active=$5
local last_login=$6
local created_at=$7
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_portal_users (user_id, username, email, role, is_active, last_login, created_at, detected_at)
VALUES ($user_id, '$username', '$email', '$role', $is_active, $last_login, $created_at, NOW())
ON CONFLICT DO NOTHING;
EOF
}
# 記錄異常
record_anomaly() {
local anomaly_type=$1
local severity=$2
local username=$3
local description=$4
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_anomalies (anomaly_type, severity, source_type, username, description, detected_at)
VALUES ('$anomaly_type', '$severity', 'wordpress', '$username', '$description', NOW());
EOF
}
# 檢查頁面
check_page() {
local url=$1
local page_type=$2
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" --max-time 10 -k -L 2>/dev/null || echo "000")
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ]; then
accessible="true"
error=""
echo -e "${GREEN}${NC} $page_type - ${ms}ms (HTTP $http_code)"
else
accessible="false"
error="HTTP $http_code"
echo -e "${RED}${NC} $page_type - HTTP $http_code"
fi
record_page "$url" "$page_type" "$accessible" "$ms" "$http_code" "$error"
}
# 檢查用戶
check_users() {
echo ""
echo "WordPress 用戶檢查:"
echo "----------------------------------------"
# 獲取用戶列表
users=$(mysql -u"$WP_DB_USER" -p"$WP_DB_PASS" -h "$WP_DB_HOST" "$WP_DB_NAME" -N -e "
SELECT u.ID, u.user_login, u.user_email, u.user_registered, u.user_status,
COALESCE(m.meta_value, 'subscriber') as role
FROM wp_users u
LEFT JOIN wp_usermeta m ON u.ID = m.user_id AND m.meta_key = 'wp_capabilities'
ORDER BY u.ID;
" 2>/dev/null)
if [ -z "$users" ]; then
echo "無法連接 WordPress 資料庫"
return 1
fi
local admin_count=0
local total_users=0
while IFS='|' read -r id login email registered status role; do
[ -z "$id" ] && continue
total_users=$((total_users + 1))
# 判斷是否管理員
if echo "$role" | grep -q "administrator"; then
admin_count=$((admin_count + 1))
role="administrator"
elif echo "$role" | grep -q "editor"; then
role="editor"
elif echo "$role" | grep -q "author"; then
role="author"
elif echo "$role" | grep -q "contributor"; then
role="contributor"
else
role="subscriber"
fi
# 記錄用戶
record_user "$id" "$login" "$email" "$role" "true" "NULL" "'$registered'"
echo " - $login ($role)"
done <<< "$users"
echo "----------------------------------------"
echo "總用戶: $total_users | 管理員: $admin_count"
}
# 主程序
echo "========================================"
echo "Layer 4: WordPress Portal Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
echo "頁面可訪問性檢查:"
echo "----------------------------------------"
# 檢查首頁
check_page "$WP_SITE/" "homepage"
# 檢查登入頁
check_page "$WP_SITE/wp-login.php" "login_page"
# 檢查 wp-json API
check_page "$WP_SITE/wp-json/" "api"
echo ""
check_users
echo ""
echo "========================================"
log "Portal check completed"
-93
View File
@@ -1,93 +0,0 @@
#!/bin/bash
# Momentry 外部監控 (Layer 1)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/service/external_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/external_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 記錄結果
record_external() {
local target=$1
local target_type=$2
local reachable=$3
local response_time=$4
local error=$5
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_external (target_name, target_type, is_reachable, response_time_ms, error_message, checked_at)
VALUES ('$target', '$target_type', $reachable, $response_time, '$error', NOW());
EOF
}
# 檢查 DDNS
check_ddns() {
local start=$(date +%s%N)
local ip=$(dig +short momentry.ddns.net 2>/dev/null | tail -1)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ -n "$ip" ]; then
echo "✓ DDNS (momentry.ddns.net) -> $ip (${ms}ms)"
record_external "ddns" "ddns" "true" "$ms" ""
return 0
else
echo "✗ DDNS (momentry.ddns.net) - DNS resolution failed"
record_external "ddns" "ddns" "false" "0" "DNS resolution failed"
return 1
fi
}
# 檢查網關
check_gateway() {
local start=$(date +%s%N)
if ping -c 1 -W 2 192.168.110.1 > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo "✓ Gateway (192.168.110.1) - ${ms}ms"
record_external "gateway" "gateway" "true" "$ms" ""
return 0
else
echo "✗ Gateway (192.168.110.1) - Unreachable"
record_external "gateway" "gateway" "false" "0" "Unreachable"
return 1
fi
}
# 檢查互聯網
check_internet() {
local start=$(date +%s%N)
if ping -c 1 -W 2 8.8.8.8 > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo "✓ Internet (8.8.8.8) - ${ms}ms"
record_external "internet" "internet" "true" "$ms" ""
return 0
else
echo "✗ Internet (8.8.8.8) - Unreachable"
record_external "internet" "internet" "false" "0" "Unreachable"
return 1
fi
}
# 主程序
echo "========================================"
echo "Layer 1: External Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
check_ddns
check_gateway
check_internet
echo ""
echo "========================================"
log "External check completed"
-370
View File
@@ -1,370 +0,0 @@
#!/bin/bash
# Momentry 服務健康檢查 (Layer 2)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/service/health_check.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/service_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 顏色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 記錄結果到資料庫
record_service() {
local service=$1
local status=$2
local response_time=$3
local error_msg=$4
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_services (service_name, service_type, status, response_time_ms, error_message, checked_at)
VALUES ('$service', 'service', '$status', $response_time, '$error_msg', NOW());
EOF
}
# 檢查 PostgreSQL
check_postgresql() {
local start=$(date +%s%N)
if pg_isready -h localhost -p 5432 -U accusys > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} PostgreSQL (5432) - ${ms}ms"
record_service "postgresql" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} PostgreSQL (5432) - Down"
record_service "postgresql" "down" "0" "Connection failed"
return 1
fi
}
# 檢查 Redis
check_redis() {
local start=$(date +%s%N)
if redis-cli -a accusys ping 2>/dev/null | grep -q "PONG"; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} Redis (6379) - ${ms}ms"
record_service "redis" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Redis (6379) - Down"
record_service "redis" "down" "0" "Connection failed"
return 1
fi
}
# 檢查 MariaDB
check_mariadb() {
local start=$(date +%s%N)
if mysql -u accusys -e "SELECT 1" > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} MariaDB (3306) - ${ms}ms"
record_service "mariadb" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} MariaDB (3306) - Down"
record_service "mariadb" "down" "0" "Connection failed"
return 1
fi
}
# 檢查 n8n
check_n8n() {
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8085/ --max-time 5)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ] || [ "$http_code" = "302" ]; then
echo -e "${GREEN}${NC} n8n (8085) - ${ms}ms"
record_service "n8n" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} n8n (8085) - HTTP $http_code"
record_service "n8n" "down" "0" "HTTP $http_code"
return 1
fi
}
# 檢查 Caddy
check_caddy() {
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:2019/config/ --max-time 5)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ]; then
echo -e "${GREEN}${NC} Caddy (2019) - ${ms}ms"
record_service "caddy" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Caddy (2019) - HTTP $http_code"
record_service "caddy" "down" "0" "HTTP $http_code"
return 1
fi
}
# 檢查 Gitea
check_gitea() {
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/ --max-time 5)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ]; then
echo -e "${GREEN}${NC} Gitea (3000) - ${ms}ms"
record_service "gitea" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Gitea (3000) - HTTP $http_code"
record_service "gitea" "down" "0" "HTTP $http_code"
return 1
fi
}
# 檢查 SFTPGo
check_sftpgo() {
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080 --max-time 5)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ] || [ "$http_code" = "301" ] || [ "$http_code" = "302" ]; then
echo -e "${GREEN}${NC} SFTPGo (8080) - ${ms}ms"
record_service "sftpgo" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} SFTPGo (8080) - HTTP $http_code"
record_service "sftpgo" "down" "0" "HTTP $http_code"
return 1
fi
}
# 檢查 Ollama
check_ollama() {
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:11434/api/tags --max-time 5)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ]; then
echo -e "${GREEN}${NC} Ollama (11434) - ${ms}ms"
record_service "ollama" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Ollama (11434) - HTTP $http_code"
record_service "ollama" "down" "0" "HTTP $http_code"
return 1
fi
}
# 檢查 Qdrant
check_qdrant() {
local start=$(date +%s%N)
local http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:6333/collections --max-time 5)
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
if [ "$http_code" = "200" ] || [ "$http_code" = "401" ]; then
echo -e "${GREEN}${NC} Qdrant (6333) - ${ms}ms"
record_service "qdrant" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} Qdrant (6333) - HTTP $http_code"
record_service "qdrant" "down" "0" "HTTP $http_code"
return 1
fi
}
# 檢查 MongoDB
check_mongodb() {
local start=$(date +%s%N)
if mongosh --quiet --eval "db.adminCommand('ping')" > /dev/null 2>&1; then
local end=$(date +%s%N)
local ms=$(( (end - start) / 1000000 ))
echo -e "${GREEN}${NC} MongoDB (27017) - ${ms}ms"
record_service "mongodb" "up" "$ms" ""
return 0
else
echo -e "${RED}${NC} MongoDB (27017) - Down"
record_service "mongodb" "down" "0" "Connection failed"
return 1
fi
}
# 檢查 PHP-FPM
check_php() {
if pgrep -f "php-fpm" > /dev/null 2>&1; then
echo -e "${GREEN}${NC} PHP-FPM - Running"
record_service "php" "up" "1" ""
return 0
else
echo -e "${RED}${NC} PHP-FPM - Not running"
record_service "php" "down" "0" "Process not found"
return 1
fi
}
# 檢查 RustDesk
check_rustdesk() {
local hbbs_ok=false
local hbbr_ok=false
if nc -z localhost 21116 > /dev/null 2>&1; then
hbbs_ok=true
fi
if nc -z localhost 21117 > /dev/null 2>&1; then
hbbr_ok=true
fi
if $hbbs_ok && $hbbr_ok; then
echo -e "${GREEN}${NC} RustDesk (21116/21117) - Running"
record_service "rustdesk" "up" "1" ""
return 0
else
echo -e "${YELLOW}${NC} RustDesk - Partial (hbbs: $hbbs_ok, hbbr: $hbbr_ok)"
record_service "rustdesk" "degraded" "0" "hbbs:$hbbs_ok hbbr:$hbbr_ok"
return 1
fi
}
# 檢查 Node.js 版本
check_node() {
local LOCKED_NODE_VERSION="22"
local version_issues=0
local node_pids=$(pgrep -f "n8n" 2>/dev/null)
if [ -z "$node_pids" ]; then
echo -e "${YELLOW}${NC} Node.js - n8n not running"
record_service "node" "degraded" "1" "n8n not running"
return 1
fi
for pid in $node_pids; do
local node_path=$(lsof -p $pid 2>/dev/null | grep "txt" | grep "node" | head -1 | awk '{print $NF}' | grep -v "dylib")
if [ -n "$node_path" ] && [ -f "$node_path" ]; then
local node_version=$($node_path --version 2>/dev/null | sed 's/v//')
local node_major=$(echo "$node_version" | cut -d. -f1)
if [ "$node_major" != "$LOCKED_NODE_VERSION" ]; then
version_issues=$((version_issues + 1))
fi
fi
done
if [ $version_issues -gt 0 ]; then
echo -e "${RED}${NC} Node.js - Version issues detected"
record_service "node" "degraded" "1" "$version_issues version issues"
return 1
else
echo -e "${GREEN}${NC} Node.js (${LOCKED_NODE_VERSION}.x) - Running"
record_service "node" "up" "1" ""
return 0
fi
}
# 檢查 Python 版本
check_python() {
local LOCKED_PYTHON_VERSION="3.11.14"
local script_issues=0
local scripts=(
"/Users/accusys/momentry_core_0.1/scripts/asr_processor.py"
"/Users/accusys/momentry_core_0.1/scripts/thumbnail_extractor.py"
)
for script in "${scripts[@]}"; do
if [ -f "$script" ]; then
local shebang=$(head -1 "$script")
if [[ "$shebang" != *"python3.11"* ]]; then
script_issues=$((script_issues + 1))
fi
fi
done
if [ $script_issues -gt 0 ]; then
echo -e "${RED}${NC} Python - Script version issues"
record_service "python" "degraded" "1" "$script_issues script issues"
return 1
else
echo -e "${GREEN}${NC} Python (${LOCKED_PYTHON_VERSION}) - Configured"
record_service "python" "up" "1" ""
return 0
fi
}
# 主程序
echo "========================================"
echo "Layer 2: Service Health Check"
echo "Time: $(date)"
echo "========================================"
echo ""
total=0
passed=0
total=$((total + 1))
check_postgresql && passed=$((passed + 1))
total=$((total + 1))
check_redis && passed=$((passed + 1))
total=$((total + 1))
check_mariadb && passed=$((passed + 1))
total=$((total + 1))
check_n8n && passed=$((passed + 1))
total=$((total + 1))
check_caddy && passed=$((passed + 1))
total=$((total + 1))
check_gitea && passed=$((passed + 1))
total=$((total + 1))
check_sftpgo && passed=$((passed + 1))
total=$((total + 1))
check_ollama && passed=$((passed + 1))
total=$((total + 1))
check_qdrant && passed=$((passed + 1))
total=$((total + 1))
check_mongodb && passed=$((passed + 1))
total=$((total + 1))
check_php && passed=$((passed + 1))
total=$((total + 1))
check_rustdesk && passed=$((passed + 1))
total=$((total + 1))
check_node && passed=$((passed + 1))
total=$((total + 1))
check_python && passed=$((passed + 1))
echo ""
echo "========================================"
echo "Result: $passed / $total services healthy"
echo "========================================"
log "Service check completed: $passed/$total healthy"
-270
View File
@@ -1,270 +0,0 @@
#!/bin/bash
#===============================================================================
# Momentry Node.js 監控腳本
# 路徑: /Users/accusys/momentry_core_0.1/monitor/service/node_monitor.sh
#
# 監控重點:
# - n8n 使用的 Node.js 版本鎖定 (22.x)
# - 進程數量與狀態
# - 資源使用情況
#
# 使用方式:
# ./node_monitor.sh status # 顯示監控狀態
# ./node_monitor.sh baseline # 建立版本基線
# ./node_monitor.sh check # 檢查版本變化
#===============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/node_check.log"
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 鎖定的 Node.js 版本
LOCKED_NODE_VERSION="22"
LOCKED_NODE_MINOR="22"
#===============================================================================
# 記錄函數
#===============================================================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] ✅ $1${NC}" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ❌ $1${NC}" | tee -a "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️ $1${NC}" | tee -a "$LOG_FILE"
}
#===============================================================================
# 記錄到資料庫
#===============================================================================
record_node_baseline() {
local runtime_name=$1
local current_version=$2
local process_path=$3
local pid=$4
local required_version="${LOCKED_NODE_VERSION}.x"
local is_compliant="false"
if [[ "$current_version" == "${LOCKED_NODE_VERSION}".* ]]; then
is_compliant="true"
fi
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO node_version_baseline (runtime_name, required_version, current_version, process_name, process_path, is_compliant, locked_path, checked_at)
VALUES ('$runtime_name', '$required_version', '$current_version', 'node', '$process_path', $is_compliant, '$process_path', NOW())
ON CONFLICT DO NOTHING;
EOF
}
record_node_history() {
local process_name=$1
local old_version=$2
local new_version=$3
local old_path=$4
local new_path=$5
# node_version_history table does not exist - skip recording
true
}
record_monitor_service() {
local service=$1
local status=$2
local version=$3
local error_msg=$4
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_services (service_name, service_type, status, response_time_ms, error_message, checked_at)
VALUES ('$service', 'node', '$status', 0, '$version - $error_msg', NOW());
EOF
}
#===============================================================================
# 發現 Node.js 進程
#===============================================================================
discover_node_processes() {
log "發現 Node.js 進程..."
echo ""
echo "========================================"
echo "Node.js 監控狀態"
echo "時間: $(date)"
echo "========================================"
echo ""
# 獲取所有 node 進程
local node_pids=$(pgrep -f "node" 2>/dev/null)
if [ -z "$node_pids" ]; then
echo -e "${RED}沒有運行中的 Node.js 進程${NC}"
record_monitor_service "node" "down" "N/A" "No processes"
return 1
fi
echo "鎖定版本: Node.js ${LOCKED_NODE_VERSION}.x (n8n 專用)"
echo ""
echo "----------------------------------------"
echo "發現的 Node.js 進程:"
echo "----------------------------------------"
local total_processes=0
local n8n_processes=0
local version_issues=0
for pid in $node_pids; do
# 獲取進程命令
local cmd=$(ps -o args= -p $pid 2>/dev/null | head -1)
# 獲取 Node.js 版本
local node_path=$(lsof -p $pid 2>/dev/null | grep "txt" | grep "node" | head -1 | awk '{print $NF}' | grep -v "dylib")
if [ -n "$node_path" ] && [ -f "$node_path" ]; then
local node_version=$($node_path --version 2>/dev/null | sed 's/v//')
local node_major=$(echo "$node_version" | cut -d. -f1)
local node_minor=$(echo "$node_version" | cut -d. -f2)
else
local node_version="unknown"
local node_major="unknown"
fi
# 內存使用
local mem=$(ps -o rss= -p $pid 2>/dev/null | awk '{print int($1/1024)}')
# CPU 使用
local cpu=$(ps -o %cpu= -p $pid 2>/dev/null | awk '{print int($1)}')
# 運行時間
local time=$(ps -o etime= -p $pid 2>/dev/null | tr -d ' ')
# 識別服務類型
local service_type="other"
if echo "$cmd" | grep -q "n8n"; then
service_type="n8n"
n8n_processes=$((n8n_processes + 1))
elif echo "$cmd" | grep -q "worker"; then
service_type="n8n-worker"
n8n_processes=$((n8n_processes + 1))
fi
# 版本檢查
local version_status="✅"
if [ "$service_type" = "n8n" ] || [ "$service_type" = "n8n-worker" ]; then
if [ "$node_major" != "$LOCKED_NODE_VERSION" ]; then
version_status="❌ 版本錯誤!"
version_issues=$((version_issues + 1))
log_error "n8n 使用 Node.js $node_version (應為 ${LOCKED_NODE_VERSION}.x)"
fi
fi
echo " PID: $pid"
echo " 命令: ${cmd:0:60}..."
echo " Node.js: $node_version $version_status"
echo " 路徑: $node_path"
echo " 內存: ${mem}MB | CPU: ${cpu}% | 運行: $time"
echo " 類型: $service_type"
echo ""
total_processes=$((total_processes + 1))
# 記錄基線
record_node_baseline "$service_type" "$node_version" "$node_path" "$pid"
done
echo "----------------------------------------"
echo "總結:"
echo " 總進程數: $total_processes"
echo " n8n 相關: $n8n_processes"
echo " 版本問題: $version_issues"
echo "========================================"
# 記錄到資料庫
if [ $version_issues -gt 0 ]; then
record_monitor_service "node" "degraded" "${LOCKED_NODE_VERSION}.x" "$version_issues version issues"
return 1
else
record_monitor_service "node" "up" "${LOCKED_NODE_VERSION}.x" "OK"
return 0
fi
}
#===============================================================================
# 版本基線檢查
#===============================================================================
check_baseline() {
log "檢查 Node.js 版本基線..."
# 檢查 n8n 進程
local n8n_pid=$(pgrep -f "n8n start" | head -1)
if [ -z "$n8n_pid" ]; then
log_error "n8n 進程未運行"
return 1
fi
# 獲取 n8n 使用的 Node.js 版本
local node_path=$(lsof -p $n8n_pid 2>/dev/null | grep "txt" | grep "node" | head -1 | awk '{print $NF}' | grep -v "dylib")
if [ -n "$node_path" ] && [ -f "$node_path" ]; then
local node_version=$($node_path --version 2>/dev/null | sed 's/v//')
local node_major=$(echo "$node_version" | cut -d. -f1)
echo "n8n 當前 Node.js 版本: $node_version"
if [ "$node_major" = "$LOCKED_NODE_VERSION" ]; then
log_success "版本正確: Node.js $node_version"
return 0
else
log_error "版本錯誤: Node.js $node_version (應為 ${LOCKED_NODE_VERSION}.x)"
return 1
fi
else
log_error "無法確定 Node.js 版本"
return 1
fi
}
#===============================================================================
# 顯示狀態
#===============================================================================
show_status() {
discover_node_processes
}
#===============================================================================
# 主程序
#===============================================================================
command=${1:-status}
case $command in
status|check)
show_status
;;
baseline)
check_baseline
;;
*)
echo "用法: $0 {status|baseline}"
echo ""
echo " status - 顯示 Node.js 監控狀態"
echo " baseline - 檢查版本基線"
exit 1
;;
esac
-281
View File
@@ -1,281 +0,0 @@
#!/bin/bash
#===============================================================================
# Momentry Python 監控腳本
# 路徑: /Users/accusys/momentry_core_0.1/monitor/service/python_monitor.sh
#
# 監控重點:
# - Momentry Python 腳本版本鎖定 (3.11.14)
# - 進程數量與狀態
# - 腳本執行狀態
#
# 使用方式:
# ./python_monitor.sh status # 顯示監控狀態
# ./python_monitor.sh baseline # 建立版本基線
# ./python_monitor.sh check # 檢查版本變化
#===============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/python_check.log"
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 鎖定的 Python 版本
LOCKED_PYTHON_VERSION="3.11.14"
LOCKED_PYTHON_MAJOR="3"
LOCKED_PYTHON_MINOR="11"
# Momentry Python 腳本
MOMENTRY_SCRIPTS=(
"/Users/accusys/momentry_core_0.1/scripts/asr_processor.py"
"/Users/accusys/momentry_core_0.1/scripts/thumbnail_extractor.py"
)
#===============================================================================
# 記錄函數
#===============================================================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] ✅ $1${NC}" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ❌ $1${NC}" | tee -a "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️ $1${NC}" | tee -a "$LOG_FILE"
}
#===============================================================================
# 記錄到資料庫
#===============================================================================
record_python_baseline() {
local runtime_name=$1
local current_version=$2
local interpreter_path=$3
local required_version="${LOCKED_PYTHON_VERSION}"
local is_compliant="false"
if [[ "$current_version" == "${LOCKED_PYTHON_VERSION}" ]]; then
is_compliant="true"
fi
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO python_version_baseline (runtime_name, required_version, current_version, interpreter_path, is_compliant, checked_at)
VALUES ('$runtime_name', '$required_version', '$current_version', '$interpreter_path', $is_compliant, NOW())
ON CONFLICT DO NOTHING;
EOF
}
record_python_history() {
local script_name=$1
local old_version=$2
local new_version=$3
local old_path=$4
local new_path=$5
# python_version_history table does not exist - skip recording
true
}
record_monitor_service() {
local service=$1
local status=$2
local version=$3
local error_msg=$4
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_services (service_name, service_type, status, response_time_ms, error_message, checked_at)
VALUES ('$service', 'python', '$status', 0, '$version - $error_msg', NOW());
EOF
}
#===============================================================================
# 發現 Python 進程
#===============================================================================
discover_python_processes() {
log "發現 Python 進程..."
echo ""
echo "========================================"
echo "Python 監控狀態"
echo "時間: $(date)"
echo "========================================"
echo ""
echo "鎖定版本: Python ${LOCKED_PYTHON_VERSION} (Momentry 專用)"
echo ""
# 檢查 Momentry 腳本
echo "----------------------------------------"
echo "Momentry Python 腳本:"
echo "----------------------------------------"
local script_issues=0
for script in "${MOMENTRY_SCRIPTS[@]}"; do
if [ -f "$script" ]; then
# 獲取腳本使用的 Python
local shebang=$(head -1 "$script")
local python_path=""
if [[ "$shebang" == *"/python3.11"* ]]; then
python_path="/opt/homebrew/bin/python3.11"
elif [[ "$shebang" == *"/python3"* ]]; then
# 檢查系統 python3
python_path=$(which python3 2>/dev/null)
fi
if [ -n "$python_path" ] && [ -f "$python_path" ]; then
local python_version=$($python_path --version 2>&1 | sed 's/Python //')
local python_major=$(echo "$python_version" | cut -d. -f1)
local python_minor=$(echo "$python_version" | cut -d. -f2)
# 檢查版本
local version_status="✅"
if [ "$python_major" = "$LOCKED_PYTHON_MAJOR" ] && [ "$python_minor" = "$LOCKED_PYTHON_MINOR" ]; then
log_success "$(basename $script): $python_version"
else
version_status="❌ 版本錯誤!"
script_issues=$((script_issues + 1))
log_error "$(basename $script): $python_version (應為 ${LOCKED_PYTHON_VERSION})"
fi
echo " $(basename $script)"
echo " 路徑: $python_path"
echo " 版本: $python_version $version_status"
echo " shebang: $shebang"
echo ""
# 記錄基線
record_python_baseline "python_${LOCKED_PYTHON_VERSION}" "$python_version" "$python_path"
else
log_error "$(basename $script): 無法確定 Python 路徑"
script_issues=$((script_issues + 1))
fi
else
log_warn "$(basename $script): 文件不存在"
script_issues=$((script_issues + 1))
fi
done
# 檢查運行中的 Python 進程
echo "----------------------------------------"
echo "運行中的 Python 進程:"
echo "----------------------------------------"
local python_pids=$(pgrep -f "python" 2>/dev/null)
local total_processes=0
if [ -n "$python_pids" ]; then
for pid in $python_pids; do
# 獲取進程命令
local cmd=$(ps -o args= -p $pid 2>/dev/null | head -1 | cut -c1-80)
# 獲取 Python 路徑
local python_path=$(lsof -p $pid 2>/dev/null | grep "txt" | grep "Python" | head -1 | awk '{print $NF}' | grep -v "dylib")
if [ -n "$python_path" ] && [ -f "$python_path" ]; then
local python_version=$($python_path --version 2>&1 | sed 's/Python //')
else
local python_version="unknown"
fi
# 內存使用
local mem=$(ps -o rss= -p $pid 2>/dev/null | awk '{print int($1/1024)}')
echo " PID $pid: $cmd"
echo " Python: $python_version"
echo " 內存: ${mem}MB"
echo ""
total_processes=$((total_processes + 1))
done
else
echo " (無運行中的 Python 進程)"
fi
echo "----------------------------------------"
echo "總結:"
echo " 總進程數: $total_processes"
echo " 腳本問題: $script_issues"
echo "========================================"
# 記錄到資料庫
if [ $script_issues -gt 0 ]; then
record_monitor_service "python" "degraded" "${LOCKED_PYTHON_VERSION}" "$script_issues issues"
return 1
else
record_monitor_service "python" "up" "${LOCKED_PYTHON_VERSION}" "OK"
return 0
fi
}
#===============================================================================
# 版本基線檢查
#===============================================================================
check_baseline() {
log "檢查 Python 版本基線..."
local script_issues=0
for script in "${MOMENTRY_SCRIPTS[@]}"; do
if [ -f "$script" ]; then
local shebang=$(head -1 "$script")
if [[ "$shebang" == *"/python3.11"* ]]; then
log_success "$(basename $script): 使用正確版本"
else
log_error "$(basename $script): 未使用 python3.11"
script_issues=$((script_issues + 1))
fi
fi
done
if [ $script_issues -gt 0 ]; then
return 1
else
return 0
fi
}
#===============================================================================
# 顯示狀態
#===============================================================================
show_status() {
discover_python_processes
}
#===============================================================================
# 主程序
#===============================================================================
command=${1:-status}
case $command in
status|check)
show_status
;;
baseline)
check_baseline
;;
*)
echo "用法: $0 {status|baseline}"
echo ""
echo " status - 顯示 Python 監控狀態"
echo " baseline - 檢查版本基線"
exit 1
;;
esac
-446
View File
@@ -1,446 +0,0 @@
#!/bin/bash
# 確保路徑正確(Crontab 環境可能缺少 PATH
export PATH="/usr/local/bin:/opt/homebrew/bin:/opt/homebrew/opt/postgresql@18/bin:/usr/bin:/bin:/sbin:$PATH"
# Momentry 備份監控與溫冷轉移
# 路徑: /Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/backup_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 備份根目錄
BACKUP_BASE="/Users/accusys/momentry/backup"
# 服務列表 (v2 新增 momentry_output)
SERVICES=("postgresql" "redis" "mariadb" "wordpress" "n8n" "qdrant" "gitea" "ollama" "caddy" "mongodb" "sftpgo" "php" "momentry")
# 溫冷分層配置 (預留給未來自動化策略使用)
_TIER_HOT=7 # 7天內 - 快速存儲
_TIER_WARM=30 # 7-30天 - 標準存儲
_TIER_COLD=90 # 30-90天 - 低成本存儲
_TIER_ARCHIVE=365 # >90天 - 歸檔
# 記錄備份元數據
record_backup() {
local service=$1
local backup_file=$2
local backup_size=$3
local backup_type=$4
psql -U accusys -h localhost -d momentry <<EOF 2>/dev/null
INSERT INTO backup_registry (service_name, backup_file, backup_size_bytes, backup_type, status, created_at)
VALUES ('$service', '$backup_file', $backup_size, '$backup_type', 'completed', NOW())
ON CONFLICT DO NOTHING;
EOF
}
# 記錄備份存儲統計
record_backup_stats() {
local tier=$1
local file_count=$2
local total_size=$3
psql -U accusys -h localhost -d momentry <<EOF 2>/dev/null
INSERT INTO backup_storage_stats (tier, file_count, total_size_bytes, record_time)
VALUES ('$tier', $file_count, $total_size, NOW());
EOF
}
# 初始化備份目錄結構
init_backup_dirs() {
log "初始化備份目錄結構..."
mkdir -p "$BACKUP_BASE"/{daily,weekly,monthly,archive}
for service in "${SERVICES[@]}"; do
mkdir -p "$BACKUP_BASE/daily/$service"
mkdir -p "$BACKUP_BASE/weekly/$service"
mkdir -p "$BACKUP_BASE/monthly/$service"
done
log "備份目錄結構已初始化"
}
# 檢查備份狀態
check_backup_status() {
log "=== 檢查備份狀態 ==="
# 命名規範: {service}_{type}_{YYYYMMDD}_{HHMMSS}.{ext}
# 例如: postgresql_db_20260315_030000.sql.gz
local total_backup_size=0
echo ""
echo "========================================"
echo "備份監控狀態"
echo "時間: $(date)"
echo "========================================"
echo ""
echo "命名規範: {service}_{type}_{YYYYMMDD}_{HHMMSS}.{ext}"
echo ""
for service in "${SERVICES[@]}"; do
service_backup_dir="$BACKUP_BASE/daily/$service"
if [ -d "$service_backup_dir" ]; then
file_count=$(find "$service_backup_dir" -type f 2>/dev/null | wc -l)
size=$(du -sb "$service_backup_dir" 2>/dev/null | cut -f1)
latest_file=$(find "$service_backup_dir" -type f \( -name "*.tar.gz" -o -name "*.sql.gz" -o -name "*.rdb" \) 2>/dev/null | head -1)
# 處理 size 為空或 0 的情況
if [ -z "$size" ] || [ "$size" = "0" ]; then
size=$(find "$service_backup_dir" -type f -exec ls -l {} \; 2>/dev/null | awk '{sum+=$5} END {print sum}')
fi
size_str="0B"
if [ -n "$size" ] && [ "$size" -gt 0 ]; then
if [ "$size" -gt 1073741824 ]; then
size_str="$((size / 1073741824))GB"
elif [ "$size" -gt 1048576 ]; then
size_str="$((size / 1048576))MB"
elif [ "$size" -gt 1024 ]; then
size_str="$((size / 1024))KB"
else
size_str="${size}B"
fi
fi
# 檢查最近備份時間 (使用文件名中的時間戳)
days_since_backup=0
today=$(date +%Y%m%d)
if [ -n "$latest_file" ]; then
# 從文件名提取日期 (支援 v1 和 v2 格式)
# v1: postgresql_db_momentry_20260325_030000.sql.gz
# v2: postgresql_db_momentry_v2_20260325_030000.sql.gz
file_date=$(echo "$latest_file" | sed -E 's/.*[_v]?2?_([0-9]{8})_[0-9]{6}.*/\1/')
if [ -n "$file_date" ] && [ "$file_date" = "$today" ]; then
days_since_backup=0
else
days_since_backup=1
fi
fi
# 狀態指示
if [ "$days_since_backup" -eq 0 ]; then
status="✅ 今日已備份"
elif [ "$days_since_backup" -le 1 ]; then
status="⚠️ 昨日已備份"
elif [ "$days_since_backup" -le 7 ]; then
status="⚠️ ${days_since_backup}天前"
else
status="❌ 超過${days_since_backup}天未備份!"
fi
echo " $service: $file_count 個文件, $size_str | $status"
[ -n "$size" ] && total_backup_size=$((total_backup_size + size))
# 記錄到資料庫
[ -n "$size" ] && record_backup "$service" "$service_backup_dir" "$size" "daily"
else
echo " $service: ❌ 備份目錄不存在"
fi
done
echo ""
echo "----------------------------------------"
echo "存儲分層:"
echo "----------------------------------------"
for tier in daily weekly monthly archive; do
tier_path="$BACKUP_BASE/$tier"
if [ -d "$tier_path" ]; then
file_count=$(find "$tier_path" -type f 2>/dev/null | wc -l)
size=$(du -sb "$tier_path" 2>/dev/null | cut -f1)
tier_size_str="0B"
if [ -n "$size" ] && [ "$size" -gt 0 ] 2>/dev/null; then
if [ "$size" -gt 1073741824 ]; then
tier_size_str="$((size / 1073741824))GB"
elif [ "$size" -gt 1048576 ]; then
tier_size_str="$((size / 1048576))MB"
else
tier_size_str="$((size / 1024))KB"
fi
fi
echo " $tier: $file_count 個文件, $tier_size_str"
[ -n "$size" ] && record_backup_stats "$tier" "$file_count" "$size"
fi
done
total_size_str="0B"
if [ "$total_backup_size" -gt 1073741824 ]; then
total_size_str="$((total_backup_size / 1073741824))GB"
elif [ "$total_backup_size" -gt 1048576 ]; then
total_size_str="$((total_backup_size / 1048576))MB"
elif [ "$total_backup_size" -gt 1024 ]; then
total_size_str="$((total_backup_size / 1024))KB"
else
total_size_str="${total_backup_size}B"
fi
echo ""
echo "----------------------------------------"
echo "總計: ${total_backup_size} bytes ($total_size_str)"
echo "========================================"
# 記錄總計
record_backup_stats "total" 0 "$total_backup_size"
# 檢查 PATH 錯誤
check_path_errors
}
# 檢查 PATH 相關錯誤
check_path_errors() {
local backup_log="/Users/accusys/momentry/log/backup.log"
if [ -f "$backup_log" ]; then
# 檢查最近的 command not found 錯誤
path_errors=$(grep -c "command not found" "$backup_log" 2>/dev/null || echo "0")
if [ "$path_errors" -gt 0 ]; then
echo ""
echo "========================================"
echo "⚠️ 警告: 檢測到 PATH 相關錯誤!"
echo "========================================"
echo " 錯誤次數: $path_errors"
echo " 請檢查: $backup_log"
echo ""
# 顯示最近3條錯誤
echo " 最近錯誤:"
grep -n "command not found" "$backup_log" 2>/dev/null | tail -3 | while read -r line; do
echo " - $line"
done
echo "========================================"
# 標記無效的備份文件
mark_invalid_backups
fi
fi
}
# 標記無效的備份文件
mark_invalid_backups() {
local backup_log="/Users/accusys/momentry/log/backup.log"
# 找出失敗的備份日期 (從 command not found 錯誤中提取)
grep "command not found" "$backup_log" 2>/dev/null | grep -oE "[0-9]{4}-[0-9]{2}-[0-9]{2}" | sort -u | while read -r date; do
local date_formatted
date_formatted=$(echo "$date" | tr -d '-')
local marker_file="$BACKUP_BASE/daily/postgresql/INVALID_BACKUP_${date_formatted}_030000.txt"
if [ ! -f "$marker_file" ]; then
echo "⚠️ 發現無效備份: $date" | tee -a "$LOG_FILE"
cat >"$marker_file" <<EOF
================================================================================
⚠️ 此備份文件無效 / THIS BACKUP IS INVALID
================================================================================
日期/Date: $date 03:00:00
原因/Reason: Crontab 執行時 PATH 環境變數缺少,導致命令找不到
(Crontab executed with missing PATH - command not found)
狀態/Status: ❌ 備份失敗 (Backup Failed)
修復方法/Fix Applied:
- 已在 backup_all.sh 和 crontab 中添加正確的 PATH 環境變數
- 已添加 PATH 錯誤監控到 backup_monitor.sh
================================================================================
EOF
fi
done
}
# 溫冷轉移 - 將舊備份移動到低成本存儲
tier_backups() {
log "執行溫冷轉移..."
local moved_count=0
# 7天前: daily -> weekly
# 命名格式: {service}_{type}_{YYYYMMDD}_{HHMMSS}.{ext}
find "$BACKUP_BASE/daily" -type f -mtime +7 | while read -r file; do
service=$(basename "$(dirname "$file")")
# 解析時間戳
filename=$(basename "$file")
timestamp=$(echo "$filename" | grep -oP '\d{8}_\d{6}' || echo "")
if [ -n "$timestamp" ]; then
year=${timestamp:0:4}
week=$(date -j -f "%Y%m%d_%H%M%S" "${timestamp}_0000" +%Y-W%V 2>/dev/null || echo "$year-W$(date +%V)")
else
week=$(date +%Y-W%V)
fi
dest_dir="$BACKUP_BASE/weekly/$service/$week"
mkdir -p "$dest_dir"
mv "$file" "$dest_dir/" 2>/dev/null && log "移動: $file -> $dest_dir" && moved_count=$((moved_count + 1))
done
# 30天前: weekly -> monthly
find "$BACKUP_BASE/weekly" -type f -mtime +30 | while read -r file; do
service=$(basename "$(dirname "$(dirname "$file")")")
month=$(date +%Y-%m)
dest_dir="$BACKUP_BASE/monthly/$service/$month"
mkdir -p "$dest_dir"
mv "$file" "$dest_dir/" 2>/dev/null && log "移動: $file -> $dest_dir" && moved_count=$((moved_count + 1))
done
# 90天前: monthly -> archive (長期歸檔)
find "$BACKUP_BASE/monthly" -type f -mtime +90 | while read -r file; do
service=$(basename "$(dirname "$(dirname "$file")")")
year=$(date +%Y)
dest_dir="$BACKUP_BASE/archive/$service/$year"
mkdir -p "$dest_dir"
mv "$file" "$dest_dir/" 2>/dev/null && log "歸檔: $file -> $dest_dir" && moved_count=$((moved_count + 1))
done
log "溫冷轉移完成: 移動了 $moved_count 個文件"
}
# 清理過期備份
cleanup_old() {
log "清理過期備份..."
# 歸檔超過 365 天
find "$BACKUP_BASE/archive" -type f -mtime +365 -delete 2>/dev/null
# 每月備份保留 12 個月
find "$BACKUP_BASE/monthly" -type f -mtime +365 -delete 2>/dev/null
# 每週備份保留 12 週
find "$BACKUP_BASE/weekly" -type f -mtime +84 -delete 2>/dev/null
# 每日備份保留 30 天
find "$BACKUP_BASE/daily" -type f -mtime +30 -delete 2>/dev/null
log "清理完成"
}
# 驗證備份完整性
verify_backup() {
local backup_file=$1
if [[ "$backup_file" == *.tar.gz ]]; then
tar -tzf "$backup_file" >/dev/null 2>&1
return $?
elif [[ "$backup_file" == *.sql ]]; then
head -1 "$backup_file" | grep -q "SQL" && return 0
return 1
elif [[ "$backup_file" == *.rdb ]]; then
file "$backup_file" | grep -q "data" && return 0
return 1
fi
return 0
}
# 生成備份報告
generate_report() {
local report_file
report_file="/Users/accusys/momentry/log/backup_report_$(date +%Y%m%d).txt"
{
echo "========================================"
echo "Momentry 備份報告"
echo "生成時間: $(date)"
echo "========================================"
echo ""
echo "## 備份狀態"
check_backup_status
echo ""
echo "## 存儲使用趨勢 (最近30天)"
psql -U accusys -h localhost -d momentry -t -A -c "
SELECT tier,
COUNT(*) as files,
AVG(total_size_bytes)::bigint as avg_size,
MAX(total_size_bytes)::bigint as max_size
FROM backup_storage_stats
WHERE record_time > NOW() - INTERVAL '30 days'
GROUP BY tier
ORDER BY tier;
" 2>/dev/null || echo " (無數據)"
echo ""
echo "## 建議"
# 檢查是否有服務超過7天未備份
for service in "${SERVICES[@]}"; do
latest=$(find "$BACKUP_BASE/daily/$service" -type f 2>/dev/null | head -1)
if [ -n "$latest" ]; then
days_old=$((($(date +%s) - $(stat -f "%m" "$latest" 2>/dev/null || echo "0")) / 86400))
if [ "$days_old" -gt 7 ]; then
echo " - ⚠️ $service 超過 $days_old 天未備份,建議立即執行備份"
fi
fi
done
} >"$report_file"
log "報告已生成: $report_file"
echo "$report_file"
}
# 主程序
command=${1:-status}
case $command in
status)
check_backup_status
;;
init)
init_backup_dirs
;;
tier)
tier_backups
;;
cleanup)
cleanup_old
;;
verify)
verify_backup "${2:-}"
;;
report)
generate_report
;;
all)
log "執行完整備份維護..."
check_backup_status
tier_backups
cleanup_old
generate_report
log "備份維護完成"
;;
*)
echo "用法: $0 {status|init|tier|cleanup|verify|report|all}"
echo ""
echo " status - 檢查備份狀態"
echo " init - 初始化備份目錄"
echo " tier - 執行溫冷轉移"
echo " cleanup - 清理過期備份"
echo " verify - 驗證備份完整性"
echo " report - 生成備份報告"
echo " all - 執行所有維護任務"
exit 1
;;
esac
-193
View File
@@ -1,193 +0,0 @@
#!/bin/bash
# Momentry Storage 管理 (Layer 7)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/storage/storage_manager.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/storage_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 存儲路徑配置
STORAGE_BASE="/Users/accusys/momentry"
DATA_DIR="$STORAGE_BASE/data"
TEMP_DIR="$STORAGE_BASE/tmp"
BACKUP_DIR="$STORAGE_BASE/backup"
# 用戶集群
CLUSTERS=("family" "work" "wordpress" "shared")
# 記錄使用統計
record_usage() {
local cluster=$1
local tier=$2
local file_count=$3
local total_size=$4
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO storage_usage_stats (user_cluster, storage_tier, file_count, total_size_bytes, record_time)
VALUES ('$cluster', '$tier', $file_count, $total_size, NOW());
EOF
}
# 記錄檔案註冊
register_file() {
local file_path=$1
local user_cluster=$2
local file_size=$3
file_name=$(basename "$file_path")
file_hash=$(echo "$file_path" | md5sum | cut -d' ' -f1)
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO file_registry (file_name, file_path, file_path_hash, file_size, user_cluster, storage_tier, status, created_at)
VALUES ('$file_name', '$file_path', '$file_hash', $file_size, '$user_cluster', 'hot', 'active', NOW())
ON CONFLICT (file_path_hash) DO UPDATE SET
last_accessed_at = NOW(),
access_count = file_registry.access_count + 1;
EOF
}
# 初始化目錄結構
init_directories() {
echo "初始化目錄結構..."
# 主目錄
mkdir -p "$DATA_DIR"
mkdir -p "$TEMP_DIR"
mkdir -p "$BACKUP_DIR"/{daily,weekly,monthly,archive}
# 用戶集群目錄
for cluster in "${CLUSTERS[@]}"; do
mkdir -p "$DATA_DIR/$cluster"
done
# 臨時子目錄
mkdir -p "$TEMP_DIR"/{upload,processing,cache,session}
echo "目錄結構已初始化"
}
# 顯示存儲狀態
show_status() {
echo "========================================"
echo "Layer 7: Storage Status"
echo "Time: $(date)"
echo "========================================"
echo ""
echo "存儲路徑:"
echo " 數據: $DATA_DIR"
echo " 臨時: $TEMP_DIR"
echo " 備份: $BACKUP_DIR"
echo ""
echo "用戶集群:"
echo "----------------------------------------"
for cluster in "${CLUSTERS[@]}"; do
cluster_path="$DATA_DIR/$cluster"
if [ -d "$cluster_path" ]; then
file_count=$(find "$cluster_path" -type f 2>/dev/null | wc -l)
total_size=$(du -sb "$cluster_path" 2>/dev/null | cut -f1)
size_str="0B"
if [ -n "$total_size" ] && [ "$total_size" -gt 0 ] 2>/dev/null; then
if [ "$total_size" -gt 1073741824 ]; then
size_str="$((total_size / 1073741824))GB"
elif [ "$total_size" -gt 1048576 ]; then
size_str="$((total_size / 1048576))MB"
elif [ "$total_size" -gt 1024 ]; then
size_str="$((total_size / 1024))KB"
else
size_str="${total_size}B"
fi
fi
echo " $cluster: $file_count files, $size_str"
[ -n "$total_size" ] && record_usage "$cluster" "hot" "$file_count" "$total_size"
else
echo " $cluster: (未創建)"
fi
done
echo ""
echo "臨時文件:"
echo "----------------------------------------"
for subdir in upload processing cache session; do
subdir_path="$TEMP_DIR/$subdir"
if [ -d "$subdir_path" ]; then
file_count=$(find "$subdir_path" -type f 2>/dev/null | wc -l)
size=$(du -sb "$subdir_path" 2>/dev/null | cut -f1)
echo " $subdir: $file_count files"
fi
done
echo ""
echo "備份目錄:"
echo "----------------------------------------"
for subdir in daily weekly monthly archive; do
subdir_path="$BACKUP_DIR/$subdir"
if [ -d "$subdir_path" ]; then
file_count=$(find "$subdir_path" -type f 2>/dev/null | wc -l)
size=$(du -sb "$subdir_path" 2>/dev/null | cut -f1)
echo " $subdir: $file_count files"
fi
done
echo ""
echo "========================================"
# 顯示資料庫統計
echo ""
echo "資料庫統計 (file_registry):"
psql -U accusys -h localhost -d momentry -t -A -c "
SELECT user_cluster, storage_tier, COUNT(*) as files,
SUM(file_size)::bigint as total_size
FROM file_registry
WHERE status = 'active'
GROUP BY user_cluster, storage_tier;
" 2>/dev/null || echo " (表未初始化)"
}
# 清理臨時文件
clean_temp() {
echo "清理臨時文件..."
# 清理超過 7 天的上傳
find "$TEMP_DIR/upload" -type f -mtime +7 -delete 2>/dev/null
# 清理超過 30 天的緩存
find "$TEMP_DIR/cache" -type f -mtime +30 -delete 2>/dev/null
# 清理超過 7 天的處理中
find "$TEMP_DIR/processing" -type f -mtime +7 -delete 2>/dev/null
echo "清理完成"
}
# 主程序
command=${1:-status}
case $command in
status)
show_status
;;
init)
init_directories
;;
clean)
clean_temp
;;
*)
echo "用法: $0 {status|init|clean}"
exit 1
;;
esac
-163
View File
@@ -1,163 +0,0 @@
#!/bin/bash
# Momentry 使用者會話追蹤 (Layer 6)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/users/session_tracker.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/session_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 記錄會話
record_session() {
local session_type=$1
local service=$2
local username=$3
local source_ip=$4
local status=$5
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_sessions (session_type, service_name, username, source_ip, connected_at, status)
VALUES ('$session_type', '$service', '$username', '$source_ip', NOW(), '$status');
EOF
}
# 記錄登入
record_login() {
local user_type=$1
local username=$2
local source_ip=$3
local success=$4
local method=$5
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_logins (user_type, username, source_ip, success, login_method, login_at)
VALUES ('$user_type', '$username', '$source_ip', $success, '$method', NOW());
EOF
}
# 記錄異常
record_anomaly() {
local anomaly_type=$1
local severity=$2
local username=$3
local source_ip=$4
local description=$5
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_anomalies (anomaly_type, severity, source_type, username, source_ip, description, detected_at)
VALUES ('$anomaly_type', '$severity', 'system', '$username', '$source_ip', '$description', NOW());
EOF
}
# SSH 會話
track_ssh() {
echo "SSH 會話:"
# 獲取當前 SSH 連線
who | grep -E "pts|tty" | while read -r line; do
user=$(echo "$line" | awk '{print $1}')
tty=$(echo "$line" | awk '{print $2}')
login_time=$(echo "$line" | awk '{print $3,$4}')
ip=$(echo "$line" | awk '{print $NF}' | tr -d '()')
if [ -n "$ip" ] && [ "$ip" != "-" ]; then
echo " - $user @ $ip (tty $tty) 登入時間: $login_time"
record_session "ssh" "sshd" "$user" "$ip" "active"
fi
done
# 檢查 SSH 登入失敗
echo ""
echo "SSH 登入失敗 (最近 5 分鐘):"
last -5 -f /var/log/auth.log 2>/dev/null | grep -i "failed password" | tail -5 | while read -r line; do
user=$(echo "$line" | awk '{print $9}')
ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | tail -1)
if [ -n "$ip" ]; then
echo " - Failed: $user from $ip"
record_login "system" "$user" "$ip" "false" "ssh"
fi
done
}
# Web 服務會話
track_web() {
echo ""
echo "Web 服務:"
# n8n 活躍會話 (如果有認證)
n8n_sessions=0
echo " - n8n: 檢查中... (需要 API key)"
# Gitea 活躍會話
gitea_sessions=0
echo " - Gitea: 檢查中... (需要登入)"
}
# 資料庫連線
track_database() {
echo ""
echo "資料庫連線:"
# PostgreSQL
pg_conn=$(psql -U accusys -h localhost -t -A -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'momentry';" 2>/dev/null || echo "0")
echo " - PostgreSQL: $pg_conn connections"
# Redis
redis_conn=$(redis-cli -a accusys INFO clients 2>/dev/null | grep "connected_clients" | cut -d: -f2 | tr -d '\r')
echo " - Redis: $redis_conn clients"
}
# SFTP 會話
track_sftp() {
echo ""
echo "SFTP 會話:"
# 檢查 SFTPGo 在線用戶
if nc -z localhost 2222 2>/dev/null; then
echo " - SFTPGo: 檢查中..."
fi
}
# 檢測暴力破解
detect_bruteforce() {
echo ""
echo "異常檢測:"
# 檢查 SSH 暴力破解
now=$(date +%s)
window=300 # 5 分鐘
# 統計最近失敗
fail_count=$(last -f /var/log/auth.log 2>/dev/null | grep -i "failed" | wc -l)
if [ $fail_count -gt 10 ]; then
echo " ⚠️ 發現潛在暴力破解嘗試: $fail_count 次失敗"
record_anomaly "bruteforce" "critical" "unknown" "multiple" "SSH暴力破解: $fail_count 次失敗"
else
echo " ✓ 無明顯暴力破解跡象"
fi
}
# 主程序
echo "========================================"
echo "Layer 6: User Session Tracking"
echo "Time: $(date)"
echo "========================================"
echo ""
track_ssh
track_web
track_database
track_sftp
detect_bruteforce
echo ""
echo "========================================"
log "Session tracking completed"
-265
View File
@@ -1,265 +0,0 @@
#!/bin/bash
# Momentry n8n Workflow 監控 (Layer 3)
# 路徑: /Users/accusys/momentry_core_0.1/monitor/workflow/n8n_workflow_monitor.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MONITOR_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="/Users/accusys/momentry/log/monitor"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/workflow_check.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# n8n API 配置
N8N_HOST="http://localhost:5678"
N8N_API_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNjdiY2UzOS1iY2RkLTRjMjEtYmMwYy0yODNhYmI3ZjVjMjMiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzczNjM5ODU4fQ.QOmOju2jLy07GrgXYvylM5AyFINPC06crKEsLLC988I"
# 從資料庫獲取 workflow
fetch_workflows_from_db() {
PGPASSWORD=accusys psql -U n8n -h localhost -d n8n -t -A <<'EOF'
SELECT json_agg(row_to_json(t)) FROM (
SELECT w.id, w.name, w.active, w."createdAt", w."updatedAt",
COALESCE(u.email, 'unknown') as owner_email
FROM workflow_entity w
LEFT JOIN shared_workflow sw ON w.id = sw."workflowId"
LEFT JOIN project p ON sw."projectId" = p.id
LEFT JOIN "user" u ON p."creatorId" = u.id
) t
EOF
}
# 記錄 workflow
record_workflow() {
local wf_id=$1
local wf_name=$2
local active=$3
local last_exec=$4
local exec_count=$5
local success=$6
local failure=$7
local avg_duration=$8
local has_schedule=$9
local has_webhook=${10}
local idle_days=${11}
local suggestion=${12}
psql -U accusys -h localhost -d momentry << EOF 2>/dev/null
INSERT INTO monitor_workflows
(workflow_id, workflow_name, is_active, last_executed_at, execution_count,
success_count, failure_count, avg_duration_ms, has_schedule, has_webhook,
idle_days, suggestion, checked_at)
VALUES
('$wf_id', '$wf_name', $active, $last_exec, $exec_count,
$success, $failure, $avg_duration, $has_schedule, $has_webhook,
$idle_days, '$suggestion', NOW())
ON CONFLICT (workflow_id) DO UPDATE SET
workflow_name = EXCLUDED.workflow_name,
is_active = EXCLUDED.is_active,
last_executed_at = EXCLUDED.last_executed_at,
execution_count = EXCLUDED.execution_count,
success_count = EXCLUDED.success_count,
failure_count = EXCLUDED.failure_count,
avg_duration_ms = EXCLUDED.avg_duration_ms,
has_schedule = EXCLUDED.has_schedule,
has_webhook = EXCLUDED.has_webhook,
idle_days = EXCLUDED.idle_days,
suggestion = EXCLUDED.suggestion,
checked_at = NOW();
EOF
}
# 獲取 workflow 列表
fetch_workflows() {
curl -s -H "Accept: application/json" \
-H "X-N8N-API-KEY: ${N8N_API_KEY}" \
"${N8N_HOST}/rest/workflows" 2>/dev/null || echo "[]"
}
# 獲取 workflow 執行統計
fetch_executions() {
local wf_id=$1
curl -s -H "Accept: application/json" \
-H "X-N8N-API-KEY: ${N8N_API_KEY}" \
"${N8N_HOST}/rest/executions?workflowId=${wf_id}&limit=50" 2>/dev/null || echo "{\"data\":[]}"
}
# 判斷是否有 schedule
has_schedule() {
local wf_data=$1
echo "$wf_data" | grep -q '"type":"schedule"' && echo "true" || echo "false"
}
# 判斷是否有 webhook
has_webhook() {
local wf_data=$1
echo "$wf_data" | grep -q '"type":"webhook"' && echo "true" || echo "false"
}
# 計算閒置天數
calc_idle_days() {
local last_exec=$1
if [ "$last_exec" = "null" ] || [ -z "$last_exec" ]; then
echo "999"
else
echo "0"
fi
}
# 生成建議
generate_suggestion() {
local has_schedule=$1
local has_webhook=$2
local idle_days=$3
local failure_rate=$4
if [ "$idle_days" -ge 90 ]; then
echo "建議刪除"
elif [ "$idle_days" -ge 30 ] && [ "$has_schedule" = "false" ] && [ "$has_webhook" = "false" ]; then
echo "建議停用"
elif [ "$failure_rate" -gt 20 ]; then
echo "建議優化"
else
echo ""
fi
}
# 主程序
echo "========================================"
echo "Layer 3: n8n Workflow Monitoring"
echo "Time: $(date)"
echo "========================================"
echo ""
# 檢查 n8n 是否可用 (檢查 PostgreSQL 中的 n8n 資料庫)
if ! PGPASSWORD=accusys psql -U n8n -h localhost -d n8n -c "SELECT 1" >/dev/null 2>&1; then
echo "n8n 資料庫不可用"
log "n8n database unavailable"
exit 1
fi
# 獲取 workflow 列表 (從資料庫)
workflows=$(fetch_workflows_from_db)
total_count=$(echo "$workflows" | jq 'length' 2>/dev/null || echo "0")
active_count=$(echo "$workflows" | jq '[.[] | select(.active == true)] | length' 2>/dev/null || echo "0")
echo "總 Workflow: $total_count"
echo "啟用中: $active_count"
echo ""
# 閒置閾值
IDLE_THRESHOLD=30
echo "Workflow 詳細:"
echo "----------------------------------------"
total_idle=0
for wf in $(echo "$workflows" | jq -r '.[] | @base64' 2>/dev/null); do
wf_decoded=$(echo "$wf" | base64 -d)
wf_id=$(echo "$wf_decoded" | jq -r '.id' 2>/dev/null)
wf_name=$(echo "$wf_decoded" | jq -r '.name' 2>/dev/null)
is_active=$(echo "$wf_decoded" | jq -r '.active' 2>/dev/null)
wf_owner=$(echo "$wf_decoded" | jq -r '.owner_email' 2>/dev/null)
# 從資料庫獲取執行數據
exec_data=$(PGPASSWORD=accusys psql -U n8n -h localhost -d n8n -t -A <<EOF
SELECT json_agg(row_to_json(t)) FROM (
SELECT status, "startedAt", "stoppedAt",
EXTRACT(EPOCH FROM ("stoppedAt" - "startedAt")) * 1000 as execution_time
FROM execution_entity
WHERE "workflowId" = '$wf_id'
ORDER BY "startedAt" DESC
LIMIT 50
) t
EOF
)
exec_count=$(echo "$exec_data" | jq '. | length' 2>/dev/null || echo "0")
# 計算成功/失敗
success_count=$(echo "$exec_data" | jq '[.[] | select(.status == "success")] | length' 2>/dev/null || echo "0")
failure_count=$(echo "$exec_data" | jq '[.[] | select(.status == "error")] | length' 2>/dev/null || echo "0")
# 平均執行時間
avg_duration=$(echo "$exec_data" | jq '[.[] | .execution_time] | map(select(. != null)) | add / length | floor' 2>/dev/null || echo "0")
# 檢查是否有 webhook
has_webh=$(PGPASSWORD=accusys psql -U n8n -h localhost -d n8n -t -A -c "
SELECT COUNT(*) FROM webhook_entity WHERE workflow_id = '$wf_id'
" 2>/dev/null || echo "0")
[ "$has_webh" -gt 0 ] && has_webh="true" || has_webh="false"
has_sched="false"
# 最後執行時間
last_exec=$(echo "$exec_data" | jq -r '.[0].startedAt // "null"' 2>/dev/null | head -1)
if [ "$last_exec" = "null" ] || [ -z "$last_exec" ]; then
idle_days=999
else
idle_days=0
fi
# 確保數值正確
exec_count=$(echo "$exec_count" | tr -d '[:space:]' || echo "0")
success_count=$(echo "$success_count" | tr -d '[:space:]' || echo "0")
failure_count=$(echo "$failure_count" | tr -d '[:space:]' || echo "0")
avg_duration=$(echo "$avg_duration" | tr -d '[:space:]' || echo "0")
# 計算失敗率
if [ -n "$exec_count" ] && [ "$exec_count" -gt 0 ] 2>/dev/null; then
failure_rate=$(( failure_count * 100 / exec_count ))
else
failure_rate=0
fi
# 生成建議
suggestion=$(generate_suggestion "$has_sched" "$has_webh" "$idle_days" "$failure_rate")
# 記錄到資料庫
if [ "$last_exec" = "null" ] || [ -z "$last_exec" ]; then
record_workflow "$wf_id" "$wf_name" "$is_active" "NULL" "$exec_count" "$success_count" "$failure_count" "$avg_duration" "$has_sched" "$has_webh" "$idle_days" "$suggestion"
else
record_workflow "$wf_id" "$wf_name" "$is_active" "'$last_exec'" "$exec_count" "$success_count" "$failure_count" "$avg_duration" "$has_sched" "$has_webh" "$idle_days" "$suggestion"
fi
# 顯示
status_icon="○"
if [ "$is_active" = "true" ]; then
status_icon="●"
fi
idle_info=""
if [ "$idle_days" -ge "$IDLE_THRESHOLD" ]; then
idle_info=" [閒置 $idle_days 天]"
total_idle=$((total_idle + 1))
fi
suggestion_info=""
if [ -n "$suggestion" ]; then
suggestion_info=" [$suggestion]"
fi
echo "$status_icon $wf_name (ID: $wf_id) [$wf_owner]$idle_info$suggestion_info"
echo " 執行: $exec_count (成功: $success_count, 失敗: $failure_count) | 平均: ${avg_duration}ms"
done
echo "----------------------------------------"
echo "閒置 Workflow (> $IDLE_THRESHOLD 天): $total_idle"
echo ""
log "Workflow check completed: $total_count total, $total_idle idle"
# 顯示閒置 workflow
if [ $total_idle -gt 0 ]; then
echo ""
echo "閒置 Workflow 建議:"
psql -U accusys -h localhost -d momentry -t -A -c "
SELECT ' - ' || workflow_name || ': ' || suggestion
FROM monitor_workflows
WHERE idle_days >= $IDLE_THRESHOLD AND suggestion != '';
" 2>/dev/null
fi
-1
View File
@@ -1 +0,0 @@
faster-whisper>=1.0.0
-3
View File
@@ -1,3 +0,0 @@
edition = "2021"
max_width = 100
tab_spaces = 4
-53
View File
@@ -1,53 +0,0 @@
#!/opt/homebrew/bin/python3.11
import sys
import json
import tempfile
import os
from faster_whisper import WhisperModel
def run_asr(video_path, output_path):
print(f"ASR_START", file=sys.stderr)
print(f"Loading Whisper model...", file=sys.stderr)
model = WhisperModel("tiny", device="cpu", compute_type="int8")
print(f"Transcribing: {video_path}", file=sys.stderr)
segments, info = model.transcribe(video_path, beam_size=5)
print(f"ASR_LANGUAGE:{info.language}", file=sys.stderr)
print(
f"Detected language: {info.language} (probability: {info.language_probability:.2f})",
file=sys.stderr,
)
results = []
total_segments = 0
for segment in segments:
results.append(
{"start": segment.start, "end": segment.end, "text": segment.text.strip()}
)
total_segments += 1
if total_segments % 100 == 0:
print(f"ASR_PROGRESS:{total_segments}", file=sys.stderr)
output = {
"language": info.language,
"language_probability": info.language_probability,
"segments": results,
}
with open(output_path, "w") as f:
json.dump(output, f, indent=2)
print(f"ASR_COMPLETE:{total_segments}", file=sys.stderr)
print(f"ASR complete. {len(results)} segments.", file=sys.stderr)
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: asr_processor.py <video_path> <output_json_path>")
sys.exit(1)
run_asr(sys.argv[1], sys.argv[2])
-96
View File
@@ -1,96 +0,0 @@
#!/bin/bash
# Config Check Script
# 驗證配置是否正確設置
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "=========================================="
echo "Momentry Core 配置檢查"
echo "=========================================="
# 檢查 .env 文件
if [ -f ".env" ]; then
echo -e "${GREEN}✅ .env 文件存在${NC}"
else
if [ -f ".env.example" ]; then
echo -e "${YELLOW}⚠️ .env 文件不存在,使用模板創建...${NC}"
cp .env.example .env
echo -e "${YELLOW}⚠️ 已創建 .env,請編輯並設置正確的憑據${NC}"
else
echo -e "${RED}❌ .env 和 .env.example 都不存在${NC}"
fi
fi
# 檢查必要配置
check_var() {
local var_name="$1"
local description="$2"
if grep -q "^${var_name}=" .env 2>/dev/null; then
echo -e "${GREEN}${var_name}${NC} - $description"
else
echo -e "${YELLOW}⚠️ ${var_name}${NC} - $description (使用默認值)"
fi
}
if [ -f ".env" ]; then
echo ""
echo "檢查環境變數..."
check_var "DATABASE_URL" "PostgreSQL 連接"
check_var "REDIS_URL" "Redis 連接"
check_var "REDIS_PASSWORD" "Redis 密碼"
check_var "MOMENTRY_OUTPUT_DIR" "輸出目錄"
check_var "MOMENTRY_PYTHON_PATH" "Python 路徑"
check_var "RUST_LOG" "日誌級別"
fi
# 檢查目錄權限
echo ""
echo "檢查目錄權限..."
check_dir() {
local dir="$1"
local description="$2"
if [ -d "$dir" ]; then
if [ -w "$dir" ]; then
echo -e "${GREEN}${dir}${NC} - $description (可寫)"
else
echo -e "${RED}${dir}${NC} - $description (不可寫)"
fi
else
echo -e "${YELLOW}⚠️ ${dir}${NC} - $description (目錄不存在)"
fi
}
check_dir "/Users/accusys/momentry/output" "輸出目錄"
check_dir "/Users/accusys/momentry/backup" "備份目錄"
# 檢查 Python
echo ""
echo "檢查 Python..."
if command -v python3.11 &> /dev/null; then
version=$(python3.11 --version 2>&1)
echo -e "${GREEN}✅ Python 3.11 可用${NC} ($version)"
else
echo -e "${RED}❌ Python 3.11 不可用${NC}"
fi
# 檢查 Rust
echo ""
echo "檢查 Rust..."
if command -v cargo &> /dev/null; then
version=$(cargo --version 2>&1)
echo -e "${GREEN}✅ Cargo 可用${NC} ($version)"
else
echo -e "${RED}❌ Cargo 不可用${NC}"
fi
echo ""
echo "=========================================="
echo "配置檢查完成"
echo "=========================================="
-78
View File
@@ -1,78 +0,0 @@
#!/bin/bash
# MongoDB Installation Script
echo "=== MongoDB Installation ==="
echo ""
# Step 1: Create directories
echo "Step 1: Creating directories..."
sudo mkdir -p /Users/accusys/momentry/var
sudo mkdir -p /Users/accusys/momentry/log
sudo chown -R accusys:staff /Users/accusys/momentry
echo " ✓ Directories created"
# Step 2: Check and backup existing plist
echo ""
echo "Step 2: Checking existing plist..."
PLIST_PATH="/Library/LaunchDaemons/com.momentry.mongodb.plist"
if [ -f "$PLIST_PATH" ]; then
BACKUP_NAME="${PLIST_PATH}.$(date +%Y%m%d%H%M%S).bak"
echo " Backing up existing plist to: $BACKUP_NAME"
sudo mv "$PLIST_PATH" "$BACKUP_NAME"
echo " ✓ Existing plist backed up"
else
echo " ✓ No existing plist found"
fi
# Step 3: Copy new plist
echo ""
echo "Step 3: Copying new plist..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_PLIST="$SCRIPT_DIR/momentry_runtime/plist/com.momentry.mongodb.plist"
if [ -f "$SOURCE_PLIST" ]; then
sudo cp "$SOURCE_PLIST" /Library/LaunchDaemons/
echo " ✓ plist copied"
else
echo " ✗ Source plist not found: $SOURCE_PLIST"
exit 1
fi
# Step 4: Start MongoDB
echo ""
echo "Step 4: Starting MongoDB..."
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
echo " ✓ MongoDB started"
# Step 5: Verify
echo ""
echo "Step 5: Verifying..."
sleep 2
echo ""
echo " Port 27017 status:"
lsof -i :27017 || echo " (checking...)"
echo ""
echo " Service status:"
sudo launchctl list | grep momentry || echo " No service found"
# Step 6: Create user (without auth first)
echo ""
echo "Step 6: Creating database user..."
mongosh --eval '
use admin
db.createUser({
user: "accusys",
pwd: "Test3200Test3200",
roles: [{ role: "root", db: "admin" }]
})
' 2>/dev/null || echo " (user creation deferred - MongoDB may still be starting)"
echo ""
echo "=== Installation Complete ==="
echo ""
echo "Connection string:"
echo " mongodb://accusys:Test3200Test3200@localhost:27017/momentry"
echo ""
echo "Remote access enabled (bind_ip: 0.0.0.0)"
-97
View File
@@ -1,97 +0,0 @@
#!/opt/homebrew/bin/python3.11
import sys
import os
import subprocess
import json
import argparse
def get_duration(video_path):
result = subprocess.run(
["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", video_path],
capture_output=True,
text=True,
)
data = json.loads(result.stdout)
return float(data["format"]["duration"])
def extract_thumbnails(video_path, uuid, output_dir, count=6):
"""Extract evenly-spaced thumbnails from video."""
if not os.path.exists(video_path):
print(f"Error: Video file not found: {video_path}", file=sys.stderr)
return None
duration = get_duration(video_path)
print(f"Video duration: {duration:.2f}s", file=sys.stderr)
# Output directory
thumb_dir = os.path.join(output_dir, uuid)
os.makedirs(thumb_dir, exist_ok=True)
# Calculate timestamps (skip first 10%, skip last 10%)
start_ts = duration * 0.1
end_ts = duration * 0.9
interval = (end_ts - start_ts) / (count - 1) if count > 1 else 0
extracted = []
for i in range(count):
if count == 1:
ts = duration / 2
else:
ts = start_ts + (interval * i)
output_file = os.path.join(thumb_dir, f"thumb_{i:03d}.jpg")
result = subprocess.run(
[
"ffmpeg",
"-y",
"-ss",
str(ts),
"-i",
video_path,
"-vframes",
"1",
"-q:v",
"2",
"-vf",
"scale=320:-1",
output_file,
],
capture_output=True,
)
if result.returncode == 0 and os.path.exists(output_file):
extracted.append(output_file)
print(f" Extracted: {output_file} at {ts:.1f}s", file=sys.stderr)
else:
print(f" Failed to extract frame at {ts:.1f}s", file=sys.stderr)
return extracted
def main():
parser = argparse.ArgumentParser(
description="Extract keyframe thumbnails from video"
)
parser.add_argument("video_path", help="Path to video file")
parser.add_argument("uuid", help="Video UUID")
parser.add_argument("-o", "--output", default="thumbnails", help="Output directory")
parser.add_argument(
"-c", "--count", type=int, default=6, help="Number of thumbnails"
)
args = parser.parse_args()
result = extract_thumbnails(args.video_path, args.uuid, args.output, args.count)
if result:
print(json.dumps({"uuid": args.uuid, "count": len(result), "files": result}))
else:
print(json.dumps({"error": "Failed to extract thumbnails"}))
sys.exit(1)
if __name__ == "__main__":
main()
-151
View File
@@ -1,151 +0,0 @@
#!/bin/bash
# Momentry 服務故障排除快速參考
# 用法: ./troubleshoot.sh [service]
# 例如: ./troubleshoot.sh n8n
SERVICE=${1:-all}
echo "========================================"
echo "Momentry 故障排除快速檢查"
echo "========================================"
echo ""
case "$SERVICE" in
all)
echo "執行所有服務健康檢查..."
/Users/accusys/momentry_core_0.1/monitor/service/health_check.sh
;;
postgresql|postgres|pg)
echo "=== PostgreSQL 診斷 ==="
echo "狀態: $(pg_isready -h localhost -p 5432 -U accusys 2>&1)"
echo "連線測試: $(psql -U accusys -h localhost -d momentry -c 'SELECT 1' 2>&1 | head -1)"
echo "數據庫列表:"
psql -U accusys -h localhost -l 2>/dev/null | grep -v "rows)" || echo " 無法連線"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/postgresql.log"
;;
redis)
echo "=== Redis 診斷 ==="
echo "狀態: $(redis-cli -a accusys ping 2>&1)"
echo "記憶體: $(redis-cli -a accusys INFO memory 2>/dev/null | grep used_memory_human | head -1)"
echo "連線數: $(redis-cli -a accusys INFO clients 2>/dev/null | grep connected_clients | head -1)"
echo "Keys: $(redis-cli -a accusys DBSIZE 2>/dev/null)"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/redis.log"
;;
mariadb|mysql)
echo "=== MariaDB 診斷 ==="
echo "狀態: $(mysql -u accusys -e 'SELECT 1' 2>&1 | head -1)"
echo "用戶:"
mysql -u accusys -e "SELECT user, host FROM mysql.user;" 2>/dev/null || echo " 無法連線"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/mariadb.log"
;;
n8n)
echo "=== n8n 診斷 ==="
echo "Web 訪問: $(curl -s -o /dev/null -w '%{http_code}' http://localhost:8085/ 2>&1)"
echo "Port 8085: $(lsof -i :8085 | tail -1)"
echo ""
echo "PostgreSQL 連線:"
psql -U n8n -h localhost -d n8n -c "SELECT COUNT(*) as workflows FROM workflow_entity;" 2>&1
psql -U n8n -h localhost -d n8n -c "SELECT email, \"firstName\", \"roleSlug\" FROM \"user\";" 2>&1
echo ""
echo "Redis Queue:"
redis-cli -a accusys LLEN bull:n8n:wait 2>/dev/null || echo " 無法連線"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/n8n-main.log"
;;
ollama)
echo "=== Ollama 診斷 ==="
echo "API 狀態: $(curl -s http://localhost:11434/api/tags 2>&1 | head -1)"
echo "模型列表:"
curl -s http://localhost:11434/api/tags 2>/dev/null | jq -r '.models[].name' 2>/dev/null || echo " 無法獲取"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/ollama.log"
;;
qdrant)
echo "=== Qdrant 診斷 ==="
echo "API 狀態: $(curl -s -o /dev/null -w '%{http_code}' -H 'api-key: Test3200Test3200' http://localhost:6333/ 2>&1)"
echo "Collections:"
curl -s -H "api-key: Test3200Test3200" http://localhost:6333/collections 2>/dev/null | jq -r '.result[].name' 2>/dev/null || echo " 無法獲取"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/qdrant.log"
;;
caddy)
echo "=== Caddy 診斷 ==="
echo "Admin API: $(curl -s http://localhost:2019/config/ 2>&1 | head -1)"
echo "Port 2019: $(lsof -i :2019 | tail -1)"
echo "Port 443: $(lsof -i :443 | tail -1)"
echo ""
echo "配置: cat /Users/accusys/momentry/etc/Caddyfile"
echo "日誌: tail -50 /Users/accusys/momentry/log/caddy.log"
;;
gitea)
echo "=== Gitea 診斷 ==="
echo "Web: $(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/ 2>&1)"
echo "Port 3000: $(lsof -i :3000 | tail -1)"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/gitea.log"
;;
sftpgo)
echo "=== SFTPGo 診斷 ==="
echo "HTTP: $(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/ 2>&1)"
echo "SFTP Port 2022: $(lsof -i :2022 | tail -1)"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/sftpgo.log"
;;
mongodb|mongo)
echo "=== MongoDB 診斷 ==="
mongosh --quiet --username accusys --password Test3200Test3200 --authenticationDatabase admin --eval "db.adminCommand('ping')" 2>&1
echo "數據庫:"
mongosh --quiet --username accusys --password Test3200Test3200 --authenticationDatabase admin --eval "db.adminCommand({listDatabases:1})" 2>&1 | grep name || echo " 無法獲取"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/mongodb.log"
;;
php)
echo "=== PHP-FPM 診斷 ==="
echo "進程: $(pgrep -a php-fpm | head -1)"
echo "Port 9000: $(lsof -i :9000 | tail -1)"
echo ""
echo "日誌: tail -50 /Users/accusys/momentry/log/php.error.log"
;;
rustdesk)
echo "=== RustDesk 診斷 ==="
echo "hbbs: $(pgrep -a hbbs | head -1)"
echo "hbbr: $(pgrep -a hbbr | head -1)"
echo "Port 21116: $(lsof -i :21116 | tail -1)"
echo "Port 21117: $(lsof -i :21117 | tail -1)"
;;
*)
echo "用法: $0 [service]"
echo ""
echo "可用服務:"
echo " all - 執行所有健康檢查"
echo " postgresql - PostgreSQL 診斷"
echo " redis - Redis 診斷"
echo " mariadb - MariaDB 診斷"
echo " n8n - n8n 診斷"
echo " ollama - Ollama 診斷"
echo " qdrant - Qdrant 診斷"
echo " caddy - Caddy 診斷"
echo " gitea - Gitea 診斷"
echo " sftpgo - SFTPGo 診斷"
echo " mongodb - MongoDB 診斷"
echo " php - PHP-FPM 診斷"
echo " rustdesk - RustDesk 診斷"
;;
esac
-3
View File
@@ -1,3 +0,0 @@
pub mod server;
pub use server::start_server;
-1302
View File
File diff suppressed because it is too large Load Diff
-162
View File
@@ -1,162 +0,0 @@
use anyhow::Result;
use redis::AsyncCommands;
use serde::{de::DeserializeOwned, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::core::config::{cache as cache_config, REDIS_KEY_PREFIX};
use crate::core::db::RedisClient;
pub struct RedisCache {
client: Arc<RwLock<RedisClient>>,
}
impl RedisCache {
pub fn new() -> Result<Self> {
let client = RedisClient::new()?;
Ok(Self {
client: Arc::new(RwLock::new(client)),
})
}
fn prefixed_key(&self, key: &str) -> String {
format!("{}cache:{}", REDIS_KEY_PREFIX.as_str(), key)
}
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let prefixed = self.prefixed_key(key);
let value: Option<String> = conn.get(&prefixed).await?;
match value {
Some(json) => {
let result = serde_json::from_str(&json)?;
Ok(Some(result))
}
None => Ok(None),
}
}
pub async fn set<T: Serialize>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let prefixed = self.prefixed_key(key);
let json = serde_json::to_string(value)?;
let _: String = conn.set_ex(&prefixed, json, ttl_secs).await?;
Ok(())
}
pub async fn delete(&self, key: &str) -> Result<bool> {
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let prefixed = self.prefixed_key(key);
let _: () = conn.del(&prefixed).await?;
Ok(true)
}
pub async fn exists(&self, key: &str) -> Result<bool> {
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let prefixed = self.prefixed_key(key);
let exists: bool = conn.exists(&prefixed).await?;
Ok(exists)
}
pub async fn invalidate_pattern(&self, pattern: &str) -> Result<u64> {
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let prefixed_pattern = self.prefixed_key(pattern);
let keys: Vec<String> = redis::cmd("KEYS")
.arg(&prefixed_pattern)
.query_async(&mut conn)
.await?;
let count = keys.len() as u64;
if !keys.is_empty() {
let _: () = conn.del(&keys).await?;
}
tracing::debug!("Invalidated {} keys matching pattern: {}", count, pattern);
Ok(count)
}
pub async fn get_or_fetch<F, Fut, T>(&self, key: &str, ttl_secs: u64, fetcher: F) -> Result<T>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<T>>,
T: DeserializeOwned + Serialize,
{
if let Some(cached) = self.get::<T>(key).await? {
tracing::debug!("Redis cache hit for key: {}", key);
return Ok(cached);
}
tracing::debug!("Redis cache miss for key: {}", key);
let value = fetcher().await?;
if let Err(e) = self.set(key, &value, ttl_secs).await {
tracing::warn!("Failed to cache value in Redis: {}", e);
}
Ok(value)
}
pub async fn get_health(&self) -> Result<Option<String>> {
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let key = self.prefixed_key("health");
let value: Option<String> = conn.get(&key).await?;
Ok(value)
}
pub async fn set_health(&self, status: &str) -> Result<()> {
let ttl = *cache_config::REDIS_CACHE_TTL_HEALTH;
let client = self.client.read().await;
let mut conn = client.get_conn_internal().await?;
let key = self.prefixed_key("health");
let _: String = conn.set_ex(&key, status, ttl).await?;
Ok(())
}
pub async fn get_video_meta(&self, uuid: &str) -> Result<Option<serde_json::Value>> {
self.get(uuid).await
}
pub async fn set_video_meta(&self, uuid: &str, value: &serde_json::Value) -> Result<()> {
let ttl = *cache_config::REDIS_CACHE_TTL_VIDEO_META;
self.set(uuid, value, ttl).await
}
pub async fn invalidate_video_meta(&self, uuid: &str) -> Result<bool> {
self.delete(uuid).await
}
pub async fn invalidate_videos_list(&self) -> Result<u64> {
self.invalidate_pattern("videos:*").await
}
}
impl Clone for RedisCache {
fn clone(&self) -> Self {
Self {
client: Arc::clone(&self.client),
}
}
}
impl Default for RedisCache {
fn default() -> Self {
Self::new().expect("Failed to create Redis cache")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prefixed_key() {
let cache = RedisCache::new().unwrap();
assert_eq!(cache.prefixed_key("test"), "momentry:cache:test");
assert_eq!(cache.prefixed_key("video:abc"), "momentry:cache:video:abc");
}
}
-5
View File
@@ -1,5 +0,0 @@
pub mod splitter;
pub mod types;
pub use splitter::{AsrSegment, ChunkSplitter};
pub use types::{Chunk, ChunkType};
-67
View File
@@ -1,67 +0,0 @@
use super::types::{Chunk, ChunkType};
use anyhow::Result;
pub struct ChunkSplitter {
time_based_duration: f64,
}
impl ChunkSplitter {
pub fn new(time_based_duration_seconds: f64) -> Self {
Self {
time_based_duration: time_based_duration_seconds,
}
}
pub fn split_time_based(&self, uuid: &str, duration: f64) -> Vec<Chunk> {
let mut chunks = Vec::new();
let mut index = 0;
let mut current_time = 0.0;
while current_time < duration {
let end_time = (current_time + self.time_based_duration).min(duration);
chunks.push(Chunk::new(
uuid.to_string(),
index,
ChunkType::TimeBased,
current_time,
end_time,
serde_json::json!({
"source": "time_based",
"duration": self.time_based_duration,
}),
));
current_time = end_time;
index += 1;
}
chunks
}
pub fn split_sentence(&self, uuid: &str, asr_segments: &[AsrSegment]) -> Vec<Chunk> {
let mut chunks = Vec::new();
for (index, segment) in asr_segments.iter().enumerate() {
chunks.push(Chunk::new(
uuid.to_string(),
index as u32,
ChunkType::Sentence,
segment.start,
segment.end,
serde_json::json!({
"text": segment.text,
"speaker_id": segment.speaker_id,
}),
));
}
chunks
}
}
#[derive(Debug, Clone)]
pub struct AsrSegment {
pub start: f64,
pub end: f64,
pub text: String,
pub speaker_id: Option<String>,
}
-52
View File
@@ -1,52 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ChunkType {
TimeBased,
Sentence,
Cut,
}
impl ChunkType {
pub fn as_str(&self) -> &'static str {
match self {
ChunkType::TimeBased => "time_based",
ChunkType::Sentence => "sentence",
ChunkType::Cut => "cut",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chunk {
pub uuid: String,
pub chunk_id: String,
pub chunk_index: u32,
pub chunk_type: ChunkType,
pub start_time: f64,
pub end_time: f64,
pub content: serde_json::Value,
}
impl Chunk {
pub fn new(
uuid: String,
chunk_index: u32,
chunk_type: ChunkType,
start_time: f64,
end_time: f64,
content: serde_json::Value,
) -> Self {
let chunk_id = format!("{}_{:04}", chunk_type.as_str(), chunk_index);
Self {
uuid,
chunk_id: chunk_id.clone(),
chunk_index,
chunk_type,
start_time,
end_time,
content,
}
}
}
-147
View File
@@ -1,147 +0,0 @@
use once_cell::sync::Lazy;
use std::env;
pub static DATABASE_URL: Lazy<String> = Lazy::new(|| {
env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://accusys@localhost:5432/momentry".to_string())
});
pub static MONGODB_URL: Lazy<String> = Lazy::new(|| {
env::var("MONGODB_URL").unwrap_or_else(|_| "mongodb://localhost:27017".to_string())
});
pub static REDIS_URL: Lazy<String> = Lazy::new(|| {
env::var("REDIS_URL").unwrap_or_else(|_| {
let password = env::var("REDIS_PASSWORD").unwrap_or_else(|_| "accusys".to_string());
// Format: redis://[:password]@host:port (use default user)
format!("redis://:{}@localhost:6379", password)
})
});
pub static REDIS_PASSWORD: Lazy<String> =
Lazy::new(|| env::var("REDIS_PASSWORD").unwrap_or_else(|_| "accusys".to_string()));
pub static OUTPUT_DIR: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_OUTPUT_DIR").unwrap_or_else(|_| "/Users/accusys/momentry/output".to_string())
});
pub static BACKUP_DIR: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_BACKUP_DIR")
.unwrap_or_else(|_| "/Users/accusys/momentry/backup/momentry".to_string())
});
pub static PYTHON_PATH: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_PYTHON_PATH").unwrap_or_else(|_| "/opt/homebrew/bin/python3.11".to_string())
});
pub static SCRIPTS_DIR: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_SCRIPTS_DIR")
.unwrap_or_else(|_| "/Users/accusys/momentry_core_0.1/scripts".to_string())
});
pub static LOG_LEVEL: Lazy<String> = Lazy::new(|| {
env::var("RUST_LOG")
.or_else(|_| env::var("MOMENTRY_LOG_LEVEL"))
.unwrap_or_else(|_| "info".to_string())
});
pub static MEDIA_BASE_URL: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_MEDIA_BASE_URL")
.unwrap_or_else(|_| "https://wp.momentry.ddns.net".to_string())
});
pub static SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("MOMENTRY_SERVER_PORT")
.unwrap_or_else(|_| "3002".to_string())
.parse()
.unwrap_or(3002)
});
pub static REDIS_KEY_PREFIX: Lazy<String> =
Lazy::new(|| env::var("MOMENTRY_REDIS_PREFIX").unwrap_or_else(|_| "momentry:".to_string()));
/// User data root path (sftpgo data directory)
/// This is the parent directory containing user directories like ./demo/, ./warren/, ./momentry/
/// Example: /Users/accusys/momentry/var/sftpgo/data
pub static USER_DATA_ROOT: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_USER_DATA_ROOT")
.unwrap_or_else(|_| "/Users/accusys/momentry/var/sftpgo/data".to_string())
});
pub mod processor {
use super::*;
pub static ASR_TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
env::var("MOMENTRY_ASR_TIMEOUT")
.unwrap_or_else(|_| "3600".to_string())
.parse()
.unwrap_or(3600)
});
pub static CUT_TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
env::var("MOMENTRY_CUT_TIMEOUT")
.unwrap_or_else(|_| "3600".to_string())
.parse()
.unwrap_or(3600)
});
pub static DEFAULT_TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
env::var("MOMENTRY_DEFAULT_TIMEOUT")
.unwrap_or_else(|_| "7200".to_string())
.parse()
.unwrap_or(7200)
});
}
pub mod cache {
use super::*;
pub static MONGODB_CACHE_ENABLED: Lazy<bool> = Lazy::new(|| {
env::var("MONGODB_CACHE_ENABLED")
.unwrap_or_else(|_| "true".to_string())
.parse()
.unwrap_or(true)
});
pub static MONGODB_CACHE_TTL_VIDEOS: Lazy<u64> = Lazy::new(|| {
env::var("MONGODB_CACHE_TTL_VIDEOS")
.unwrap_or_else(|_| "300".to_string())
.parse()
.unwrap_or(300)
});
pub static MONGODB_CACHE_TTL_SEARCH: Lazy<u64> = Lazy::new(|| {
env::var("MONGODB_CACHE_TTL_SEARCH")
.unwrap_or_else(|_| "300".to_string())
.parse()
.unwrap_or(300)
});
pub static MONGODB_CACHE_TTL_HYBRID_SEARCH: Lazy<u64> = Lazy::new(|| {
env::var("MONGODB_CACHE_TTL_HYBRID_SEARCH")
.unwrap_or_else(|_| "600".to_string())
.parse()
.unwrap_or(600)
});
pub static MONGODB_CACHE_TTL_VIDEO_META: Lazy<u64> = Lazy::new(|| {
env::var("MONGODB_CACHE_TTL_VIDEO_META")
.unwrap_or_else(|_| "3600".to_string())
.parse()
.unwrap_or(3600)
});
pub static REDIS_CACHE_TTL_HEALTH: Lazy<u64> = Lazy::new(|| {
env::var("REDIS_CACHE_TTL_HEALTH")
.unwrap_or_else(|_| "30".to_string())
.parse()
.unwrap_or(30)
});
pub static REDIS_CACHE_TTL_VIDEO_META: Lazy<u64> = Lazy::new(|| {
env::var("REDIS_CACHE_TTL_VIDEO_META")
.unwrap_or_else(|_| "3600".to_string())
.parse()
.unwrap_or(3600)
});
}
-40
View File
@@ -1,40 +0,0 @@
use anyhow::Result;
use async_trait::async_trait;
use crate::core::chunk::Chunk;
#[derive(Debug, Clone)]
pub struct SearchResult {
pub chunk_id: String,
pub score: f32,
}
#[async_trait]
pub trait Database: Send + Sync {
async fn init() -> Result<Self>
where
Self: Sized;
}
#[async_trait]
pub trait ChunkStore: Send + Sync {
async fn store_chunk(&self, chunk: &Chunk) -> Result<()>;
async fn get_chunks_by_uuid(&self, uuid: &str) -> Result<Vec<Chunk>>;
async fn get_all_chunks(&self) -> Result<Vec<Chunk>>;
}
#[async_trait]
pub trait VectorStore: Send + Sync {
async fn store_vector(&self, chunk_id: &str, vector: &[f32]) -> Result<()>;
async fn search(&self, query_vector: &[f32], limit: usize) -> Result<Vec<SearchResult>>;
}
pub mod mongodb_db;
pub mod postgres_db;
pub mod qdrant_db;
pub mod redis_db;
pub use mongodb_db::MongoDb;
pub use postgres_db::{PostgresDb, VideoRecord};
pub use qdrant_db::QdrantDb;
pub use redis_db::RedisDb;
-53
View File
@@ -1,53 +0,0 @@
use anyhow::Result;
use async_trait::async_trait;
use std::sync::Arc;
use tokio::sync::RwLock;
use super::Database;
pub struct MongoDb {
cache: Arc<RwLock<MongoCache>>,
}
#[derive(Debug, Default)]
pub struct MongoCache {
documents: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct VideoDocument {
pub uuid: String,
pub file_path: String,
pub file_name: String,
pub probe: serde_json::Value,
pub asr: Option<serde_json::Value>,
pub asrx: Option<serde_json::Value>,
pub ocr: Option<serde_json::Value>,
pub yolo: Option<serde_json::Value>,
pub face: Option<serde_json::Value>,
pub pose: Option<serde_json::Value>,
pub created_at: String,
pub updated_at: String,
}
impl MongoDb {
pub async fn store_video(&self, _doc: &VideoDocument) -> Result<()> {
// TODO: Implement MongoDB client
Ok(())
}
pub async fn get_video(&self, _uuid: &str) -> Result<Option<VideoDocument>> {
// TODO: Implement MongoDB client
Ok(None)
}
}
#[async_trait]
impl Database for MongoDb {
async fn init() -> Result<Self> {
// TODO: Initialize MongoDB client
Ok(Self {
cache: Arc::new(RwLock::new(MongoCache::default())),
})
}
}
File diff suppressed because it is too large Load Diff
-88
View File
@@ -1,88 +0,0 @@
use anyhow::Result;
use async_trait::async_trait;
use std::sync::Arc;
use tokio::sync::RwLock;
use super::{Database, SearchResult, VectorStore};
pub struct QdrantDb {
collection_name: String,
cache: Arc<RwLock<QdrantCache>>,
}
#[derive(Debug, Default)]
pub struct QdrantCache {
vectors: std::collections::HashMap<String, Vec<f32>>,
}
impl QdrantDb {
pub async fn init_collection(&self) -> Result<()> {
// TODO: Implement actual Qdrant client
// This is a placeholder
Ok(())
}
pub async fn upsert_vector(&self, chunk_id: &str, vector: &[f32]) -> Result<()> {
let mut cache = self.cache.write().await;
cache.vectors.insert(chunk_id.to_string(), vector.to_vec());
Ok(())
}
}
#[async_trait]
impl Database for QdrantDb {
async fn init() -> Result<Self> {
let collection_name =
std::env::var("QDRANT_COLLECTION").unwrap_or_else(|_| "momentry_chunks".to_string());
let db = Self {
collection_name,
cache: Arc::new(RwLock::new(QdrantCache::default())),
};
db.init_collection().await?;
Ok(db)
}
}
#[async_trait]
impl VectorStore for QdrantDb {
async fn store_vector(&self, chunk_id: &str, vector: &[f32]) -> Result<()> {
self.upsert_vector(chunk_id, vector).await
}
async fn search(&self, query_vector: &[f32], limit: usize) -> Result<Vec<SearchResult>> {
// Simple cosine similarity search (placeholder)
let cache = self.cache.read().await;
let mut results: Vec<SearchResult> = Vec::new();
for (chunk_id, vector) in &cache.vectors {
let similarity = cosine_similarity(query_vector, vector);
results.push(SearchResult {
chunk_id: chunk_id.clone(),
score: similarity,
});
}
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
results.truncate(limit);
Ok(results)
}
}
fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() || a.is_empty() {
return 0.0;
}
let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot_product / (norm_a * norm_b)
}
-556
View File
@@ -1,556 +0,0 @@
use anyhow::{Context, Result};
use futures_util::stream::StreamExt;
use redis::aio::MultiplexedConnection;
use redis::{AsyncCommands, Client};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::core::config::REDIS_KEY_PREFIX;
pub struct RedisClient {
client: Client,
state: Arc<RwLock<RedisState>>,
}
#[derive(Debug, Clone, Default)]
pub struct RedisState {
pub connected: bool,
}
impl RedisClient {
pub fn new() -> Result<Self> {
let client = Client::open(crate::core::config::REDIS_URL.as_str())
.context("Failed to connect to Redis")?;
Ok(Self {
client,
state: Arc::new(RwLock::new(RedisState { connected: true })),
})
}
pub async fn is_connected(&self) -> bool {
self.state.read().await.connected
}
pub async fn get_conn(&self) -> Result<MultiplexedConnection> {
self.get_conn_internal().await
}
pub async fn get_conn_internal(&self) -> Result<MultiplexedConnection> {
self.client
.get_multiplexed_async_connection()
.await
.context("Failed to get Redis connection")
}
pub async fn get_job_status(&self, uuid: &str) -> Result<Option<JobStatus>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}job:{}", prefix, uuid);
let status: Option<String> = conn.hget(&key, "status").await?;
if status.is_none() {
return Ok(None);
}
let current_processor: String = conn.hget(&key, "current_processor").await?;
let progress_total: i32 = conn.hget(&key, "progress_total").await?;
let progress_current: i32 = conn.hget(&key, "progress_current").await?;
let started_at: String = conn.hget(&key, "started_at").await?;
let updated_at: String = conn.hget(&key, "updated_at").await?;
let error_count: i32 = conn.hget(&key, "error_count").await?;
let last_error: String = conn.hget(&key, "last_error").await?;
Ok(Some(JobStatus {
status: status.unwrap_or_default(),
current_processor,
progress_total,
progress_current,
started_at,
updated_at,
error_count,
last_error,
}))
}
pub async fn set_processor_status(
&self,
uuid: &str,
processor: &str,
status: &ProcessorStatus,
) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}job:{}:processor:{}", prefix, uuid, processor);
let _: Option<String> = conn
.hset_multiple(
&key,
&[
("status", status.status.as_str()),
("progress", status.progress.to_string().as_str()),
("current", status.current.to_string().as_str()),
("total", status.total.to_string().as_str()),
("started_at", status.started_at.as_str()),
("updated_at", status.updated_at.as_str()),
("message", status.message.as_str()),
],
)
.await?;
let _: bool = conn.expire(&key, 86400).await?;
Ok(())
}
pub async fn get_processor_status(
&self,
uuid: &str,
processor: &str,
) -> Result<Option<ProcessorStatus>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}job:{}:processor:{}", prefix, uuid, processor);
let status: Option<String> = conn.hget(&key, "status").await?;
if status.is_none() {
return Ok(None);
}
let progress: i32 = conn.hget(&key, "progress").await?;
let current: i32 = conn.hget(&key, "current").await?;
let total: i32 = conn.hget(&key, "total").await?;
let started_at: String = conn.hget(&key, "started_at").await?;
let updated_at: String = conn.hget(&key, "updated_at").await?;
let message: String = conn.hget(&key, "message").await?;
Ok(Some(ProcessorStatus {
status: status.unwrap_or_default(),
progress,
current,
total,
started_at,
updated_at,
message,
}))
}
pub async fn publish_progress(&self, uuid: &str, message: &ProgressMessage) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let channel = format!("{}progress:{}", prefix, uuid);
let json = serde_json::to_string(message)?;
let _: usize = conn.publish(&channel, json).await?;
Ok(())
}
pub async fn subscribe_progress(&self, uuid: &str) -> Result<redis::aio::PubSub> {
let mut pubsub = self.client.get_async_pubsub().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let channel = format!("{}progress:{}", prefix, uuid);
pubsub.subscribe(channel).await?;
Ok(pubsub)
}
pub async fn subscribe_and_callback<F>(&self, uuid: &str, mut callback: F) -> Result<()>
where
F: FnMut(ProgressMessage) + Send + 'static,
{
let mut pubsub = self.subscribe_progress(uuid).await?;
let mut stream = pubsub.on_message();
while let Some(msg) = stream.next().await {
let payload: String = msg.get_payload().map_err(|e| anyhow::anyhow!("{}", e))?;
if let Ok(progress_msg) = serde_json::from_str::<ProgressMessage>(&payload) {
callback(progress_msg);
}
}
Ok(())
}
pub async fn add_to_active_jobs(&self, uuid: &str) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let _: usize = conn.sadd(format!("{}jobs:active", prefix), uuid).await?;
Ok(())
}
pub async fn move_to_completed_jobs(&self, uuid: &str) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let _: bool = conn
.smove(
format!("{}jobs:active", prefix),
format!("{}jobs:completed", prefix),
uuid,
)
.await?;
Ok(())
}
pub async fn move_to_failed_jobs(&self, uuid: &str) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let _: bool = conn
.smove(
format!("{}jobs:active", prefix),
format!("{}jobs:failed", prefix),
uuid,
)
.await?;
Ok(())
}
pub async fn get_active_jobs(&self) -> Result<Vec<String>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let jobs: Vec<String> = conn.smembers(format!("{}jobs:active", prefix)).await?;
Ok(jobs)
}
pub async fn set_health(&self, status: &str) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let _: String = conn
.set_ex(format!("{}health:momentry_core", prefix), status, 60)
.await?;
Ok(())
}
pub async fn get_health(&self) -> Result<Option<String>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let health: Option<String> = conn.get(format!("{}health:momentry_core", prefix)).await?;
Ok(health)
}
pub async fn sync_monitor_job(&self, job: &MonitorJobRedis) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}monitor:job:{}", prefix, job.uuid);
let _: Option<String> = conn
.hset_multiple(
&key,
&[
("uuid", job.uuid.as_str()),
("status", job.status.as_str()),
("current_processor", job.current_processor.as_str()),
("progress_total", job.progress_total.to_string().as_str()),
(
"progress_current",
job.progress_current.to_string().as_str(),
),
("error_count", job.error_count.to_string().as_str()),
("started_at", job.started_at.as_str()),
("updated_at", job.updated_at.as_str()),
],
)
.await?;
let _: bool = conn.expire(&key, 86400).await?;
Ok(())
}
pub async fn publish_job_error(&self, uuid: &str, error: &str) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let channel = format!("{}errors:{}", prefix, uuid);
let error_msg = JobErrorMessage {
uuid: uuid.to_string(),
error: error.to_string(),
timestamp: chrono::Utc::now().timestamp(),
};
let json = serde_json::to_string(&error_msg)?;
let _: usize = conn.publish(&channel, json).await?;
Ok(())
}
pub async fn publish_anomaly_alert(&self, alert: &AnomalyAlertMessage) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let channel = format!("{}anomaly:alerts", prefix);
let json = serde_json::to_string(alert)?;
let _: usize = conn.publish(&channel, json).await?;
let key = format!("{}anomaly:key:{}", prefix, alert.key_id);
let alert_data = serde_json::json!({
"key_id": alert.key_id,
"anomaly_type": alert.anomaly_type,
"severity": alert.severity,
"timestamp": alert.timestamp,
"message": alert.message,
});
let _: Option<String> = conn
.hset(&key, "latest", &serde_json::to_string(&alert_data)?)
.await?;
let _: bool = conn.expire(&key, 86400).await?;
Ok(())
}
pub async fn subscribe_anomaly_alerts(&self) -> Result<redis::aio::PubSub> {
let mut pubsub = self.client.get_async_pubsub().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
pubsub
.subscribe(format!("{}anomaly:alerts", prefix))
.await?;
Ok(pubsub)
}
pub async fn get_latest_anomaly(&self, key_id: &str) -> Result<Option<AnomalyAlertMessage>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}anomaly:key:{}", prefix, key_id);
let latest: Option<String> = conn.hget(&key, "latest").await?;
if let Some(json) = latest {
let alert: AnomalyAlertMessage = serde_json::from_str(&json)?;
Ok(Some(alert))
} else {
Ok(None)
}
}
pub async fn subscribe_job_errors(&self, uuid: &str) -> Result<redis::aio::PubSub> {
let mut pubsub = self.client.get_async_pubsub().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let channel = format!("{}errors:{}", prefix, uuid);
pubsub.subscribe(channel).await?;
Ok(pubsub)
}
pub async fn update_worker_job_status(
&self,
uuid: &str,
job_id: i32,
status: &str,
current_processor: Option<&str>,
progress: i32,
total: i32,
) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}worker:job:{}", prefix, uuid);
let _: Option<String> = conn
.hset_multiple(
&key,
&[
("job_id", job_id.to_string().as_str()),
("status", status),
("current_processor", current_processor.unwrap_or("")),
("progress_current", progress.to_string().as_str()),
("progress_total", total.to_string().as_str()),
("updated_at", &chrono::Utc::now().to_rfc3339()),
],
)
.await?;
let _: bool = conn.expire(&key, 86400).await?;
Ok(())
}
pub async fn update_worker_processor_status(
&self,
uuid: &str,
processor: &str,
status: &str,
error: Option<&str>,
) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}worker:job:{}:processor:{}", prefix, uuid, processor);
let now = chrono::Utc::now().to_rfc3339();
let mut fields: Vec<(&str, &str)> = vec![("status", status), ("updated_at", &now)];
if let Some(err) = error {
fields.push(("error", err));
}
let _: Option<String> = conn.hset_multiple(&key, &fields).await?;
let _: bool = conn.expire(&key, 86400).await?;
Ok(())
}
pub async fn get_worker_job_status(&self, uuid: &str) -> Result<Option<WorkerJobStatus>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}worker:job:{}", prefix, uuid);
let exists: bool = conn.exists(&key).await?;
if !exists {
return Ok(None);
}
let status: String = conn.hget(&key, "status").await?;
let job_id: i32 = conn.hget(&key, "job_id").await?;
let current_processor: String = conn.hget(&key, "current_processor").await?;
let progress_current: i32 = conn.hget(&key, "progress_current").await?;
let progress_total: i32 = conn.hget(&key, "progress_total").await?;
let updated_at: String = conn.hget(&key, "updated_at").await?;
Ok(Some(WorkerJobStatus {
uuid: uuid.to_string(),
job_id,
status,
current_processor,
progress_current,
progress_total,
updated_at,
}))
}
pub async fn delete_worker_job(&self, uuid: &str) -> Result<()> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}worker:job:{}", prefix, uuid);
let _: i32 = conn.del(&key).await?;
let processor_types = ["asr", "cut", "yolo", "ocr", "face", "pose", "asrx"];
for ptype in processor_types {
let proc_key = format!("{}worker:job:{}:processor:{}", prefix, uuid, ptype);
let _: i32 = conn.del(&proc_key).await?;
}
Ok(())
}
pub async fn get_all_worker_jobs(&self) -> Result<Vec<WorkerJobInfo>> {
let mut conn = self.get_conn_internal().await?;
let prefix = REDIS_KEY_PREFIX.as_str();
let keys: Vec<String> = conn.keys(format!("{}worker:job:*", prefix)).await?;
let mut jobs = Vec::new();
for key in keys {
let uuid = key.replace(&format!("{}worker:job:", prefix), "");
if let Some(status) = self.get_worker_job_status(&uuid).await? {
jobs.push(WorkerJobInfo {
uuid,
job_id: status.job_id,
status: status.status,
progress_current: status.progress_current,
progress_total: status.progress_total,
});
}
}
Ok(jobs)
}
}
impl Default for RedisClient {
fn default() -> Self {
Self::new().expect("Failed to create Redis client")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobStatus {
pub status: String,
pub current_processor: String,
pub progress_total: i32,
pub progress_current: i32,
pub started_at: String,
pub updated_at: String,
pub error_count: i32,
pub last_error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessorStatus {
pub status: String,
pub progress: i32,
pub current: i32,
pub total: i32,
pub started_at: String,
pub updated_at: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressMessage {
#[serde(rename = "type")]
pub msg_type: String,
pub processor: String,
pub uuid: String,
pub timestamp: i64,
pub data: ProgressData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressData {
pub message: Option<String>,
pub current: Option<i32>,
pub total: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorJobRedis {
pub uuid: String,
pub status: String,
pub current_processor: String,
pub progress_total: i32,
pub progress_current: i32,
pub error_count: i32,
pub started_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobErrorMessage {
pub uuid: String,
pub error: String,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyAlertMessage {
pub key_id: String,
pub anomaly_type: String,
pub severity: String,
pub ip_address: Option<String>,
pub request_count: Option<i32>,
pub error_rate: Option<f64>,
pub unique_ips: Option<i32>,
pub timestamp: i64,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerJobStatus {
pub uuid: String,
pub job_id: i32,
pub status: String,
pub current_processor: String,
pub progress_current: i32,
pub progress_total: i32,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerJobInfo {
pub uuid: String,
pub job_id: i32,
pub status: String,
pub progress_current: i32,
pub progress_total: i32,
}
-65
View File
@@ -1,65 +0,0 @@
use anyhow::Result;
use async_trait::async_trait;
use std::sync::Arc;
use tokio::sync::RwLock;
use super::Database;
pub struct RedisDb {
state: Arc<RwLock<RedisState>>,
}
#[derive(Debug, Clone, Default)]
pub struct RedisState {
pub processing: Vec<String>,
pub completed: Vec<String>,
pub failed: Vec<String>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Job {
pub id: String,
pub uuid: String,
pub job_type: String,
pub status: String,
pub progress: f32,
pub created_at: String,
pub updated_at: String,
}
impl RedisDb {
pub async fn push_job(&self, _job: &Job) -> Result<()> {
// TODO: Implement Redis client
Ok(())
}
pub async fn get_pending_jobs(&self) -> Result<Vec<Job>> {
// TODO: Implement Redis client
Ok(vec![])
}
pub async fn update_job_status(
&self,
_job_id: &str,
_status: &str,
_progress: f32,
) -> Result<()> {
// TODO: Implement Redis client
Ok(())
}
pub async fn publish_event(&self, _channel: &str, _message: &str) -> Result<()> {
// TODO: Implement Redis Pub/Sub
Ok(())
}
}
#[async_trait]
impl Database for RedisDb {
async fn init() -> Result<Self> {
// TODO: Initialize Redis client
Ok(Self {
state: Arc::new(RwLock::new(RedisState::default())),
})
}
}
-66
View File
@@ -1,66 +0,0 @@
use anyhow::Result;
pub struct Embedder {
model_path: String,
}
impl Embedder {
pub fn new(model_path: String) -> Self {
Self { model_path }
}
pub async fn embed_text(&self, text: &str) -> Result<Vec<f32>> {
// TODO: Implement comic-embed-text model loading and inference
// This is a placeholder that generates a random 768-dimensional vector
//
// Implementation would use:
// - candle (Rust ML framework) or
// - ort (ONNX Runtime) to run the model
//
// Example with ort:
// let session = Session::builder()?
// .with_execution_providers([CPUExecutionProvider::default().build()])?
// .with_model_from_file(&self.model_path)?;
//
// // Preprocess text to tensor
// let input = preprocess_text(text);
//
// // Run inference
// let output = session.run(vec![input])?;
//
// // Extract embeddings
// let embedding = output[0].view()[..768].to_vec();
let dim = 768;
let mut embedding = vec![0.0f32; dim];
// Simple hash-based embedding for now
let hash = self.hash_text(text);
for i in 0..dim {
embedding[i] = ((hash >> i) & 1) as f32;
}
// Normalize
let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for v in &mut embedding {
*v /= norm;
}
}
Ok(embedding)
}
pub async fn embed_chunk_content(&self, chunk: &crate::core::chunk::Chunk) -> Result<Vec<f32>> {
let text = serde_json::to_string(&chunk.content)?;
self.embed_text(&text).await
}
fn hash_text(&self, text: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
text.hash(&mut hasher);
hasher.finish()
}
}
-3
View File
@@ -1,3 +0,0 @@
pub mod comic_embed;
pub use comic_embed::Embedder;
-8
View File
@@ -1,8 +0,0 @@
pub mod chunk;
pub mod db;
pub mod embedding;
pub mod overlay;
pub mod probe;
pub mod processor;
pub mod storage;
pub mod thumbnail;

Some files were not shown because too many files have changed in this diff Show More