feat: add momentry_playground binary for development
- Add separate momentry_playground binary with distinct configuration - Production (momentry): Port 3002, Redis prefix 'momentry:' - Development (momentry_playground): Port 3003, Redis prefix 'momentry_dev:' - Add SERVER_PORT and REDIS_KEY_PREFIX config via environment variables - Replace all hardcoded Redis key prefixes with configurable values - Create .env.development for playground environment settings - Update .env with production defaults - Add dotenv dependency for environment file loading Configuration isolation allows running both binaries simultaneously without port conflicts or Redis key collisions.
This commit is contained in:
39
.env
Normal file
39
.env
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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 (not installed, comment out for now)
|
||||
# QDRANT_URL=http://localhost:6333
|
||||
# QDRANT_COLLECTION=momentry_chunks
|
||||
|
||||
# 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
|
||||
55
.env.development
Normal file
55
.env.development
Normal file
@@ -0,0 +1,55 @@
|
||||
# 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
|
||||
|
||||
# Paths
|
||||
MOMENTRY_OUTPUT_DIR=/Users/accusys/momentry/output_dev
|
||||
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry_dev
|
||||
|
||||
# Python (for processing scripts)
|
||||
MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11
|
||||
MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core_0.1/scripts
|
||||
|
||||
# Logging
|
||||
RUST_LOG=debug
|
||||
MOMENTRY_LOG_LEVEL=debug
|
||||
|
||||
# Media
|
||||
MOMENTRY_MEDIA_BASE_URL=https://wp.momentry.ddns.net
|
||||
|
||||
# Processor Timeouts
|
||||
MOMENTRY_ASR_TIMEOUT=3600
|
||||
MOMENTRY_CUT_TIMEOUT=3600
|
||||
MOMENTRY_DEFAULT_TIMEOUT=7200
|
||||
|
||||
# Cache Settings
|
||||
MONGODB_CACHE_ENABLED=true
|
||||
MONGODB_CACHE_TTL_VIDEOS=300
|
||||
MONGODB_CACHE_TTL_SEARCH=300
|
||||
MONGODB_CACHE_TTL_HYBRID_SEARCH=600
|
||||
MONGODB_CACHE_TTL_VIDEO_META=3600
|
||||
REDIS_CACHE_TTL_HEALTH=30
|
||||
REDIS_CACHE_TTL_VIDEO_META=3600
|
||||
70
.env.example
Normal file
70
.env.example
Normal file
@@ -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
|
||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -1,11 +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
|
||||
.DS_Store
|
||||
*.log
|
||||
|
||||
# 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
|
||||
*~
|
||||
|
||||
255
AGENTS.md
255
AGENTS.md
@@ -9,13 +9,29 @@ Rust-based digital asset management system with video analysis and RAG capabilit
|
||||
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 3000
|
||||
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
|
||||
@@ -133,16 +149,20 @@ 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
|
||||
│ ├── 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
|
||||
```
|
||||
|
||||
@@ -151,18 +171,243 @@ src/
|
||||
- **Error handling**: `anyhow`, `thiserror`
|
||||
- **Async**: `tokio` (full features), `async-trait`
|
||||
- **CLI**: `clap` (derive)
|
||||
- **Serialization**: `serde`, `serde_json`
|
||||
- **Database**: `sqlx`, `mongodb`, `redis`, `qdrant-client`
|
||||
- **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
|
||||
|
||||
- No unit tests exist - add tests when implementing features
|
||||
- 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 依賴版本
|
||||
```
|
||||
|
||||
1123
Cargo.lock
generated
1123
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
57
Cargo.toml
57
Cargo.toml
@@ -12,48 +12,85 @@ 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 = "0.25", features = ["tokio-comp"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "json"] }
|
||||
mongodb = { version = "2", features = ["tokio-sync"] }
|
||||
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"
|
||||
|
||||
# Video/Audio
|
||||
sdl2 = "0.38"
|
||||
# Logging
|
||||
env_logger = "0.11"
|
||||
|
||||
# Configuration
|
||||
dotenv = "0.15"
|
||||
# Hash
|
||||
md5 = "0.7"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
dev = []
|
||||
# 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"
|
||||
|
||||
105
config/README.md
Normal file
105
config/README.md
Normal file
@@ -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 密碼
|
||||
- [ ] 配置目錄路徑
|
||||
- [ ] 確認日誌級別
|
||||
376
docs/PLAYGROUND_BINARY_IMPLEMENTATION.md
Normal file
376
docs/PLAYGROUND_BINARY_IMPLEMENTATION.md
Normal file
@@ -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 |
|
||||
446
docs/SERVICES.md
446
docs/SERVICES.md
@@ -1,31 +1,167 @@
|
||||
# Momentry 系統服務安裝與管理指南
|
||||
|
||||
| 項目 | 內容 |
|
||||
|------|------|
|
||||
| 建立者 | Warren |
|
||||
| 建立時間 | 2026-03-18 |
|
||||
| 更新時間 | 2026-03-24 |
|
||||
| 文件版本 | V1.1 |
|
||||
|
||||
---
|
||||
|
||||
## 版本歷史
|
||||
|
||||
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|
||||
|------|------|------|--------|-----------|
|
||||
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
|
||||
| V1.1 | 2026-03-24 | 更新所有服務 plist 狀態,統一使用自定義 plist | OpenCode | OpenCode / big-pickle |
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本文檔記錄 momentry 系統所需的所有服務,包括安裝步驟、健康檢查和管理命令。
|
||||
|
||||
**重要**: 請勿使用 `brew services` 命令管理服務,否則可能導致 .plist 檔案還原為預設狀態,造成系統異常。請使用 `launchctl` 命令進行管理。
|
||||
|
||||
**2026-03-24 更新**: 所有服務已統一使用自定義 plist,存在於 `/Library/LaunchDaemons/` 目錄。Reboot 後會自動啟動。
|
||||
|
||||
---
|
||||
|
||||
## 服務清單
|
||||
|
||||
| 服務名稱 | 安裝方式 | 用途 | 狀態 |
|
||||
|----------|----------|------|-------|
|
||||
| PostgreSQL | Homebrew | 影片元資料儲存 | 已安裝 |
|
||||
| Redis | Homebrew | 快取與工作佇列 | 已安裝 |
|
||||
| Ollama | Homebrew | 本地 LLM 推論 | 已安裝 |
|
||||
| Caddy | Homebrew | 網頁伺服器 (可選) | 已安裝 |
|
||||
| Gitea | 手動安裝 | Git 服務 | 已安裝 |
|
||||
| Grafana | Homebrew | 監控儀表板 | 已安裝 |
|
||||
| Kafka | 手動安裝 | 訊息佇列 (可選) | 已安裝 |
|
||||
| MariaDB | Homebrew | 資料庫 (可選) | 已安裝 |
|
||||
| Netdata | Homebrew | 系統監控 | 已安裝 |
|
||||
| PHP | Homebrew | Web 後端 | 已安裝 |
|
||||
| Prometheus | Homebrew | 指標收集 | 已安裝 |
|
||||
| SeaweedFS | 手動安裝 | 分散式儲存 (可選) | 已安裝 |
|
||||
| SFTPGo | 手動安裝 | SFTP 服務 | 已安裝 |
|
||||
| n8n | Homebrew | 工作流自動化 | 已安裝 |
|
||||
| PostgreSQL | 自定義 plist | 影片元資料儲存 | ✅ 正常 |
|
||||
| Redis | 自定義 plist | 快取與工作佇列 | ✅ 正常 |
|
||||
| Ollama | 自定義 plist | 本地 LLM 推論 | ✅ 正常 |
|
||||
| Caddy | 自定義 plist | 網頁伺服器 | ✅ 正常 |
|
||||
| Gitea | 自定義 plist | Git 服務 | ✅ 正常 |
|
||||
| Gitea MCP Server | 自定義 plist | Gitea MCP 整合 | ✅ 正常 |
|
||||
| Grafana | Homebrew | 監控儀表板 | ⚠️ Homebrew |
|
||||
| Kafka | 手動安裝 | 訊息佇列 (可選) | ⚠️ 未遷移 |
|
||||
| MariaDB | 自定義 plist | 資料庫 (可選) | ✅ 正常 |
|
||||
| Netdata | Homebrew | 系統監控 | ⚠️ Homebrew |
|
||||
| PHP | 自定義 plist | Web 後端 | ✅ 正常 |
|
||||
| Prometheus | Homebrew | 指標收集 | ⚠️ Homebrew |
|
||||
| SeaweedFS | 手動安裝 | 分散式儲存 (可選) | ⚠️ 未遷移 |
|
||||
| SFTPGo | 自定義 plist | SFTP 服務 | ✅ 正常 |
|
||||
| n8n | 自定義 plist | 工作流自動化 | ✅ 正常 |
|
||||
| n8n Worker | 自定義 plist | 工作流 Worker | ✅ 正常 |
|
||||
| MongoDB | 自定義 plist | 文件資料庫 | ✅ 正常 |
|
||||
| Qdrant | 自定義 plist | 向量資料庫 | ✅ 正常 |
|
||||
| Momentry API | 自定義 plist | 影片管理 API | ✅ 正常 |
|
||||
| RustDesk HBBR | 自定義 plist | 遠端桌面橋接 | ✅ 正常 |
|
||||
| RustDesk HBBS | 自定義 plist | 遠端桌面服務器 | ✅ 正常 |
|
||||
|
||||
---
|
||||
|
||||
## 服務健康檢查結果 (2026-03-24)
|
||||
|
||||
### ✅ 正常運行的服務
|
||||
|
||||
| 服務 | 版本 | Port | 測試命令 | 結果 |
|
||||
|------|------|------|----------|------|
|
||||
| **PostgreSQL** | 18.1 | 5432 | `pg_isready -h 127.0.0.1 -p 5432` | ✅ 接受連線 |
|
||||
| **Redis** | 7.4.x | 6379 | `redis-cli -a accusys ping` | ✅ PONG |
|
||||
| **MongoDB** | 8.2.6 | 27017 | `mongosh --eval "db.adminCommand('ping')"` | ✅ { ok: 1 } |
|
||||
| **Ollama** | - | 11434 | `curl -s http://localhost:11434/api/tags` | ✅ 模型可用 |
|
||||
| **n8n** | 2.12.3 | 5678/5681/5682 | `curl -s http://localhost:5678/healthz` | ✅ {"status":"ok"} |
|
||||
| **n8n Worker** | 2.12.3 | 5681/5690/5691 | - | ✅ 運行中 |
|
||||
| **Momentry API** | 0.1.0 | 3002 | `curl -s http://localhost:3002/health` | ✅ OK |
|
||||
| **Qdrant** | 1.17.0 | 6333 | `curl -s http://localhost:6333/` | ✅ 版本資訊 |
|
||||
| **Caddy** | 2.10.x | 443 | `curl -sI https://momentry.ddns.net` | ✅ HTTP 200 |
|
||||
| **SFTPGo** | 2.7.x | 8080 | `curl -s http://localhost:8080/sftpgo/` | ✅ JSON 響應 |
|
||||
| **Gitea** | - | 3000 | `curl -s http://localhost:3000/` | ✅ HTML 響應 |
|
||||
| **Gitea MCP** | - | 8787 | `curl -s http://localhost:8787/` | ✅ 運行中 |
|
||||
| **MariaDB** | 12.1.x | 3306 | `mariadb -u root -e "SELECT 1;"` | ✅ 正常 |
|
||||
| **PHP-FPM** | 8.5.x | 9000 | `ps aux \| grep php-fpm` | ✅ 運行中 |
|
||||
|
||||
### ⚠️ 需要配置的服務
|
||||
|
||||
| 服務 | 問題 | 解決方案 |
|
||||
|------|------|----------|
|
||||
| Grafana | 使用 Homebrew | 考慮遷移到自定義 plist |
|
||||
| Prometheus | 使用 Homebrew | 考慮遷移到自定義 plist |
|
||||
| Kafka | 未遷移 | 可選服務 |
|
||||
| SeaweedFS | 未遷移 | 可選服務 |
|
||||
| Netdata | 使用 Homebrew | 考慮遷移到自定義 plist |
|
||||
|
||||
### ⚠️ Homebrew 管理的服務 (建議遷移)
|
||||
|
||||
| 服務 | 風險 |
|
||||
|------|------|
|
||||
| homebrew.mxcl.grafana | Reboot 後可能自動啟動但使用預設設定 |
|
||||
| homebrew.mxcl.prometheus | Reboot 後可能自動啟動但使用預設設定 |
|
||||
| homebrew.mxcl.openwebui | 需要確認是否需要 |
|
||||
| homebrew.mxcl.kafka | 需要確認是否需要 |
|
||||
| homebrew.mxcl.seaweedfs | 需要確認是否需要 |
|
||||
| homebrew.mxcl.netdata | 需要確認是否需要 |
|
||||
| homebrew.mxcl.ddclient | 動態 DNS,可能需要 |
|
||||
| homebrew.mxcl.shadowsocks-rust | VPN,可能需要 |
|
||||
|
||||
### MCP Servers (2026-03-24)
|
||||
|
||||
| Server | 安裝方式 | 路徑 | 狀態 |
|
||||
|--------|----------|------|------|
|
||||
| gitea | Homebrew | /opt/homebrew/bin/gitea-mcp-server | ✅ Connected |
|
||||
| n8n | NPM | /opt/homebrew/bin/mcp-n8n | ✅ Connected |
|
||||
| postgres | NPM | /opt/homebrew/bin/mcp-server-postgres | ✅ Connected |
|
||||
| redis | NPM | /opt/homebrew/bin/mcp-server-redis | ✅ Connected |
|
||||
| mongodb | NPM | /opt/homebrew/bin/mongodb-mcp-server | ✅ Connected |
|
||||
| qdrant | Python | /opt/homebrew/bin/mcp-server-qdrant | ✅ Connected |
|
||||
| filesystem | NPM | /opt/homebrew/bin/mcp-server-filesystem | ✅ Connected |
|
||||
| sentry | NPM | /opt/homebrew/bin/sentry-mcp | ⏳ Pending Config |
|
||||
| context7 | NPM | /opt/homebrew/bin/context7-mcp | ✅ Connected |
|
||||
| playwright | NPM | /opt/homebrew/bin/playwright-mcp | ✅ Connected |
|
||||
|
||||
**配置文件**: `~/.config/opencode/opencode.json`
|
||||
|
||||
**驗證命令**:
|
||||
```bash
|
||||
opencode mcp ls
|
||||
```
|
||||
|
||||
**詳細文檔**: [OpenCode MCP 安裝指南](./OPENCODE_MCP_INSTALL.md)
|
||||
|
||||
### 測試腳本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# services_health_check.sh
|
||||
|
||||
echo "=== Momentry 服務健康檢查 ==="
|
||||
echo ""
|
||||
|
||||
# PostgreSQL
|
||||
pg_isready -h 127.0.0.1 -p 5432 -U accusys > /dev/null 2>&1 && echo "✅ PostgreSQL" || echo "❌ PostgreSQL"
|
||||
|
||||
# Redis
|
||||
redis-cli -a accusys ping > /dev/null 2>&1 && echo "✅ Redis" || echo "❌ Redis"
|
||||
|
||||
# MongoDB
|
||||
mongosh --quiet --eval "db.adminCommand('ping')" > /dev/null 2>&1 && echo "✅ MongoDB" || echo "❌ MongoDB"
|
||||
|
||||
# Ollama
|
||||
curl -s http://localhost:11434/api/tags > /dev/null 2>&1 && echo "✅ Ollama" || echo "❌ Ollama"
|
||||
|
||||
# n8n
|
||||
curl -s http://localhost:5678/ > /dev/null 2>&1 && echo "✅ n8n" || echo "❌ n8n"
|
||||
|
||||
# Momentry API
|
||||
curl -s http://localhost:3002/health > /dev/null 2>&1 && echo "✅ Momentry API" || echo "❌ Momentry API"
|
||||
|
||||
# Qdrant
|
||||
curl -s http://localhost:6333/ > /dev/null 2>&1 && echo "✅ Qdrant" || echo "❌ Qdrant"
|
||||
|
||||
# Caddy
|
||||
curl -sI https://momentry.ddns.net > /dev/null 2>&1 && echo "✅ Caddy" || echo "❌ Caddy"
|
||||
|
||||
# SFTPGo
|
||||
curl -s http://localhost:8080/api/v2/healthz > /dev/null 2>&1 && echo "✅ SFTPGo" || echo "⚠️ SFTPGo (需配置)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
@@ -42,14 +178,12 @@ brew list postgresql@18 2>/dev/null || echo "Not installed"
|
||||
brew install postgresql@18
|
||||
```
|
||||
|
||||
#### 初始化資料庫
|
||||
```bash
|
||||
# 初始化資料庫 (如尚未初始化)
|
||||
initdb /usr/local/var/postgresql@18
|
||||
|
||||
# 建立 momentry 資料庫
|
||||
createdb -U accusys momentry
|
||||
#### 資料目錄
|
||||
```
|
||||
/Users/accusys/momentry/var/postgresql
|
||||
```
|
||||
|
||||
**重要**: 確保使用統一的資料目錄,避免與 homebrew plist 衝突。
|
||||
|
||||
#### 開機自動啟動
|
||||
```bash
|
||||
@@ -61,42 +195,51 @@ sudo tee /Library/LaunchDaemons/com.momentry.postgresql.plist > /dev/null <<'EOF
|
||||
<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/pg_ctl</string>
|
||||
<string>/opt/homebrew/opt/postgresql@18/bin/postgres</string>
|
||||
<string>-D</string>
|
||||
<string>/opt/homebrew/var/postgresql@18</string>
|
||||
<string>-l</string>
|
||||
<string>/opt/homebrew/var/postgresql@18/logfile</string>
|
||||
<string>start</string>
|
||||
<string>/Users/accusys/momentry/var/postgresql</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/opt/homebrew/var/postgresql@18</string>
|
||||
<key>UserName</key>
|
||||
<string>accusys</string>
|
||||
<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>
|
||||
EOF
|
||||
|
||||
# 載入服務
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
```
|
||||
|
||||
#### 管理命令
|
||||
```bash
|
||||
# 啟動
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# 停止
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
sudo launchctl bootout system/com.momentry.postgresql.plist
|
||||
|
||||
# 重新載入
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
sudo launchctl bootout system/com.momentry.postgresql.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# 查看狀態
|
||||
launchctl list | grep com.momentry.postgresql
|
||||
```
|
||||
|
||||
#### 健康檢查
|
||||
@@ -105,10 +248,13 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
pg_isready -h localhost -p 5432 -U accusys
|
||||
|
||||
# 方法 2: 連線測試
|
||||
psql -U accusys -h localhost -d momentry -c "SELECT 1;"
|
||||
PGPASSWORD=n8n1234 psql -h localhost -U n8n -d n8n -c "SELECT 1;"
|
||||
|
||||
# 方法 3: 檢查程序
|
||||
pgrep -f "postgres.*postgresql@18"
|
||||
pgrep -a postgres
|
||||
|
||||
# 方法 4: 檢查資料庫
|
||||
PGPASSWORD=n8n1234 psql -h localhost -U n8n -d n8n -c "SELECT COUNT(*) FROM workflow_entity;"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -356,7 +502,7 @@ ps aux | grep n8n | grep -v grep
|
||||
|
||||
# 方法 3: 檢查端口
|
||||
lsof -i :5678
|
||||
lsof -i :5690
|
||||
lsof -i :5679
|
||||
```
|
||||
|
||||
---
|
||||
@@ -512,41 +658,54 @@ chmod +x scripts/health_check.sh
|
||||
|
||||
## 服務管理速查表
|
||||
|
||||
### 啟動服務
|
||||
### 啟動服務 (使用 launchctl bootstrap)
|
||||
```bash
|
||||
# PostgreSQL (需要 root 權限)
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
# 所有服務
|
||||
for plist in /Library/LaunchDaemons/com.momentry.*.plist; do
|
||||
sudo launchctl bootstrap system "$plist"
|
||||
done
|
||||
|
||||
# Redis, Ollama (需要 root 權限)
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
# PostgreSQL
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# n8n (需要 root 權限)
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
# Redis, Ollama, Qdrant
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.qdrant.plist
|
||||
|
||||
# n8n (main + worker)
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
```
|
||||
|
||||
### 停止服務
|
||||
### 停止服務 (使用 launchctl bootout)
|
||||
```bash
|
||||
# PostgreSQL
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
# 所有 Momentry 服務
|
||||
for svc in $(launchctl list | grep com.momentry | awk '{print $3}'); do
|
||||
sudo launchctl bootout system/$svc 2>/dev/null
|
||||
done
|
||||
|
||||
# Redis, Ollama
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
# PostgreSQL
|
||||
sudo launchctl bootout system/com.momentry.postgresql.plist
|
||||
|
||||
# Redis, Ollama, Qdrant
|
||||
sudo launchctl bootout system/com.momentry.redis.plist
|
||||
sudo launchctl bootout system/com.momentry.ollama.plist
|
||||
sudo launchctl bootout system/com.momentry.qdrant.plist
|
||||
|
||||
# n8n
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
sudo launchctl bootout system/com.momentry.n8n.main.plist
|
||||
sudo launchctl bootout system/com.momentry.n8n.worker.plist
|
||||
```
|
||||
|
||||
### 查詢服務狀態
|
||||
```bash
|
||||
# 查看所有服務
|
||||
launchctl list | grep -E "(postgres|redis|ollama|n8n|grafana|prometheus)"
|
||||
# 查看所有 Momentry 服務
|
||||
launchctl list | grep com.momentry
|
||||
|
||||
# 查看特定服務
|
||||
launchctl list | grep com.momentry
|
||||
launchctl list | grep com.momentry.postgresql
|
||||
launchctl list | grep com.momentry.n8n
|
||||
```
|
||||
|
||||
---
|
||||
@@ -711,21 +870,160 @@ esac
|
||||
|
||||
---
|
||||
|
||||
## 服務快速參照
|
||||
|
||||
### Port 對照表
|
||||
|
||||
| Port | 服務 | 說明 |
|
||||
|------|------|------|
|
||||
| 11434 | Ollama | LLM API |
|
||||
| 19999 | Netdata | 系統監控 |
|
||||
| 2019 | Caddy | 管理 API |
|
||||
| 21115-21119 | RustDesk | 遠端桌面 |
|
||||
| 27017 | MongoDB | 文件資料庫 |
|
||||
| 3000 | Gitea | Git 服務 |
|
||||
| 3001 | Grafana | 監控儀表板 |
|
||||
| 3002 | Momentry API | Rust API 伺服器 (生產環境) |
|
||||
| 3003 | Momentry Playground | Rust API 伺服器 (開發環境) |
|
||||
| 3306 | MariaDB | MySQL 相容資料庫 |
|
||||
| 4096 | OpenCode | CLI 工具 |
|
||||
| 5000-7000 | ControlCenter | 控制中心 |
|
||||
| 5678 | n8n | 工作流自動化 (Main) |
|
||||
| 5681 | n8n | Worker HTTP |
|
||||
| 5682 | n8n | Worker Health Check |
|
||||
| 5690 | n8n | Task Broker |
|
||||
| 5691 | n8n | Runner Health |
|
||||
| 6333-6334 | Qdrant | 向量資料庫 |
|
||||
| 6379 | Redis | 快取/佇列 |
|
||||
| 8080 | SFTPGo | HTTP/WebDAV |
|
||||
| 8081 | Trunk | 轉發服務 |
|
||||
| 8082 | SeaweedFS | Master |
|
||||
| 8090 | SFTPGo | WebDAV |
|
||||
| 8333 | SeaweedFS | Volume |
|
||||
| 8388 | Shadowsocks | VPN |
|
||||
| 8888 | SeaweedFS | Filer |
|
||||
| 9000 | PHP-FPM | PHP 處理器 |
|
||||
| 9090 | Prometheus | 指標收集 |
|
||||
| 9092-9093 | Kafka | 訊息佇列 |
|
||||
| 9333 | SeaweedFS | Volume |
|
||||
| 18082 | SeaweedFS | Volume |
|
||||
| 18333 | SeaweedFS | Volume |
|
||||
| 18888 | SeaweedFS | Filer |
|
||||
| 19333 | SeaweedFS | Volume |
|
||||
|
||||
## Caddy 反向代理 URL 對照表
|
||||
|
||||
| URL | 內部服務 | 說明 |
|
||||
|-----|----------|------|
|
||||
| `n8n.momentry.ddns.net` | :5678 | n8n 工作流自動化 |
|
||||
| `wp.momentry.ddns.net` | :9000 | WordPress 網站 |
|
||||
| `seaweed.momentry.ddns.net` | :8888 | SeaweedFS Filer |
|
||||
| `sftpgo.momentry.ddns.net` | :8080 | SFTPGo HTTP |
|
||||
| `webdav.momentry.ddns.net` | :8090 | SFTPGo WebDAV |
|
||||
| `qdrant.momentry.ddns.net` | :6333 | Qdrant 向量資料庫 |
|
||||
| `gitea.momentry.ddns.net` | :3000 | Gitea Git 服務 |
|
||||
| `chat.momentry.ddns.net` | :8085 | Open WebUI |
|
||||
| `netdata.momentry.ddns.net` | :19999 | Netdata 監控 |
|
||||
| `grafana.momentry.ddns.net` | :3001 | Grafana 儀表板 |
|
||||
| `router5.momentry.ddns.net` | 192.168.5.1:80 | Router 5 |
|
||||
| `router110.momentry.ddns.net` | 192.168.110.1:80 | Router 110 |
|
||||
| `router0.momentry.ddns.net/admin/*` | 192.168.0.1:80 | Router 0 |
|
||||
| `truenas.momentry.ddns.net` | 192.168.0.219:80 | TrueNAS |
|
||||
| `:3200` | :3002 | Momentry Dashboard + API |
|
||||
|
||||
**Caddy 管理**: https://localhost:2019/
|
||||
|
||||
---
|
||||
|
||||
## 目錄對照表
|
||||
|
||||
### /Users/accusys/momentry/var/
|
||||
|
||||
| 目錄 | 服務 | 說明 |
|
||||
|------|------|------|
|
||||
| `caddy/` | Caddy | 資料目錄 |
|
||||
| `gitea/` | Gitea | Git 資料庫 |
|
||||
| `mariadb/` | MariaDB | 資料庫檔案 |
|
||||
| `mongodb/` | MongoDB | 文件資料庫 (自定義 plist) |
|
||||
| `n8n/` | n8n | 工作流資料 |
|
||||
| `ollama/` | Ollama | 模型快取 |
|
||||
| `php/` | PHP | FastCGI 進程 |
|
||||
| `postgresql/` | PostgreSQL | 主資料庫 (自定義 plist) |
|
||||
| `qdrant/` | Qdrant | 向量資料庫 |
|
||||
| `redis/` | Redis | 持久化檔案 |
|
||||
| `rustdesk/` | RustDesk | ID 資料庫 |
|
||||
| `sftpgo/` | SFTPGo | 使用者資料 |
|
||||
| `sftpgo_backup/` | SFTPGo | 備份 |
|
||||
|
||||
### /opt/homebrew/var/ (Homebrew 管理)
|
||||
|
||||
| 目錄 | 服務 | 說明 |
|
||||
|------|------|------|
|
||||
| `mongodb/` | MongoDB | (舊) 文件資料庫 |
|
||||
| `postgresql@18/` | PostgreSQL | (舊) 主資料庫 |
|
||||
|
||||
### /Users/accusys/momentry/etc/
|
||||
|
||||
| 目錄 | 服務 | 說明 |
|
||||
|------|------|------|
|
||||
| `caddy/` | Caddy | 配置 |
|
||||
| `gitea/` | Gitea | 配置 |
|
||||
| `php/` | PHP | 配置 |
|
||||
| `sftpgo/` | SFTPGo | 配置 |
|
||||
|
||||
### /Users/accusys/momentry/log/
|
||||
|
||||
| 檔案 | 服務 | 說明 |
|
||||
|------|------|------|
|
||||
| `backup.log` | Backup | 備份日誌 |
|
||||
| `redis.log` | Redis | 執行日誌 |
|
||||
| `redis.error.log` | Redis | 錯誤日誌 |
|
||||
| `ollama.log` | Ollama | 執行日誌 |
|
||||
| `ollama.error.log` | Ollama | 錯誤日誌 |
|
||||
|
||||
### /Users/accusys/momentry/backup/
|
||||
|
||||
| 目錄 | 說明 |
|
||||
|------|------|
|
||||
| `daily/` | 每日備份 |
|
||||
| `weekly/` | 每週備份 |
|
||||
| `monthly/` | 每月備份 |
|
||||
|
||||
---
|
||||
|
||||
## 附錄: 服務版本對應表
|
||||
|
||||
| 服務 | 版本 | Port | 使用者 | plist 位置 |
|
||||
|------|------|------|--------|-------------|
|
||||
| PostgreSQL | 18.1 | 5432 | accusys | /Library/LaunchDaemons/ |
|
||||
| Redis | 7.4.x | 6379 | accusys | /Library/LaunchDaemons/ |
|
||||
| Ollama | - | 11434 | accusys | /Library/LaunchDaemons/ |
|
||||
| n8n | 2.3.5 | 5678/5690 | accusys | /Library/LaunchDaemons/ |
|
||||
| Node.js (n8n) | 22.22.1 | - | - | /opt/homebrew/opt/node@22/ |
|
||||
| Python (Momentry) | 3.11.14 | - | - | venv/bin/python |
|
||||
| Caddy | 2.10.x | 2019 | root | /Library/LaunchDaemons/ |
|
||||
| Gitea | - | 3000 | accusys | /Library/LaunchDaemons/ |
|
||||
| SFTPGo | 2.7.x | 8080 | accusys | /Library/LaunchDaemons/ |
|
||||
| Qdrant | 1.17.x | 6333 | accusys | /Library/LaunchDaemons/ |
|
||||
| MongoDB | - | 27017 | accusys | /Library/LaunchDaemons/ |
|
||||
| MariaDB | 12.1.x | 3306 | accusys | /Library/LaunchDaemons/ |
|
||||
| RustDesk | - | 21115-21119 | accusys | /Library/LaunchDaemons/ |
|
||||
| PHP | 8.3.x | - | - | /opt/homebrew/ |
|
||||
| 服務 | 版本 | Port | 使用者 | plist 位置 | 資料目錄 |
|
||||
|------|------|------|--------|-------------|----------|
|
||||
| PostgreSQL | 18.1 | 5432 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/postgresql |
|
||||
| Redis | 7.4.x | 6379 | accusys | /Library/LaunchDaemons/ | /opt/homebrew/var/redis |
|
||||
| Ollama | 0.13.5 | 11434 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/ollama/models |
|
||||
| n8n | 2.12.3 | 5678/5681 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/n8n |
|
||||
| Node.js (n8n) | 22.22.1 | - | - | /opt/homebrew/opt/node@22/ | - |
|
||||
| Python (Momentry) | 3.11.14 | - | - | venv/bin/python | - |
|
||||
| Caddy | 2.10.x | 2019/443 | root | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/caddy |
|
||||
| Gitea | - | 3000 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/gitea |
|
||||
| Gitea MCP | - | 8787 | accusys | /Library/LaunchDaemons/ | - |
|
||||
| SFTPGo | 2.7.x | 8080/2022 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/sftpgo |
|
||||
| Qdrant | 1.17.0 | 6333 | accusys | /Library/LaunchDaemons/ | /Users/accusys/.local/share/qdrant |
|
||||
| MongoDB | 8.2.6 | 27017 | root | /Library/LaunchDaemons/ | /opt/homebrew/var/mongodb |
|
||||
| MariaDB | 12.1.x | 3306 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/mariadb |
|
||||
| RustDesk HBBR | - | 21115 | accusys | /Library/LaunchDaemons/ | - |
|
||||
| RustDesk HBBS | - | 21115-21119 | accusys | /Library/LaunchDaemons/ | /Users/accusys/momentry/var/rustdesk |
|
||||
| PHP | 8.5.x | 9000 | - | /Library/LaunchDaemons/ | - |
|
||||
| Momentry API | 0.1.0 | 3002 | accusys | /Library/LaunchDaemons/ | - |
|
||||
| Momentry Playground | 0.1.0 | 3003 | accusys | - | - |
|
||||
|
||||
---
|
||||
|
||||
## 重要密碼與金鑰
|
||||
|
||||
| 服務 | 用途 | 預設值 |
|
||||
|------|------|--------|
|
||||
| **PostgreSQL** | 連線密碼 (accusys) | `accusys` |
|
||||
| **Redis** | 認證密碼 | `accusys` |
|
||||
| **Qdrant** | API Key | `Test3200Test3200Test3200` |
|
||||
| **n8n** | 資料庫密碼 | `n8n` (PostgreSQL) |
|
||||
| **SFTPGo** | 安裝碼 | `Test3200Test3200` |
|
||||
| **SFTPGo** | DB 密碼 | `sftpgo_pass_2026` |
|
||||
| **Momentry API** | 資料庫 | `postgres://accusys:accusys@127.0.0.1:5432/momentry` |
|
||||
|
||||
96
scripts/check_config.sh
Executable file
96
scripts/check_config.sh
Executable file
@@ -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 "=========================================="
|
||||
162
src/core/cache/redis_cache.rs
vendored
Normal file
162
src/core/cache/redis_cache.rs
vendored
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
139
src/core/config.rs
Normal file
139
src/core/config.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
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()));
|
||||
|
||||
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)
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,8 @@ 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>>,
|
||||
@@ -18,13 +20,8 @@ pub struct RedisState {
|
||||
|
||||
impl RedisClient {
|
||||
pub fn new() -> Result<Self> {
|
||||
let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| {
|
||||
let password =
|
||||
std::env::var("REDIS_PASSWORD").unwrap_or_else(|_| "accusys".to_string());
|
||||
format!("redis://:{}@localhost:6379", password)
|
||||
});
|
||||
|
||||
let client = Client::open(redis_url.as_str()).context("Failed to connect to Redis")?;
|
||||
let client = Client::open(crate::core::config::REDIS_URL.as_str())
|
||||
.context("Failed to connect to Redis")?;
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
@@ -49,7 +46,8 @@ impl RedisClient {
|
||||
|
||||
pub async fn get_job_status(&self, uuid: &str) -> Result<Option<JobStatus>> {
|
||||
let mut conn = self.get_conn_internal().await?;
|
||||
let key = format!("momentry:job:{}", uuid);
|
||||
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() {
|
||||
@@ -83,7 +81,8 @@ impl RedisClient {
|
||||
status: &ProcessorStatus,
|
||||
) -> Result<()> {
|
||||
let mut conn = self.get_conn_internal().await?;
|
||||
let key = format!("momentry:job:{}:processor:{}", uuid, processor);
|
||||
let prefix = REDIS_KEY_PREFIX.as_str();
|
||||
let key = format!("{}job:{}:processor:{}", prefix, uuid, processor);
|
||||
|
||||
let _: Option<String> = conn
|
||||
.hset_multiple(
|
||||
@@ -111,7 +110,8 @@ impl RedisClient {
|
||||
processor: &str,
|
||||
) -> Result<Option<ProcessorStatus>> {
|
||||
let mut conn = self.get_conn_internal().await?;
|
||||
let key = format!("momentry:job:{}:processor:{}", uuid, processor);
|
||||
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() {
|
||||
@@ -138,7 +138,8 @@ impl RedisClient {
|
||||
|
||||
pub async fn publish_progress(&self, uuid: &str, message: &ProgressMessage) -> Result<()> {
|
||||
let mut conn = self.get_conn_internal().await?;
|
||||
let channel = format!("momentry:progress:{}", uuid);
|
||||
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?;
|
||||
@@ -148,7 +149,8 @@ impl RedisClient {
|
||||
|
||||
pub async fn subscribe_progress(&self, uuid: &str) -> Result<redis::aio::PubSub> {
|
||||
let mut pubsub = self.client.get_async_pubsub().await?;
|
||||
let channel = format!("momentry:progress:{}", uuid);
|
||||
let prefix = REDIS_KEY_PREFIX.as_str();
|
||||
let channel = format!("{}progress:{}", prefix, uuid);
|
||||
|
||||
pubsub.subscribe(channel).await?;
|
||||
|
||||
@@ -174,45 +176,285 @@ impl RedisClient {
|
||||
|
||||
pub async fn add_to_active_jobs(&self, uuid: &str) -> Result<()> {
|
||||
let mut conn = self.get_conn_internal().await?;
|
||||
let _: usize = conn.sadd("momentry:jobs:active", uuid).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("momentry:jobs:active", "momentry:jobs:completed", uuid)
|
||||
.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("momentry:jobs:active", "momentry:jobs:failed", uuid)
|
||||
.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 jobs: Vec<String> = conn.smembers("momentry:jobs:active").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("momentry:health:momentry_core", status, 60)
|
||||
.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 health: Option<String> = conn.get("momentry:health:momentry_core").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: i64,
|
||||
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: i64 = 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 {
|
||||
@@ -260,3 +502,55 @@ pub struct ProgressData {
|
||||
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: i64,
|
||||
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: i64,
|
||||
pub status: String,
|
||||
pub progress_current: i32,
|
||||
pub progress_total: i32,
|
||||
}
|
||||
|
||||
2253
src/main.rs
2253
src/main.rs
File diff suppressed because it is too large
Load Diff
3208
src/playground.rs
Normal file
3208
src/playground.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user