Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83ae05014f | |||
| 171c36acce | |||
| 02c63892b7 | |||
| 93a9f8c349 | |||
| 89fbfd6ad9 | |||
| 3edaf01f6c | |||
| 786381ac67 | |||
| 2d787b7806 | |||
| fe24906791 | |||
| 1d1c60366e | |||
| bfc4317b88 | |||
| 26f73ab620 | |||
| 47b9c2f750 | |||
| e66b9501bd | |||
| 9bbfaa1d06 | |||
| ea2bbb9fd9 | |||
| de14bd6afa |
@@ -0,0 +1,40 @@
|
||||
# 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
|
||||
@@ -0,0 +1,60 @@
|
||||
# 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
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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
@@ -0,0 +1,40 @@
|
||||
# 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
|
||||
*~
|
||||
@@ -0,0 +1,413 @@
|
||||
# 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
File diff suppressed because it is too large
Load Diff
+96
@@ -0,0 +1,96 @@
|
||||
[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"
|
||||
@@ -0,0 +1,443 @@
|
||||
# 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 | 新增服務添加規範 (第九章)、模板文件 |
|
||||
@@ -1,3 +0,0 @@
|
||||
# momentry_core
|
||||
|
||||
Digital asset management system with video analysis and RAG - Production version with API Key authentication
|
||||
@@ -0,0 +1,105 @@
|
||||
# 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 密碼
|
||||
- [ ] 配置目錄路徑
|
||||
- [ ] 確認日誌級別
|
||||
@@ -0,0 +1,217 @@
|
||||
# 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 使用範例
|
||||
@@ -0,0 +1,450 @@
|
||||
# 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 "=== 檢查完成 ==="
|
||||
```
|
||||
@@ -0,0 +1,586 @@
|
||||
# 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) - 開發規範
|
||||
@@ -0,0 +1,424 @@
|
||||
# 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 Keys(UUID 必要)**:
|
||||
- job:{uuid}, progress:{uuid}, metrics:{uuid}
|
||||
|
||||
**Per-Processor Keys(UUID + 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>
|
||||
```
|
||||
@@ -0,0 +1,451 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,394 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,380 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,367 @@
|
||||
# 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 (共用 - 不要刪除!)
|
||||
@@ -0,0 +1,466 @@
|
||||
# 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
|
||||
@@ -0,0 +1,359 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,379 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,372 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,456 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,367 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,284 @@
|
||||
# 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 添加服務器公式
|
||||
@@ -0,0 +1,360 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,682 @@
|
||||
# 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 設計原則
|
||||
|
||||
```
|
||||
每個模組獨立處理:
|
||||
- 成功 → 產出完整 .json,status=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` | 跳過(如檔案已存在) |
|
||||
@@ -0,0 +1,503 @@
|
||||
# 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) - 監控配置
|
||||
@@ -0,0 +1,674 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,283 @@
|
||||
# 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 Keys(UUID 必要)
|
||||
|
||||
- 每個視頻獨立處理狀態
|
||||
- 即時進度追蹤
|
||||
|
||||
### 3.3 Per-Processor Keys(UUID + 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
@@ -0,0 +1,433 @@
|
||||
# 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 監控腳本
|
||||
@@ -0,0 +1,376 @@
|
||||
# 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
@@ -0,0 +1,494 @@
|
||||
# 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 監控腳本
|
||||
@@ -0,0 +1,931 @@
|
||||
# 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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,659 @@
|
||||
# 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 | 新增服務備份作業、服務完整刪除作業 |
|
||||
@@ -0,0 +1,233 @@
|
||||
# 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 說明 |
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/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"
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/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)
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
# 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
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,102 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/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)"
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,366 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,388 @@
|
||||
# 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/`
|
||||
@@ -0,0 +1,503 @@
|
||||
# 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
|
||||
Executable
+362
@@ -0,0 +1,362 @@
|
||||
#!/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 "$@"
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/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 "========================================"
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/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"
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/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"
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,492 @@
|
||||
-- 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);
|
||||
Executable
+175
@@ -0,0 +1,175 @@
|
||||
#!/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"
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/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"
|
||||
Executable
+370
@@ -0,0 +1,370 @@
|
||||
#!/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"
|
||||
Executable
+270
@@ -0,0 +1,270 @@
|
||||
#!/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
|
||||
Executable
+281
@@ -0,0 +1,281 @@
|
||||
#!/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
|
||||
Executable
+446
@@ -0,0 +1,446 @@
|
||||
#!/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
|
||||
Executable
+193
@@ -0,0 +1,193 @@
|
||||
#!/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
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
#!/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"
|
||||
Executable
+265
@@ -0,0 +1,265 @@
|
||||
#!/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
|
||||
@@ -0,0 +1 @@
|
||||
faster-whisper>=1.0.0
|
||||
@@ -0,0 +1,3 @@
|
||||
edition = "2021"
|
||||
max_width = 100
|
||||
tab_spaces = 4
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/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])
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/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 "=========================================="
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/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)"
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/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()
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod server;
|
||||
|
||||
pub use server::start_server;
|
||||
+1302
File diff suppressed because it is too large
Load Diff
Vendored
+162
@@ -0,0 +1,162 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod splitter;
|
||||
pub mod types;
|
||||
|
||||
pub use splitter::{AsrSegment, ChunkSplitter};
|
||||
pub use types::{Chunk, ChunkType};
|
||||
@@ -0,0 +1,67 @@
|
||||
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>,
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
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)
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
@@ -0,0 +1,53 @@
|
||||
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
@@ -0,0 +1,88 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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())),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod comic_embed;
|
||||
|
||||
pub use comic_embed::Embedder;
|
||||
@@ -0,0 +1,8 @@
|
||||
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
Reference in New Issue
Block a user