Initial commit: Momentry Core v0.1
- Rust-based digital asset management system - Video analysis: ASR, OCR, YOLO, Face, Pose - RAG capabilities with Qdrant vector database - Multi-database support: PostgreSQL, Redis, MongoDB - Monitoring system with launchd plists - n8n workflow automation integration
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.env
|
||||
.env.local
|
||||
target/
|
||||
venv/
|
||||
thumbnails/
|
||||
*.asr.json
|
||||
*.probe.json
|
||||
test_asr.json
|
||||
.DS_Store
|
||||
*.log
|
||||
.ruff_cache/
|
||||
168
AGENTS.md
Normal file
168
AGENTS.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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
|
||||
|
||||
# Run CLI
|
||||
cargo run -- --help
|
||||
cargo run -- register /path/to/video.mp4
|
||||
cargo run -- server --host 0.0.0.0 --port 3000
|
||||
```
|
||||
|
||||
## 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/
|
||||
│ ├── chunk/ # Chunking logic
|
||||
│ ├── db/ # Database (PostgreSQL, MongoDB, Redis, Qdrant)
|
||||
│ ├── embedding/ # Vector embeddings
|
||||
│ ├── overlay/ # Video overlay
|
||||
│ ├── probe/ # ffprobe integration
|
||||
│ ├── processor/ # ASR, OCR, YOLO, Face, Pose
|
||||
│ ├── storage/ # File management
|
||||
│ └── thumbnail/ # Thumbnail extraction
|
||||
├── api/ # HTTP API (axum)
|
||||
├── player/ # Video player
|
||||
└── watcher/ # File system watcher
|
||||
```
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **Error handling**: `anyhow`, `thiserror`
|
||||
- **Async**: `tokio` (full features), `async-trait`
|
||||
- **CLI**: `clap` (derive)
|
||||
- **Serialization**: `serde`, `serde_json`
|
||||
- **Database**: `sqlx`, `mongodb`, `redis`, `qdrant-client`
|
||||
- **HTTP**: `axum`, `tower`
|
||||
- **Logging**: `tracing`, `tracing-subscriber`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `DATABASE_URL` - PostgreSQL (default: `postgres://accusys@localhost:5432/momentry`)
|
||||
|
||||
## Notes
|
||||
|
||||
- No unit tests exist - add tests when implementing features
|
||||
- Video processing uses external tools (ffprobe, Python scripts)
|
||||
- Multi-database architecture (PostgreSQL, MongoDB, Redis, Qdrant)
|
||||
- Monitor directory is a separate system (not Rust)
|
||||
4260
Cargo.lock
generated
Normal file
4260
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
59
Cargo.toml
Normal file
59
Cargo.toml
Normal file
@@ -0,0 +1,59 @@
|
||||
[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"
|
||||
|
||||
# CLI
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
# Async
|
||||
async-trait = "0.1"
|
||||
|
||||
# Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# UUID
|
||||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
|
||||
# Database
|
||||
redis = { version = "0.25", features = ["tokio-comp"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "json"] }
|
||||
mongodb = { version = "2", features = ["tokio-sync"] }
|
||||
qdrant-client = "1.7"
|
||||
|
||||
# HTTP Server
|
||||
axum = "0.7"
|
||||
tower = "0.4"
|
||||
|
||||
# File watching
|
||||
notify = "6"
|
||||
|
||||
# Video/Audio
|
||||
sdl2 = "0.38"
|
||||
|
||||
# Configuration
|
||||
dotenv = "0.15"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
dev = []
|
||||
|
||||
[lib]
|
||||
name = "momentry_core"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "momentry"
|
||||
path = "src/main.rs"
|
||||
443
MOMENTRY_INTEGRATION_GUIDE.md
Normal file
443
MOMENTRY_INTEGRATION_GUIDE.md
Normal file
@@ -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 | 新增服務添加規範 (第九章)、模板文件 |
|
||||
451
docs/INSTALL_CADDY.md
Normal file
451
docs/INSTALL_CADDY.md
Normal file
@@ -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/
|
||||
394
docs/INSTALL_GITEA.md
Normal file
394
docs/INSTALL_GITEA.md
Normal file
@@ -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/
|
||||
380
docs/INSTALL_MARIADB.md
Normal file
380
docs/INSTALL_MARIADB.md
Normal file
@@ -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/
|
||||
367
docs/INSTALL_MONGODB.md
Normal file
367
docs/INSTALL_MONGODB.md
Normal file
@@ -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 (共用 - 不要刪除!)
|
||||
466
docs/INSTALL_N8N.md
Normal file
466
docs/INSTALL_N8N.md
Normal file
@@ -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
|
||||
359
docs/INSTALL_OLLAMA.md
Normal file
359
docs/INSTALL_OLLAMA.md
Normal file
@@ -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/
|
||||
379
docs/INSTALL_PHP.md
Normal file
379
docs/INSTALL_PHP.md
Normal file
@@ -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/
|
||||
372
docs/INSTALL_POSTGRESQL.md
Normal file
372
docs/INSTALL_POSTGRESQL.md
Normal file
@@ -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/
|
||||
456
docs/INSTALL_QDRANT.md
Normal file
456
docs/INSTALL_QDRANT.md
Normal file
@@ -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/
|
||||
367
docs/INSTALL_REDIS.md
Normal file
367
docs/INSTALL_REDIS.md
Normal file
@@ -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/
|
||||
284
docs/INSTALL_RUSTDESK.md
Normal file
284
docs/INSTALL_RUSTDESK.md
Normal file
@@ -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 添加服務器公式
|
||||
360
docs/INSTALL_SFTPGO.md
Normal file
360
docs/INSTALL_SFTPGO.md
Normal file
@@ -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/
|
||||
423
docs/NODEJS.md
Normal file
423
docs/NODEJS.md
Normal file
@@ -0,0 +1,423 @@
|
||||
# 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 | 開機啟動 |
|
||||
|
||||
---
|
||||
|
||||
## 相關文件
|
||||
|
||||
- [INSTALL_N8N.md](./INSTALL_N8N.md) - n8n 安裝指南 (包含 Node.js 配置範例)
|
||||
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
|
||||
- [node_monitor.sh](../monitor/service/node_monitor.sh) - Node.js 監控腳本
|
||||
483
docs/PYTHON.md
Normal file
483
docs/PYTHON.md
Normal file
@@ -0,0 +1,483 @@
|
||||
# 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 進行日誌輸出
|
||||
- [ ] 錯誤處理適當
|
||||
- [ ] 類型提示完整
|
||||
- [ ] 更新監控配置
|
||||
- [ ] 建立測試案例
|
||||
|
||||
---
|
||||
|
||||
## 相關文件
|
||||
|
||||
- [NODEJS.md](./NODEJS.md) - Node.js 開發指南
|
||||
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
|
||||
- [python_monitor.sh](../monitor/service/python_monitor.sh) - Python 監控腳本
|
||||
726
docs/SERVICES.md
Normal file
726
docs/SERVICES.md
Normal file
@@ -0,0 +1,726 @@
|
||||
# Momentry 系統服務安裝與管理指南
|
||||
|
||||
## 概述
|
||||
|
||||
本文檔記錄 momentry 系統所需的所有服務,包括安裝步驟、健康檢查和管理命令。
|
||||
|
||||
**重要**: 請勿使用 `brew services` 命令管理服務,否則可能導致 .plist 檔案還原為預設狀態,造成系統異常。請使用 `launchctl` 命令進行管理。
|
||||
|
||||
---
|
||||
|
||||
## 服務清單
|
||||
|
||||
| 服務名稱 | 安裝方式 | 用途 | 狀態 |
|
||||
|----------|----------|------|-------|
|
||||
| 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 | 工作流自動化 | 已安裝 |
|
||||
|
||||
---
|
||||
|
||||
## 必要服務 (Momentry 核心)
|
||||
|
||||
### 1. PostgreSQL
|
||||
|
||||
#### 安裝
|
||||
```bash
|
||||
# 檢查是否已安裝
|
||||
brew list postgresql@18 2>/dev/null || echo "Not installed"
|
||||
|
||||
# 安裝 PostgreSQL 18
|
||||
brew install postgresql@18
|
||||
```
|
||||
|
||||
#### 初始化資料庫
|
||||
```bash
|
||||
# 初始化資料庫 (如尚未初始化)
|
||||
initdb /usr/local/var/postgresql@18
|
||||
|
||||
# 建立 momentry 資料庫
|
||||
createdb -U accusys momentry
|
||||
```
|
||||
|
||||
#### 開機自動啟動
|
||||
```bash
|
||||
# 建立 plist 檔案
|
||||
sudo tee /Library/LaunchDaemons/com.momentry.postgresql.plist > /dev/null <<'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.postgresql</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/opt/homebrew/opt/postgresql@18/bin/pg_ctl</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>
|
||||
</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>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# 載入服務
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
```
|
||||
|
||||
#### 管理命令
|
||||
```bash
|
||||
# 啟動
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# 停止
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# 重新載入
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
```
|
||||
|
||||
#### 健康檢查
|
||||
```bash
|
||||
# 方法 1: 使用 pg_isready
|
||||
pg_isready -h localhost -p 5432 -U accusys
|
||||
|
||||
# 方法 2: 連線測試
|
||||
psql -U accusys -h localhost -d momentry -c "SELECT 1;"
|
||||
|
||||
# 方法 3: 檢查程序
|
||||
pgrep -f "postgres.*postgresql@18"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Redis
|
||||
|
||||
#### 安裝
|
||||
```bash
|
||||
# 檢查是否已安裝
|
||||
brew list redis 2>/dev/null || echo "Not installed"
|
||||
|
||||
# 安裝 Redis
|
||||
brew install redis
|
||||
```
|
||||
|
||||
#### 設定密碼
|
||||
```bash
|
||||
# 編輯 Redis 設定檔
|
||||
vim /opt/homebrew/etc/redis.conf
|
||||
|
||||
# 找到 requirepass 行,修改為:
|
||||
requirepass accusys
|
||||
|
||||
# 或使用環境變數方式啟動
|
||||
```
|
||||
|
||||
#### 開機自動啟動
|
||||
```bash
|
||||
# 建立 plist 檔案
|
||||
sudo tee /Library/LaunchDaemons/com.momentry.redis.plist > /dev/null <<'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.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>/opt/homebrew/etc/redis.conf</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>REDIS_PASSWORD</key>
|
||||
<string>accusys</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Users/accusys/momentry/log/redis.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Users/accusys/momentry/log/redis.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# 載入服務
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
```
|
||||
|
||||
#### 管理命令
|
||||
```bash
|
||||
# 啟動
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
|
||||
# 停止
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
|
||||
# 重啟
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
|
||||
# 查看狀態
|
||||
launchctl list | grep com.momentry.redis
|
||||
|
||||
# 查看日誌
|
||||
tail -f /Users/accusys/momentry/log/redis.log
|
||||
tail -f /Users/accusys/momentry/log/redis.error.log
|
||||
```
|
||||
|
||||
#### 健康檢查
|
||||
```bash
|
||||
# 方法 1: 使用 redis-cli ping
|
||||
redis-cli -a accusys ping
|
||||
|
||||
# 輸出應為: PONG
|
||||
|
||||
# 方法 2: 檢查密碼認證
|
||||
redis-cli -a accusys AUTH accusys
|
||||
|
||||
# 方法 3: 檢查程序
|
||||
pgrep -f redis-server
|
||||
|
||||
# 方法 4: 檢查連線數
|
||||
redis-cli -a accusys INFO clients
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Ollama
|
||||
|
||||
#### 安裝
|
||||
```bash
|
||||
# 檢查是否已安裝
|
||||
which ollama || echo "Not installed"
|
||||
|
||||
# 安裝 Ollama
|
||||
brew install ollama
|
||||
```
|
||||
|
||||
#### 模型下載
|
||||
```bash
|
||||
# 下載 Mistral (LLM)
|
||||
ollama pull mistral:latest
|
||||
|
||||
# 下載 Embedding 模型
|
||||
ollama pull nomic-embed-text:latest
|
||||
|
||||
# 驗證模型
|
||||
ollama list
|
||||
```
|
||||
|
||||
#### 開機自動啟動
|
||||
```bash
|
||||
# 建立 plist 檔案
|
||||
sudo tee /Library/LaunchDaemons/com.momentry.ollama.plist > /dev/null <<'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.ollama</string>
|
||||
<key>UserName</key>
|
||||
<string>accusys</string>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/Users/accusys/momentry/var/ollama</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/opt/homebrew/bin/ollama</string>
|
||||
<string>serve</string>
|
||||
</array>
|
||||
<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>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Users/accusys/momentry/log/ollama.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Users/accusys/momentry/log/ollama.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# 載入服務
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
```
|
||||
|
||||
#### 管理命令
|
||||
```bash
|
||||
# 啟動
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
|
||||
# 停止
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
|
||||
# 重啟
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
|
||||
# 查看狀態
|
||||
launchctl list | grep com.momentry.ollama
|
||||
|
||||
# 查看日誌
|
||||
tail -f /Users/accusys/momentry/log/ollama.log
|
||||
tail -f /Users/accusys/momentry/log/ollama.error.log
|
||||
```
|
||||
|
||||
#### 健康檢查
|
||||
```bash
|
||||
# 方法 1: API 測試
|
||||
curl -s http://localhost:11434/api/tags | jq '.models[].name'
|
||||
|
||||
# 方法 2: 檢查程序
|
||||
pgrep -f ollama
|
||||
|
||||
# 方法 3: 列出模型
|
||||
ollama list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. n8n (工作流自動化)
|
||||
|
||||
#### 安裝
|
||||
```bash
|
||||
# 檢查是否已安裝
|
||||
which n8n || echo "Not installed"
|
||||
|
||||
# 安裝 n8n
|
||||
brew install n8n
|
||||
```
|
||||
|
||||
#### 開機自動啟動
|
||||
```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/
|
||||
|
||||
# 載入服務
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
```
|
||||
|
||||
#### 管理命令
|
||||
```bash
|
||||
# 啟動
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
|
||||
# 停止
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
```
|
||||
|
||||
#### 健康檢查
|
||||
```bash
|
||||
# 方法 1: API 測試
|
||||
curl -s http://localhost:5678/
|
||||
|
||||
# 方法 2: 檢查程序
|
||||
ps aux | grep n8n | grep -v grep
|
||||
|
||||
# 方法 3: 檢查端口
|
||||
lsof -i :5678
|
||||
lsof -i :5690
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可選服務
|
||||
|
||||
### 4. Caddy (網頁伺服器)
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
brew install caddy
|
||||
|
||||
# 開機啟動
|
||||
cp /opt/homebrew.mxcl.caddy.plist /Library/LaunchDaemons/
|
||||
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.caddy.plist
|
||||
|
||||
# 健康檢查
|
||||
curl -s https://localhost:2019/config/ | head -5
|
||||
```
|
||||
|
||||
### 5. Grafana (監控)
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
brew install grafana
|
||||
|
||||
# 開機啟動
|
||||
cp /opt/homebrew.mxcl.grafana.plist ~/Library/LaunchAgents/
|
||||
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.grafana.plist
|
||||
|
||||
# 健康檢查
|
||||
curl -s http://localhost:3000/api/health | jq '.'
|
||||
```
|
||||
|
||||
### 6. Prometheus (監控)
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
brew install prometheus
|
||||
|
||||
# 開機啟動
|
||||
cp /opt/homebrew.mxcl.prometheus.plist ~/Library/LaunchAgents/
|
||||
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.prometheus.plist
|
||||
|
||||
# 健康檢查
|
||||
curl -s http://localhost:9090/-/healthy
|
||||
```
|
||||
|
||||
### 7. Netdata (系統監控)
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
brew install netdata
|
||||
|
||||
# 開機啟動
|
||||
sudo brew services start netdata
|
||||
|
||||
# 健康檢查
|
||||
curl -s http://localhost:19999/api/v1/info | jq '.version'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 統一健康檢查腳本
|
||||
|
||||
建立 `/Users/accusys/momentry_core_0.1/scripts/health_check.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Momentry 系統健康檢查腳本
|
||||
|
||||
echo "========================================"
|
||||
echo "Momentry 系統健康檢查"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 顏色定義
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
check_service() {
|
||||
local name=$1
|
||||
local check_cmd=$2
|
||||
|
||||
if eval "$check_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
|
||||
|
||||
# 1. PostgreSQL
|
||||
total=$((total + 1))
|
||||
check_service "PostgreSQL (localhost:5432)" "pg_isready -h localhost -p 5432 -U accusys" && passed=$((passed + 1))
|
||||
|
||||
# 2. Redis
|
||||
total=$((total + 1))
|
||||
check_service "Redis (localhost:6379)" "redis-cli -a accusys ping" && passed=$((passed + 1))
|
||||
|
||||
# 3. Ollama
|
||||
total=$((total + 1))
|
||||
check_service "Ollama (localhost:11434)" "curl -s http://localhost:11434/api/tags > /dev/null" && passed=$((passed + 1))
|
||||
|
||||
# 4. n8n
|
||||
total=$((total + 1))
|
||||
check_service "n8n (localhost:5678)" "curl -s http://localhost:5678/ > /dev/null" && passed=$((passed + 1))
|
||||
|
||||
# 5. Grafana (如果安裝)
|
||||
if command -v grafana-server > /dev/null 2>&1; then
|
||||
total=$((total + 1))
|
||||
check_service "Grafana (localhost:3000)" "curl -s http://localhost:3000/api/health > /dev/null" && passed=$((passed + 1))
|
||||
fi
|
||||
|
||||
# 6. Prometheus (如果安裝)
|
||||
if command -v prometheus > /dev/null 2>&1; then
|
||||
total=$((total + 1))
|
||||
check_service "Prometheus (localhost:9090)" "curl -s http://localhost:9090/-/healthy > /dev/null" && passed=$((passed + 1))
|
||||
fi
|
||||
|
||||
# 7. Netdata (如果安裝)
|
||||
if command -v netdata > /dev/null 2>&1; then
|
||||
total=$((total + 1))
|
||||
check_service "Netdata (localhost:19999)" "curl -s http://localhost:19999/api/v1/info > /dev/null" && passed=$((passed + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "結果: $passed/$total 服務正常"
|
||||
echo "========================================"
|
||||
|
||||
if [ $passed -eq $total ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
使用方式:
|
||||
```bash
|
||||
chmod +x scripts/health_check.sh
|
||||
./scripts/health_check.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 服務管理速查表
|
||||
|
||||
### 啟動服務
|
||||
```bash
|
||||
# PostgreSQL (需要 root 權限)
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# Redis, Ollama (需要 root 權限)
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
|
||||
# n8n (需要 root 權限)
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
```
|
||||
|
||||
### 停止服務
|
||||
```bash
|
||||
# PostgreSQL
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist
|
||||
|
||||
# Redis, Ollama
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.ollama.plist
|
||||
|
||||
# n8n
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
|
||||
```
|
||||
|
||||
### 查詢服務狀態
|
||||
```bash
|
||||
# 查看所有服務
|
||||
launchctl list | grep -E "(postgres|redis|ollama|n8n|grafana|prometheus)"
|
||||
|
||||
# 查看特定服務
|
||||
launchctl list | grep com.momentry
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排除
|
||||
|
||||
### PostgreSQL 問題
|
||||
|
||||
```bash
|
||||
# 查看日誌
|
||||
tail -f /opt/homebrew/var/postgresql@18/logfile
|
||||
|
||||
# 重新初始化
|
||||
pg_ctl -D /opt/homebrew/var/postgresql@18 stop
|
||||
rm -rf /opt/homebrew/var/postgresql@18
|
||||
initdb -D /opt/homebrew/var/postgresql@18
|
||||
|
||||
# 重建資料庫
|
||||
dropdb momentry
|
||||
createdb -U accusys momentry
|
||||
```
|
||||
|
||||
### Redis 問題
|
||||
|
||||
```bash
|
||||
# 查看日誌
|
||||
tail -f /opt/homebrew/var/log/redis.log
|
||||
|
||||
# 測試連線
|
||||
redis-cli -a accusys DEBUG SLEEP 1
|
||||
|
||||
# 重新整理 ACL
|
||||
redis-cli -a accusys FLUSHALL
|
||||
```
|
||||
|
||||
### Ollama 問題
|
||||
|
||||
```bash
|
||||
# 查看日誌
|
||||
tail -f ~/.ollama/logs/server.log
|
||||
|
||||
# 重新下載模型
|
||||
ollama pull mistral:latest
|
||||
ollama pull nomic-embed-text:latest
|
||||
|
||||
# 檢查 GPU 使用情況
|
||||
ollama list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自動化腳本
|
||||
|
||||
建立 `/Users/accusys/momentry_core_0.1/scripts/service_manager.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLIST_DIR="$SCRIPT_DIR/../momentry_runtime/plist"
|
||||
|
||||
action=${1:-start}
|
||||
service=${2:-all}
|
||||
|
||||
start_postgresql() {
|
||||
echo "Starting PostgreSQL..."
|
||||
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist 2>/dev/null || \
|
||||
echo "PostgreSQL plist not found, skipping..."
|
||||
}
|
||||
|
||||
start_redis() {
|
||||
echo "Starting Redis..."
|
||||
launchctl load ~/Library/LaunchAgents/com.momentry.redis.plist 2>/dev/null || \
|
||||
echo "Redis plist not found, skipping..."
|
||||
}
|
||||
|
||||
start_ollama() {
|
||||
echo "Starting Ollama..."
|
||||
launchctl load ~/Library/LaunchAgents/com.momentry.ollama.plist 2>/dev/null || \
|
||||
echo "Ollama plist not found, skipping..."
|
||||
}
|
||||
|
||||
stop_postgresql() {
|
||||
echo "Stopping PostgreSQL..."
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.momentry.postgresql.plist 2>/dev/null || true
|
||||
}
|
||||
|
||||
stop_redis() {
|
||||
echo "Stopping Redis..."
|
||||
launchctl unload ~/Library/LaunchAgents/com.momentry.redis.plist 2>/dev/null || true
|
||||
}
|
||||
|
||||
stop_ollama() {
|
||||
echo "Stopping Ollama..."
|
||||
launchctl unload ~/Library/LaunchAgents/com.momentry.ollama.plist 2>/dev/null || true
|
||||
}
|
||||
|
||||
case $action in
|
||||
start)
|
||||
case $service in
|
||||
all)
|
||||
start_postgresql
|
||||
start_redis
|
||||
start_ollama
|
||||
;;
|
||||
postgresql|pgsql|pg)
|
||||
start_postgresql
|
||||
;;
|
||||
redis)
|
||||
start_redis
|
||||
;;
|
||||
ollama)
|
||||
start_ollama
|
||||
;;
|
||||
*)
|
||||
echo "Unknown service: $service"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
case $service in
|
||||
all)
|
||||
stop_ollama
|
||||
stop_redis
|
||||
stop_postgresql
|
||||
;;
|
||||
postgresql|pgsql|pg)
|
||||
stop_postgresql
|
||||
;;
|
||||
redis)
|
||||
stop_redis
|
||||
;;
|
||||
ollama)
|
||||
stop_ollama
|
||||
;;
|
||||
*)
|
||||
echo "Unknown service: $service"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
restart)
|
||||
$0 stop $service
|
||||
sleep 2
|
||||
$0 start $service
|
||||
;;
|
||||
status)
|
||||
echo "Service Status:"
|
||||
echo "==============="
|
||||
launchctl list | grep -E "(postgres|redis|ollama)" || echo "No services found"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status} [service]"
|
||||
echo "Services: all, postgresql, redis, ollama"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附錄: 服務對應表
|
||||
|
||||
| 服務 | Port | 使用者 | plist 位置 |
|
||||
|------|------|--------|-------------|
|
||||
| PostgreSQL | 5432 | accusys | /Library/LaunchDaemons/ |
|
||||
| Redis | 6379 | accusys | /Library/LaunchDaemons/ |
|
||||
| Ollama | 11434 | accusys | /Library/LaunchDaemons/ |
|
||||
| n8n Main | 5678 | accusys | /Library/LaunchDaemons/ |
|
||||
| n8n Worker | 5690-5691 | accusys | /Library/LaunchDaemons/ |
|
||||
| Grafana | 3000 | accusys | /Library/LaunchDaemons/ |
|
||||
| Prometheus | 9090 | accusys | /Library/LaunchDaemons/ |
|
||||
| Caddy | 2019 | root | /Library/LaunchDaemons/ |
|
||||
| Netdata | 19999 | root | /Library/LaunchDaemons/ |
|
||||
659
docs/SERVICE_ADDITION_GUIDE.md
Normal file
659
docs/SERVICE_ADDITION_GUIDE.md
Normal file
@@ -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 | 新增服務備份作業、服務完整刪除作業 |
|
||||
32
momentry_runtime/build.sh
Executable file
32
momentry_runtime/build.sh
Executable file
@@ -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"
|
||||
86
momentry_runtime/check.sh
Executable file
86
momentry_runtime/check.sh
Executable file
@@ -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)
|
||||
65
momentry_runtime/env/momentry.env
vendored
Normal file
65
momentry_runtime/env/momentry.env
vendored
Normal file
@@ -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
|
||||
47
momentry_runtime/plist/com.momentry.agent.plist
Normal file
47
momentry_runtime/plist/com.momentry.agent.plist
Normal file
@@ -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>
|
||||
42
momentry_runtime/plist/com.momentry.caddy.plist
Normal file
42
momentry_runtime/plist/com.momentry.caddy.plist
Normal file
@@ -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>
|
||||
44
momentry_runtime/plist/com.momentry.gitea.plist
Normal file
44
momentry_runtime/plist/com.momentry.gitea.plist
Normal file
@@ -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>
|
||||
32
momentry_runtime/plist/com.momentry.mariadb.plist
Normal file
32
momentry_runtime/plist/com.momentry.mariadb.plist
Normal file
@@ -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>
|
||||
39
momentry_runtime/plist/com.momentry.mongodb.plist
Normal file
39
momentry_runtime/plist/com.momentry.mongodb.plist
Normal file
@@ -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>
|
||||
49
momentry_runtime/plist/com.momentry.monitor.plist
Normal file
49
momentry_runtime/plist/com.momentry.monitor.plist
Normal file
@@ -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>
|
||||
102
momentry_runtime/plist/com.momentry.n8n.main.plist
Normal file
102
momentry_runtime/plist/com.momentry.n8n.main.plist
Normal file
@@ -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>
|
||||
105
momentry_runtime/plist/com.momentry.n8n.worker.plist
Normal file
105
momentry_runtime/plist/com.momentry.n8n.worker.plist
Normal file
@@ -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>
|
||||
44
momentry_runtime/plist/com.momentry.ollama.plist
Normal file
44
momentry_runtime/plist/com.momentry.ollama.plist
Normal file
@@ -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>
|
||||
34
momentry_runtime/plist/com.momentry.php.plist
Normal file
34
momentry_runtime/plist/com.momentry.php.plist
Normal file
@@ -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>
|
||||
39
momentry_runtime/plist/com.momentry.postgresql.plist
Normal file
39
momentry_runtime/plist/com.momentry.postgresql.plist
Normal file
@@ -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>
|
||||
46
momentry_runtime/plist/com.momentry.qdrant.plist
Normal file
46
momentry_runtime/plist/com.momentry.qdrant.plist
Normal file
@@ -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>
|
||||
41
momentry_runtime/plist/com.momentry.redis.plist
Normal file
41
momentry_runtime/plist/com.momentry.redis.plist
Normal file
@@ -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>
|
||||
33
momentry_runtime/plist/com.momentry.rustdesk.hbbr.plist
Normal file
33
momentry_runtime/plist/com.momentry.rustdesk.hbbr.plist
Normal file
@@ -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>
|
||||
33
momentry_runtime/plist/com.momentry.rustdesk.hbbs.plist
Normal file
33
momentry_runtime/plist/com.momentry.rustdesk.hbbs.plist
Normal file
@@ -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>
|
||||
42
momentry_runtime/plist/com.momentry.sftpgo.plist
Normal file
42
momentry_runtime/plist/com.momentry.sftpgo.plist
Normal file
@@ -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>
|
||||
39
momentry_runtime/plist/template.service.plist
Normal file
39
momentry_runtime/plist/template.service.plist
Normal file
@@ -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>
|
||||
59
momentry_runtime/start.sh
Executable file
59
momentry_runtime/start.sh
Executable file
@@ -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)"
|
||||
25
momentry_runtime/stop.sh
Executable file
25
momentry_runtime/stop.sh
Executable file
@@ -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
|
||||
366
monitor/MONITORING.md
Normal file
366
monitor/MONITORING.md
Normal file
@@ -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)
|
||||
388
monitor/SKILL_TROUBLESHOOTING.md
Normal file
388
monitor/SKILL_TROUBLESHOOTING.md
Normal file
@@ -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/`
|
||||
503
monitor/config/monitor_config.yaml
Normal file
503
monitor/config/monitor_config.yaml
Normal file
@@ -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
|
||||
362
monitor/control/monitor_control.sh
Executable file
362
monitor/control/monitor_control.sh
Executable file
@@ -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 "$@"
|
||||
82
monitor/database/mongodb_monitor.sh
Executable file
82
monitor/database/mongodb_monitor.sh
Executable file
@@ -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 "========================================"
|
||||
130
monitor/database/postgres_monitor.sh
Executable file
130
monitor/database/postgres_monitor.sh
Executable file
@@ -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"
|
||||
124
monitor/database/qdrant_monitor.sh
Executable file
124
monitor/database/qdrant_monitor.sh
Executable file
@@ -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"
|
||||
111
monitor/database/redis_monitor.sh
Executable file
111
monitor/database/redis_monitor.sh
Executable file
@@ -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"
|
||||
492
monitor/database/schema.sql
Normal file
492
monitor/database/schema.sql
Normal file
@@ -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);
|
||||
175
monitor/portal/page_monitor.sh
Executable file
175
monitor/portal/page_monitor.sh
Executable file
@@ -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"
|
||||
93
monitor/service/external_monitor.sh
Executable file
93
monitor/service/external_monitor.sh
Executable file
@@ -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"
|
||||
370
monitor/service/health_check.sh
Executable file
370
monitor/service/health_check.sh
Executable file
@@ -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"
|
||||
270
monitor/service/node_monitor.sh
Executable file
270
monitor/service/node_monitor.sh
Executable file
@@ -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
|
||||
281
monitor/service/python_monitor.sh
Executable file
281
monitor/service/python_monitor.sh
Executable file
@@ -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
|
||||
375
monitor/storage/backup_monitor.sh
Executable file
375
monitor/storage/backup_monitor.sh
Executable file
@@ -0,0 +1,375 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Momentry 備份監控與溫冷轉移
|
||||
# 路徑: /Users/accusys/momentry_core_0.1/monitor/storage/backup_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/backup_check.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 備份根目錄
|
||||
BACKUP_BASE="/Users/accusys/momentry/backup"
|
||||
|
||||
# 服務列表
|
||||
SERVICES=("postgresql" "redis" "mariadb" "n8n" "qdrant" "gitea" "ollama" "caddy" "mongodb" "sftpgo" "php")
|
||||
|
||||
# 溫冷分層配置
|
||||
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
|
||||
# 從文件名提取日期
|
||||
file_date=$(echo "$latest_file" | sed 's/.*\([0-9]\{8\}\).*/\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"
|
||||
}
|
||||
|
||||
# 溫冷轉移 - 將舊備份移動到低成本存儲
|
||||
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="/Users/accusys/momentry/log/backup_report_$(date +%Y%m%d).txt"
|
||||
|
||||
{
|
||||
echo "========================================"
|
||||
echo "Momentry 備份報告"
|
||||
echo "生成時間: $(date)"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
echo "## 備份狀態"
|
||||
check_backup_status
|
||||
|
||||
echo ""
|
||||
echo "## 存儲使用趨勢 (最近30天)"
|
||||
psql -U accusys -h localhost -d momentry -t -A -c "
|
||||
SELECT tier,
|
||||
COUNT(*) as files,
|
||||
AVG(total_size_bytes)::bigint as avg_size,
|
||||
MAX(total_size_bytes)::bigint as max_size
|
||||
FROM backup_storage_stats
|
||||
WHERE record_time > NOW() - INTERVAL '30 days'
|
||||
GROUP BY tier
|
||||
ORDER BY tier;
|
||||
" 2>/dev/null || echo " (無數據)"
|
||||
|
||||
echo ""
|
||||
echo "## 建議"
|
||||
|
||||
# 檢查是否有服務超過7天未備份
|
||||
for service in "${SERVICES[@]}"; do
|
||||
latest=$(find "$BACKUP_BASE/daily/$service" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$latest" ]; then
|
||||
days_old=$(($(date +%s) - $(stat -f "%m" "$latest" 2>/dev/null || echo "0")) / 86400)
|
||||
if [ "$days_old" -gt 7 ]; then
|
||||
echo " - ⚠️ $service 超過 $days_old 天未備份,建議立即執行備份"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
} > "$report_file"
|
||||
|
||||
log "報告已生成: $report_file"
|
||||
echo "$report_file"
|
||||
}
|
||||
|
||||
# 主程序
|
||||
command=${1:-status}
|
||||
|
||||
case $command in
|
||||
status)
|
||||
check_backup_status
|
||||
;;
|
||||
init)
|
||||
init_backup_dirs
|
||||
;;
|
||||
tier)
|
||||
tier_backups
|
||||
;;
|
||||
cleanup)
|
||||
cleanup_old
|
||||
;;
|
||||
verify)
|
||||
verify_backup "${2:-}"
|
||||
;;
|
||||
report)
|
||||
generate_report
|
||||
;;
|
||||
all)
|
||||
log "執行完整備份維護..."
|
||||
check_backup_status
|
||||
tier_backups
|
||||
cleanup_old
|
||||
generate_report
|
||||
log "備份維護完成"
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {status|init|tier|cleanup|verify|report|all}"
|
||||
echo ""
|
||||
echo " status - 檢查備份狀態"
|
||||
echo " init - 初始化備份目錄"
|
||||
echo " tier - 執行溫冷轉移"
|
||||
echo " cleanup - 清理過期備份"
|
||||
echo " verify - 驗證備份完整性"
|
||||
echo " report - 生成備份報告"
|
||||
echo " all - 執行所有維護任務"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
193
monitor/storage/storage_manager.sh
Executable file
193
monitor/storage/storage_manager.sh
Executable file
@@ -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
|
||||
163
monitor/users/session_tracker.sh
Executable file
163
monitor/users/session_tracker.sh
Executable file
@@ -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"
|
||||
265
monitor/workflow/n8n_workflow_monitor.sh
Executable file
265
monitor/workflow/n8n_workflow_monitor.sh
Executable file
@@ -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
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
faster-whisper>=1.0.0
|
||||
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
edition = "2021"
|
||||
max_width = 100
|
||||
tab_spaces = 4
|
||||
53
scripts/asr_processor.py
Normal file
53
scripts/asr_processor.py
Normal file
@@ -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])
|
||||
78
scripts/install_mongodb.sh
Executable file
78
scripts/install_mongodb.sh
Executable file
@@ -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)"
|
||||
97
scripts/thumbnail_extractor.py
Normal file
97
scripts/thumbnail_extractor.py
Normal file
@@ -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()
|
||||
151
scripts/troubleshoot.sh
Executable file
151
scripts/troubleshoot.sh
Executable file
@@ -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
|
||||
3
src/api/mod.rs
Normal file
3
src/api/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod server;
|
||||
|
||||
pub use server::start_server;
|
||||
20
src/api/server.rs
Normal file
20
src/api/server.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Placeholder for API server
|
||||
|
||||
pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
|
||||
println!("Starting API server at {}:{}", host, port);
|
||||
// TODO: Implement Axum server
|
||||
//
|
||||
// Routes:
|
||||
// POST /api/v1/register
|
||||
// POST /api/v1/process
|
||||
// POST /api/v1/chunk
|
||||
// POST /api/v1/vectorize
|
||||
// POST /api/v1/store
|
||||
// POST /api/v1/watch
|
||||
// DELETE /api/v1/watch/{path}
|
||||
// GET /api/v1/lookup?path=...
|
||||
// GET /api/v1/resolve?uuid=...
|
||||
// GET /api/v1/status/{uuid}
|
||||
// POST /api/v1/query
|
||||
Ok(())
|
||||
}
|
||||
5
src/core/chunk/mod.rs
Normal file
5
src/core/chunk/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod splitter;
|
||||
pub mod types;
|
||||
|
||||
pub use splitter::{AsrSegment, ChunkSplitter};
|
||||
pub use types::{Chunk, ChunkType};
|
||||
67
src/core/chunk/splitter.rs
Normal file
67
src/core/chunk/splitter.rs
Normal file
@@ -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>,
|
||||
}
|
||||
52
src/core/chunk/types.rs
Normal file
52
src/core/chunk/types.rs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/core/db/mod.rs
Normal file
40
src/core/db/mod.rs
Normal file
@@ -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;
|
||||
53
src/core/db/mongodb_db.rs
Normal file
53
src/core/db/mongodb_db.rs
Normal file
@@ -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())),
|
||||
})
|
||||
}
|
||||
}
|
||||
286
src/core/db/postgres_db.rs
Normal file
286
src/core/db/postgres_db.rs
Normal file
@@ -0,0 +1,286 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{PgPool, Row};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::Database;
|
||||
use crate::core::chunk::{Chunk, ChunkType};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VideoRecord {
|
||||
pub id: i64,
|
||||
pub uuid: String,
|
||||
pub file_path: String,
|
||||
pub file_name: String,
|
||||
pub duration: f64,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub fps: f64,
|
||||
pub probe_json: Option<String>,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
pub struct PostgresDb {
|
||||
pool: PgPool,
|
||||
cache: Arc<RwLock<PostgresCache>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PostgresCache {
|
||||
videos: std::collections::HashMap<String, VideoRecord>,
|
||||
chunks: std::collections::HashMap<String, Vec<Chunk>>,
|
||||
}
|
||||
|
||||
impl PostgresDb {
|
||||
pub async fn new(database_url: &str) -> Result<Self> {
|
||||
let pool = PgPool::connect(database_url).await?;
|
||||
|
||||
let db = Self {
|
||||
pool,
|
||||
cache: Arc::new(RwLock::new(PostgresCache::default())),
|
||||
};
|
||||
|
||||
db.init_schema().await?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub async fn register_video(&self, record: &VideoRecord) -> Result<i64> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO videos (uuid, file_path, file_name, duration, width, height, fps, probe_json)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (uuid) DO UPDATE SET
|
||||
file_path = EXCLUDED.file_path,
|
||||
file_name = EXCLUDED.file_name,
|
||||
duration = EXCLUDED.duration,
|
||||
width = EXCLUDED.width,
|
||||
height = EXCLUDED.height,
|
||||
fps = EXCLUDED.fps,
|
||||
probe_json = EXCLUDED.probe_json,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
RETURNING id::bigint
|
||||
"#
|
||||
)
|
||||
.bind(&record.uuid)
|
||||
.bind(&record.file_path)
|
||||
.bind(&record.file_name)
|
||||
.bind(record.duration)
|
||||
.bind(record.width as i32)
|
||||
.bind(record.height as i32)
|
||||
.bind(record.fps)
|
||||
.bind(&record.probe_json)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
||||
let id: i64 = result.get(0);
|
||||
|
||||
// Update cache
|
||||
let mut cache = self.cache.write().await;
|
||||
let mut record = record.clone();
|
||||
record.id = id as i64;
|
||||
cache.videos.insert(record.uuid.clone(), record);
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub async fn get_video_by_uuid(&self, uuid: &str) -> Result<Option<VideoRecord>> {
|
||||
// Check cache first
|
||||
{
|
||||
let cache = self.cache.read().await;
|
||||
if let Some(video) = cache.videos.get(uuid) {
|
||||
return Ok(Some(video.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let result = sqlx::query_as::<_, (i32, String, String, String, f64, i32, i32, f64, Option<String>)>(
|
||||
"SELECT id, uuid, file_path, file_name, duration, width, height, fps, probe_json FROM videos WHERE uuid = $1"
|
||||
)
|
||||
.bind(uuid)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
if let Some(r) = result {
|
||||
let video = VideoRecord {
|
||||
id: r.0 as i64,
|
||||
uuid: r.1,
|
||||
file_path: r.2,
|
||||
file_name: r.3,
|
||||
duration: r.4,
|
||||
width: r.5 as u32,
|
||||
height: r.6 as u32,
|
||||
fps: r.7,
|
||||
probe_json: r.8,
|
||||
created_at: String::new(),
|
||||
};
|
||||
|
||||
// Update cache
|
||||
let mut cache = self.cache.write().await;
|
||||
cache.videos.insert(uuid.to_string(), video.clone());
|
||||
|
||||
Ok(Some(video))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_videos(&self) -> Result<Vec<VideoRecord>> {
|
||||
let rows = sqlx::query_as::<_, (i32, String, String, String, f64, i32, i32, f64, Option<String>)>(
|
||||
"SELECT id, uuid, file_path, file_name, duration, width, height, fps, probe_json FROM videos ORDER BY id DESC"
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
let videos: Vec<VideoRecord> = rows
|
||||
.into_iter()
|
||||
.map(|r| VideoRecord {
|
||||
id: r.0 as i64,
|
||||
uuid: r.1,
|
||||
file_path: r.2,
|
||||
file_name: r.3,
|
||||
duration: r.4,
|
||||
width: r.5 as u32,
|
||||
height: r.6 as u32,
|
||||
fps: r.7,
|
||||
probe_json: r.8,
|
||||
created_at: String::new(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(videos)
|
||||
}
|
||||
|
||||
async fn init_schema(&self) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS videos (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(32) UNIQUE NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
duration DOUBLE PRECISION,
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
fps DOUBLE PRECISION,
|
||||
probe_json TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_videos_uuid ON videos(uuid)")
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS chunks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(32) 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,
|
||||
vector_id VARCHAR(64),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(uuid, chunk_id)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_chunks_uuid ON chunks(uuid)")
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_chunks_type ON chunks(chunk_type)")
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_chunks_time ON chunks(start_time, end_time)")
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn store_chunk(&self, chunk: &Chunk) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO chunks (uuid, chunk_id, chunk_index, chunk_type, start_time, end_time, content)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)
|
||||
ON CONFLICT (uuid, chunk_id) DO UPDATE SET
|
||||
start_time = EXCLUDED.start_time,
|
||||
end_time = EXCLUDED.end_time,
|
||||
content = EXCLUDED.content,
|
||||
vector_id = EXCLUDED.vector_id
|
||||
"#
|
||||
)
|
||||
.bind(&chunk.uuid)
|
||||
.bind(&chunk.chunk_id)
|
||||
.bind(chunk.chunk_index as i32)
|
||||
.bind(chunk.chunk_type.as_str())
|
||||
.bind(chunk.start_time)
|
||||
.bind(chunk.end_time)
|
||||
.bind(&chunk.content)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_chunks_by_uuid(&self, uuid: &str) -> Result<Vec<Chunk>> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT uuid, chunk_id, chunk_index, chunk_type, start_time, end_time, content FROM chunks WHERE uuid = $1 ORDER BY chunk_index"
|
||||
)
|
||||
.bind(uuid)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
let chunks: Vec<Chunk> = rows
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let chunk_type_str: String = r.get(3);
|
||||
let chunk_index: i32 = r.get(2);
|
||||
let chunk_type = match chunk_type_str.as_str() {
|
||||
"time_based" => ChunkType::TimeBased,
|
||||
"sentence" => ChunkType::Sentence,
|
||||
"cut" => ChunkType::Cut,
|
||||
_ => ChunkType::TimeBased,
|
||||
};
|
||||
|
||||
let content_json: String = r.get(6);
|
||||
let content: serde_json::Value =
|
||||
serde_json::from_str(&content_json).unwrap_or(serde_json::json!({}));
|
||||
|
||||
Chunk {
|
||||
uuid: r.get(0),
|
||||
chunk_id: r.get(1),
|
||||
chunk_index: chunk_index as u32,
|
||||
chunk_type,
|
||||
start_time: r.get(4),
|
||||
end_time: r.get(5),
|
||||
content,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(chunks)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for PostgresDb {
|
||||
async fn init() -> Result<Self> {
|
||||
let database_url = std::env::var("DATABASE_URL")
|
||||
.unwrap_or_else(|_| "postgres://accusys@localhost:5432/momentry".to_string());
|
||||
Self::new(&database_url).await
|
||||
}
|
||||
}
|
||||
88
src/core/db/qdrant_db.rs
Normal file
88
src/core/db/qdrant_db.rs
Normal file
@@ -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)
|
||||
}
|
||||
65
src/core/db/redis_db.rs
Normal file
65
src/core/db/redis_db.rs
Normal file
@@ -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())),
|
||||
})
|
||||
}
|
||||
}
|
||||
66
src/core/embedding/comic_embed.rs
Normal file
66
src/core/embedding/comic_embed.rs
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
3
src/core/embedding/mod.rs
Normal file
3
src/core/embedding/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod comic_embed;
|
||||
|
||||
pub use comic_embed::Embedder;
|
||||
8
src/core/mod.rs
Normal file
8
src/core/mod.rs
Normal file
@@ -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;
|
||||
3
src/core/overlay/mod.rs
Normal file
3
src/core/overlay/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod types;
|
||||
|
||||
pub use types::OverlayFlags;
|
||||
41
src/core/overlay/types.rs
Normal file
41
src/core/overlay/types.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub struct OverlayFlags {
|
||||
pub asr: bool,
|
||||
pub asrx: bool,
|
||||
pub ocr: bool,
|
||||
pub yolo: bool,
|
||||
pub face: bool,
|
||||
pub pose: bool,
|
||||
pub status: bool,
|
||||
}
|
||||
|
||||
impl Default for OverlayFlags {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
asr: false,
|
||||
asrx: false,
|
||||
ocr: false,
|
||||
yolo: false,
|
||||
face: false,
|
||||
pose: false,
|
||||
status: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlayFlags {
|
||||
pub fn toggle(&mut self, layer: char) {
|
||||
match layer {
|
||||
'a' => self.asr = !self.asr,
|
||||
'x' => self.asrx = !self.asrx,
|
||||
'o' => self.ocr = !self.ocr,
|
||||
'y' => self.yolo = !self.yolo,
|
||||
'f' => self.face = !self.face,
|
||||
'p' => self.pose = !self.pose,
|
||||
'b' => self.status = !self.status,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/core/probe/mod.rs
Normal file
3
src/core/probe/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod probe;
|
||||
|
||||
pub use probe::{probe_video, ProbeResult};
|
||||
84
src/core/probe/probe.rs
Normal file
84
src/core/probe/probe.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ProbeResult {
|
||||
pub streams: Vec<StreamInfo>,
|
||||
pub format: FormatInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct StreamInfo {
|
||||
pub index: u32,
|
||||
pub codec_name: Option<String>,
|
||||
pub codec_type: Option<String>,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub r_frame_rate: Option<String>,
|
||||
pub duration: Option<String>,
|
||||
pub sample_rate: Option<String>,
|
||||
pub channels: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FormatInfo {
|
||||
pub filename: Option<String>,
|
||||
pub format_name: Option<String>,
|
||||
pub duration: Option<String>,
|
||||
pub size: Option<String>,
|
||||
pub bit_rate: Option<String>,
|
||||
}
|
||||
|
||||
pub fn probe_video(video_path: &str) -> Result<ProbeResult> {
|
||||
let output = Command::new("ffprobe")
|
||||
.args([
|
||||
"-v",
|
||||
"quiet",
|
||||
"-print_format",
|
||||
"json",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
video_path,
|
||||
])
|
||||
.output()
|
||||
.context("Failed to run ffprobe")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("ffprobe failed: {}", stderr);
|
||||
}
|
||||
|
||||
let json_str = String::from_utf8_lossy(&output.stdout);
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_str(&json_str).context("Failed to parse ffprobe output")?;
|
||||
|
||||
let streams: Vec<StreamInfo> = json["streams"]
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.map(|s| StreamInfo {
|
||||
index: s["index"].as_u64().unwrap_or(0) as u32,
|
||||
codec_name: s["codec_name"].as_str().map(String::from),
|
||||
codec_type: s["codec_type"].as_str().map(String::from),
|
||||
width: s["width"].as_u64().map(|v| v as u32),
|
||||
height: s["height"].as_u64().map(|v| v as u32),
|
||||
r_frame_rate: s["r_frame_rate"].as_str().map(String::from),
|
||||
duration: s["duration"].as_str().map(String::from),
|
||||
sample_rate: s["sample_rate"].as_str().map(String::from),
|
||||
channels: s["channels"].as_u64().map(|v| v as u32),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let format = FormatInfo {
|
||||
filename: json["format"]["filename"].as_str().map(String::from),
|
||||
format_name: json["format"]["format_name"].as_str().map(String::from),
|
||||
duration: json["format"]["duration"].as_str().map(String::from),
|
||||
size: json["format"]["size"].as_str().map(String::from),
|
||||
bit_rate: json["format"]["bit_rate"].as_str().map(String::from),
|
||||
};
|
||||
|
||||
Ok(ProbeResult { streams, format })
|
||||
}
|
||||
73
src/core/processor/asr.rs
Normal file
73
src/core/processor/asr.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AsrResult {
|
||||
pub language: Option<String>,
|
||||
pub language_probability: Option<f64>,
|
||||
pub segments: Vec<AsrSegment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AsrSegment {
|
||||
pub start: f64,
|
||||
pub end: f64,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("venv")
|
||||
.join("bin")
|
||||
.join("python");
|
||||
|
||||
println!("[ASR] Starting ASR processing...");
|
||||
println!("[ASR] Video: {}", video_path);
|
||||
|
||||
let output = Command::new(venv_python)
|
||||
.arg(script_path)
|
||||
.arg(video_path)
|
||||
.arg(output_path)
|
||||
.output()
|
||||
.context("Failed to run ASR processor")?;
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stderr.lines() {
|
||||
if line.starts_with("ASR_START") {
|
||||
println!("[ASR] Loading model...");
|
||||
} else if line.starts_with("ASR_LANGUAGE:") {
|
||||
let lang = line.trim_start_matches("ASR_LANGUAGE:");
|
||||
println!("[ASR] Detected language: {}", lang);
|
||||
} else if line.starts_with("ASR_PROGRESS:") {
|
||||
let count = line.trim_start_matches("ASR_PROGRESS:");
|
||||
println!("[ASR] Processed {} segments...", count);
|
||||
} else if line.starts_with("ASR_COMPLETE:") {
|
||||
let count = line.trim_start_matches("ASR_COMPLETE:");
|
||||
println!("[ASR] Completed! Total: {} segments", count);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::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")?;
|
||||
|
||||
println!(
|
||||
"[ASR] Result: {} segments, language: {:?}",
|
||||
result.segments.len(),
|
||||
result.language
|
||||
);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
28
src/core/processor/asrx.rs
Normal file
28
src/core/processor/asrx.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AsrxResult {
|
||||
pub segments: Vec<AsrxSegment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AsrxSegment {
|
||||
pub start: f64,
|
||||
pub end: f64,
|
||||
pub text: String,
|
||||
pub speaker_id: String,
|
||||
pub speaker_embedding: Option<Vec<f32>>,
|
||||
}
|
||||
|
||||
pub async fn process_asrx(video_path: &str, output_path: &str) -> Result<AsrxResult> {
|
||||
// TODO: Implement speaker diarization
|
||||
// Options:
|
||||
// 1. Use pyannote.audio
|
||||
// 2. Use whisperx
|
||||
// 3. Use Python subprocess
|
||||
|
||||
println!("Processing speaker diarization for: {}", video_path);
|
||||
|
||||
Ok(AsrxResult { segments: vec![] })
|
||||
}
|
||||
36
src/core/processor/face.rs
Normal file
36
src/core/processor/face.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FaceResult {
|
||||
pub frames: Vec<FaceFrame>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FaceFrame {
|
||||
pub frame: u64,
|
||||
pub timestamp: f64,
|
||||
pub faces: Vec<Face>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Face {
|
||||
pub face_id: String,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub confidence: f32,
|
||||
pub embedding: Option<Vec<f32>>,
|
||||
}
|
||||
|
||||
pub async fn process_face(video_path: &str, output_path: &str) -> Result<FaceResult> {
|
||||
// TODO: Implement face detection
|
||||
// Options:
|
||||
// 1. Use MTCNN or RetinaFace with ONNX
|
||||
// 2. Use Python subprocess
|
||||
|
||||
println!("Processing face detection for: {}", video_path);
|
||||
|
||||
Ok(FaceResult { frames: vec![] })
|
||||
}
|
||||
13
src/core/processor/mod.rs
Normal file
13
src/core/processor/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub mod asr;
|
||||
pub mod asrx;
|
||||
pub mod face;
|
||||
pub mod ocr;
|
||||
pub mod pose;
|
||||
pub mod yolo;
|
||||
|
||||
pub use asr::{process_asr, AsrResult, AsrSegment};
|
||||
pub use asrx::process_asrx;
|
||||
pub use face::process_face;
|
||||
pub use ocr::process_ocr;
|
||||
pub use pose::process_pose;
|
||||
pub use yolo::process_yolo;
|
||||
36
src/core/processor/ocr.rs
Normal file
36
src/core/processor/ocr.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct OcrResult {
|
||||
pub frames: Vec<OcrFrame>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct OcrFrame {
|
||||
pub frame: u64,
|
||||
pub timestamp: f64,
|
||||
pub texts: Vec<OcrText>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct OcrText {
|
||||
pub text: String,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
pub async fn process_ocr(video_path: &str, output_path: &str) -> Result<OcrResult> {
|
||||
// TODO: Implement OCR processing
|
||||
// Options:
|
||||
// 1. Use tesseract
|
||||
// 2. Use Python pytesseract via subprocess
|
||||
// 3. Use Rust OCR library
|
||||
|
||||
println!("Processing OCR for: {}", video_path);
|
||||
|
||||
Ok(OcrResult { frames: vec![] })
|
||||
}
|
||||
47
src/core/processor/pose.rs
Normal file
47
src/core/processor/pose.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PoseResult {
|
||||
pub frames: Vec<PoseFrame>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PoseFrame {
|
||||
pub frame: u64,
|
||||
pub timestamp: f64,
|
||||
pub persons: Vec<PersonPose>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PersonPose {
|
||||
pub keypoints: Vec<Keypoint>,
|
||||
pub bbox: Bbox,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Keypoint {
|
||||
pub name: String,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Bbox {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
pub async fn process_pose(video_path: &str, output_path: &str) -> Result<PoseResult> {
|
||||
// TODO: Implement pose estimation
|
||||
// Options:
|
||||
// 1. Use MoveNet or PoseNet with ONNX
|
||||
// 2. Use Python subprocess with ultralytics
|
||||
|
||||
println!("Processing pose estimation for: {}", video_path);
|
||||
|
||||
Ok(PoseResult { frames: vec![] })
|
||||
}
|
||||
36
src/core/processor/yolo.rs
Normal file
36
src/core/processor/yolo.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct YoloResult {
|
||||
pub frames: Vec<YoloFrame>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct YoloFrame {
|
||||
pub frame: u64,
|
||||
pub timestamp: f64,
|
||||
pub objects: Vec<YoloObject>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct YoloObject {
|
||||
pub class_name: String,
|
||||
pub class_id: u32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
pub async fn process_yolo(video_path: &str, output_path: &str) -> Result<YoloResult> {
|
||||
// TODO: Implement YOLO processing
|
||||
// Options:
|
||||
// 1. Use ONNX Runtime (ort) with YOLO model
|
||||
// 2. Use Python subprocess with ultralytics
|
||||
|
||||
println!("Processing YOLO for: {}", video_path);
|
||||
|
||||
Ok(YoloResult { frames: vec![] })
|
||||
}
|
||||
57
src/core/storage/file_manager.rs
Normal file
57
src/core/storage/file_manager.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct FileManager {
|
||||
base_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl FileManager {
|
||||
pub fn new(base_dir: PathBuf) -> Self {
|
||||
Self { base_dir }
|
||||
}
|
||||
|
||||
pub fn get_json_path(&self, uuid: &str, suffix: &str) -> PathBuf {
|
||||
self.base_dir.join(format!("{}.{}.json", uuid, suffix))
|
||||
}
|
||||
|
||||
pub fn save_json(&self, uuid: &str, suffix: &str, content: &str) -> Result<PathBuf> {
|
||||
let path = self.get_json_path(uuid, suffix);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::write(&path, content)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn load_json(&self, uuid: &str, suffix: &str) -> Result<String> {
|
||||
let path = self.get_json_path(uuid, suffix);
|
||||
Ok(fs::read_to_string(path)?)
|
||||
}
|
||||
|
||||
pub fn exists(&self, uuid: &str, suffix: &str) -> bool {
|
||||
self.get_json_path(uuid, suffix).exists()
|
||||
}
|
||||
|
||||
pub fn list_video_files(dir: &Path) -> Result<Vec<PathBuf>> {
|
||||
let mut videos = Vec::new();
|
||||
|
||||
if !dir.exists() {
|
||||
return Ok(videos);
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if let Some(ext) = path.extension() {
|
||||
let ext = ext.to_string_lossy().to_lowercase();
|
||||
if matches!(ext.as_str(), "mp4" | "mkv" | "avi" | "mov" | "webm" | "m4v") {
|
||||
videos.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(videos)
|
||||
}
|
||||
}
|
||||
5
src/core/storage/mod.rs
Normal file
5
src/core/storage/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod file_manager;
|
||||
pub mod uuid;
|
||||
|
||||
pub use file_manager::FileManager;
|
||||
pub use uuid::compute_uuid;
|
||||
44
src/core/storage/uuid.rs
Normal file
44
src/core/storage/uuid.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Compute UUID from file path using SHA256
|
||||
/// UUID = SHA256(user_path + filename)[0:16]
|
||||
pub fn compute_uuid(user_path: &str, filename: &str) -> String {
|
||||
let key = format!("{}/{}", user_path.trim_end_matches('/'), filename);
|
||||
let hash = Sha256::digest(key.as_bytes());
|
||||
let hash_str = hex::encode(hash);
|
||||
hash_str[0..16].to_string()
|
||||
}
|
||||
|
||||
/// Compute UUID from full file path
|
||||
pub fn compute_uuid_from_path(full_path: &str) -> String {
|
||||
let path = PathBuf::from(full_path);
|
||||
let parent = path
|
||||
.parent()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let filename = path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
compute_uuid(&parent, &filename)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_uuid_computation() {
|
||||
let uuid = compute_uuid("/Users/test/Videos", "video.mp4");
|
||||
assert_eq!(uuid.len(), 16);
|
||||
println!("UUID: {}", uuid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuid_from_path() {
|
||||
let uuid = compute_uuid_from_path("/Users/test/Videos/video.mp4");
|
||||
assert_eq!(uuid.len(), 16);
|
||||
}
|
||||
}
|
||||
108
src/core/thumbnail/mod.rs
Normal file
108
src/core/thumbnail/mod.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ThumbnailResult {
|
||||
pub uuid: String,
|
||||
pub count: usize,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct ThumbnailExtractor {
|
||||
output_dir: PathBuf,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl ThumbnailExtractor {
|
||||
pub fn new(output_dir: PathBuf, count: u32) -> Self {
|
||||
Self { output_dir, count }
|
||||
}
|
||||
|
||||
pub fn extract(&self, video_path: &str, uuid: &str) -> Result<ThumbnailResult> {
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts")
|
||||
.join("thumbnail_extractor.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(uuid)
|
||||
.arg("-o")
|
||||
.arg(&self.output_dir)
|
||||
.arg("-c")
|
||||
.arg(self.count.to_string())
|
||||
.output()
|
||||
.context("Failed to run thumbnail extractor")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("Thumbnail extraction failed: {}", stderr);
|
||||
}
|
||||
|
||||
let json_str = String::from_utf8_lossy(&output.stdout);
|
||||
let result: ThumbnailResult =
|
||||
serde_json::from_str(&json_str).context("Failed to parse thumbnail result")?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_or_create(&self, video_path: &str, uuid: &str) -> Result<ThumbnailResult> {
|
||||
let thumb_dir = self.output_dir.join(uuid);
|
||||
|
||||
// Check if thumbnails already exist
|
||||
if thumb_dir.exists() {
|
||||
let files: Vec<String> = (0..self.count)
|
||||
.map(|i| thumb_dir.join(format!("thumb_{:03}.jpg", i)))
|
||||
.filter(|p| p.exists())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
if files.len() as u32 == self.count {
|
||||
return Ok(ThumbnailResult {
|
||||
uuid: uuid.to_string(),
|
||||
count: files.len(),
|
||||
files,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract new thumbnails
|
||||
self.extract(video_path, uuid)
|
||||
}
|
||||
|
||||
pub fn get_thumbnails(&self, uuid: &str) -> Option<Vec<String>> {
|
||||
let thumb_dir = self.output_dir.join(uuid);
|
||||
|
||||
if !thumb_dir.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let files: Vec<String> = (0..10)
|
||||
.map(|i| thumb_dir.join(format!("thumb_{:03}.jpg", i)))
|
||||
.filter(|p| p.exists())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
if files.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(files)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanup(&self, uuid: &str) -> Result<()> {
|
||||
let thumb_dir = self.output_dir.join(uuid);
|
||||
if thumb_dir.exists() {
|
||||
std::fs::remove_dir_all(&thumb_dir).context("Failed to remove thumbnail directory")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
pub mod core;
|
||||
|
||||
pub use core::chunk::{Chunk, ChunkSplitter, ChunkType};
|
||||
pub use core::db::{Database, MongoDb, PostgresDb, QdrantDb, RedisDb, VideoRecord};
|
||||
pub use core::probe::ProbeResult;
|
||||
pub use core::storage::file_manager::FileManager;
|
||||
pub use core::storage::uuid;
|
||||
pub use core::thumbnail::{ThumbnailExtractor, ThumbnailResult};
|
||||
340
src/main.rs
Normal file
340
src/main.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::Path;
|
||||
|
||||
use momentry_core::{Database, PostgresDb, VideoRecord};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "momentry")]
|
||||
#[command(about = "Digital asset management system with video analysis and RAG")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Register a video file
|
||||
Register {
|
||||
/// Video file path or URL
|
||||
path: String,
|
||||
},
|
||||
/// Process video (generate all JSON files)
|
||||
Process {
|
||||
/// UUID or path
|
||||
target: String,
|
||||
},
|
||||
/// Generate chunks and store in database
|
||||
Chunk {
|
||||
/// UUID
|
||||
uuid: String,
|
||||
},
|
||||
/// Vectorize chunks
|
||||
Vectorize {
|
||||
/// UUID (or 'all' for all)
|
||||
uuid: String,
|
||||
},
|
||||
/// Play video with overlays
|
||||
Play {
|
||||
/// Video path or UUID
|
||||
target: String,
|
||||
},
|
||||
/// Start watching directories
|
||||
Watch {
|
||||
/// Directories to watch (comma separated)
|
||||
directories: Option<String>,
|
||||
},
|
||||
/// Start API server
|
||||
Server {
|
||||
/// Host
|
||||
#[arg(long, default_value = "127.0.0.1")]
|
||||
host: String,
|
||||
/// Port
|
||||
#[arg(long, default_value = "3000")]
|
||||
port: u16,
|
||||
},
|
||||
/// Query using RAG
|
||||
Query {
|
||||
/// Query text
|
||||
query: String,
|
||||
},
|
||||
/// Lookup UUID from path
|
||||
Lookup {
|
||||
/// File path
|
||||
path: String,
|
||||
},
|
||||
/// Resolve path from UUID
|
||||
Resolve {
|
||||
/// UUID
|
||||
uuid: String,
|
||||
},
|
||||
/// Generate thumbnails for videos
|
||||
Thumbnails {
|
||||
/// UUID (optional, generates for all if not specified)
|
||||
uuid: Option<String>,
|
||||
/// Number of thumbnails per video
|
||||
#[arg(short, long, default_value = "6")]
|
||||
count: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Register { path } => {
|
||||
println!("Registering: {}", path);
|
||||
|
||||
// Compute UUID
|
||||
let uuid = momentry_core::uuid::compute_uuid_from_path(&path);
|
||||
println!("UUID: {}", uuid);
|
||||
|
||||
// Run ffprobe
|
||||
let probe_result = momentry_core::core::probe::probe_video(&path)?;
|
||||
|
||||
println!("\nVideo probe results:");
|
||||
let duration = probe_result
|
||||
.format
|
||||
.duration
|
||||
.as_ref()
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
println!(" Duration: {}s", duration);
|
||||
if let Some(size) = &probe_result.format.size {
|
||||
println!(" Size: {}", size);
|
||||
}
|
||||
|
||||
let mut width = 0u32;
|
||||
let mut height = 0u32;
|
||||
let mut fps = 0.0;
|
||||
|
||||
for stream in &probe_result.streams {
|
||||
if stream.codec_type.as_deref() == Some("video") {
|
||||
width = stream.width.unwrap_or(0);
|
||||
height = stream.height.unwrap_or(0);
|
||||
if let Some(fps_str) = &stream.r_frame_rate {
|
||||
if let Some((num, den)) = fps_str.split_once('/') {
|
||||
if let (Ok(n), Ok(d)) = (num.parse::<f64>(), den.parse::<f64>()) {
|
||||
if d > 0.0 {
|
||||
fps = n / d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(" Video: {}x{}", width, height);
|
||||
if let Some(fps) = &stream.r_frame_rate {
|
||||
println!(" FPS: {}", fps);
|
||||
}
|
||||
}
|
||||
if stream.codec_type.as_deref() == Some("audio") {
|
||||
println!(" Audio: {} channels", stream.channels.unwrap_or(0));
|
||||
if let Some(sr) = &stream.sample_rate {
|
||||
println!(" Sample Rate: {}", sr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save probe JSON to file
|
||||
let file_manager = momentry_core::FileManager::new(std::path::PathBuf::from("."));
|
||||
let json_str = serde_json::to_string_pretty(&probe_result)?;
|
||||
let json_path = file_manager.save_json(&uuid, "probe", &json_str)?;
|
||||
println!("\nProbe JSON saved to: {:?}", json_path);
|
||||
|
||||
// Store in PostgreSQL
|
||||
println!("\nStoring in database...");
|
||||
let db = PostgresDb::init().await?;
|
||||
let file_path = Path::new(&path)
|
||||
.canonicalize()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| path.clone());
|
||||
let file_name = Path::new(&path)
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let record = VideoRecord {
|
||||
id: 0,
|
||||
uuid: uuid.clone(),
|
||||
file_path,
|
||||
file_name,
|
||||
duration,
|
||||
width,
|
||||
height,
|
||||
fps,
|
||||
probe_json: Some(json_str),
|
||||
created_at: String::new(),
|
||||
};
|
||||
|
||||
let video_id = db.register_video(&record).await?;
|
||||
println!("Video registered with ID: {}", video_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Commands::Process { target } => {
|
||||
println!("Processing: {}", target);
|
||||
|
||||
// Compute UUID if path is given
|
||||
let uuid = if target.len() == 16 && !target.contains('/') {
|
||||
target.clone()
|
||||
} else {
|
||||
momentry_core::uuid::compute_uuid_from_path(&target)
|
||||
};
|
||||
|
||||
// Get video from database
|
||||
let db = PostgresDb::init().await?;
|
||||
let video = db
|
||||
.get_video_by_uuid(&uuid)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Video not found: {}", uuid))?;
|
||||
|
||||
let video_path = &video.file_path;
|
||||
let file_manager = momentry_core::FileManager::new(std::path::PathBuf::from("."));
|
||||
|
||||
// Process ASR
|
||||
println!("\nRunning ASR...");
|
||||
let asr_path = format!("{}.asr.json", uuid);
|
||||
let asr_result =
|
||||
momentry_core::core::processor::process_asr(video_path, &asr_path).await?;
|
||||
let asr_json = serde_json::to_string_pretty(&asr_result)?;
|
||||
std::fs::write(&asr_path, &asr_json)?;
|
||||
println!("ASR saved to: {}", asr_path);
|
||||
println!(" {} segments found", asr_result.segments.len());
|
||||
|
||||
// TODO: Process OCR, YOLO, Face, Pose, ASRx
|
||||
println!("\nOther processors not yet implemented.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Commands::Chunk { uuid } => {
|
||||
println!("Chunking: {}", uuid);
|
||||
|
||||
let db = PostgresDb::init().await?;
|
||||
let video = db
|
||||
.get_video_by_uuid(&uuid)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Video not found: {}", uuid))?;
|
||||
|
||||
// Read ASR JSON
|
||||
let asr_path = format!("{}.asr.json", uuid);
|
||||
let asr_json = std::fs::read_to_string(&asr_path)
|
||||
.context("ASR file not found. Run 'process' first.")?;
|
||||
|
||||
let asr_result: momentry_core::core::processor::asr::AsrResult =
|
||||
serde_json::from_str(&asr_json)?;
|
||||
|
||||
println!("Processing {} ASR segments...", asr_result.segments.len());
|
||||
|
||||
// Split into sentence chunks
|
||||
let mut sentence_chunks = Vec::new();
|
||||
for (i, seg) in asr_result.segments.iter().enumerate() {
|
||||
let chunk = momentry_core::Chunk::new(
|
||||
uuid.clone(),
|
||||
i as u32,
|
||||
momentry_core::ChunkType::Sentence,
|
||||
seg.start,
|
||||
seg.end,
|
||||
serde_json::json!({
|
||||
"text": seg.text,
|
||||
}),
|
||||
);
|
||||
sentence_chunks.push(chunk);
|
||||
}
|
||||
|
||||
// Split into time-based chunks (10 seconds)
|
||||
let splitter = momentry_core::core::chunk::ChunkSplitter::new(10.0);
|
||||
let time_chunks = splitter.split_time_based(&uuid, video.duration);
|
||||
|
||||
// Store in database
|
||||
println!("Storing {} sentence chunks...", sentence_chunks.len());
|
||||
for chunk in &sentence_chunks {
|
||||
db.store_chunk(chunk).await?;
|
||||
}
|
||||
|
||||
println!("Storing {} time-based chunks...", time_chunks.len());
|
||||
for chunk in &time_chunks {
|
||||
db.store_chunk(chunk).await?;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Done! {} total chunks stored.",
|
||||
sentence_chunks.len() + time_chunks.len()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Commands::Vectorize { uuid } => {
|
||||
println!("Vectorizing: {}", uuid);
|
||||
// TODO: Implement vectorize
|
||||
Ok(())
|
||||
}
|
||||
Commands::Play { target } => {
|
||||
println!("Playing: {}", target);
|
||||
// TODO: Implement play
|
||||
Ok(())
|
||||
}
|
||||
Commands::Watch { directories } => {
|
||||
println!("Starting watcher: {:?}", directories);
|
||||
// TODO: Implement watch
|
||||
Ok(())
|
||||
}
|
||||
Commands::Server { host, port } => {
|
||||
println!("Starting API server at {}:{}", host, port);
|
||||
// TODO: Implement server
|
||||
Ok(())
|
||||
}
|
||||
Commands::Query { query } => {
|
||||
println!("Query: {}", query);
|
||||
// TODO: Implement query
|
||||
Ok(())
|
||||
}
|
||||
Commands::Lookup { path } => {
|
||||
let uuid = momentry_core::uuid::compute_uuid_from_path(&path);
|
||||
println!("Path: {}", path);
|
||||
println!("UUID: {}", uuid);
|
||||
Ok(())
|
||||
}
|
||||
Commands::Resolve { uuid } => {
|
||||
println!("Resolving UUID: {}", uuid);
|
||||
// TODO: Look up path from UUID in database
|
||||
println!("(Database lookup not implemented yet)");
|
||||
Ok(())
|
||||
}
|
||||
Commands::Thumbnails { uuid, count } => {
|
||||
let db = PostgresDb::init().await?;
|
||||
|
||||
let videos = if let Some(ref uuid) = uuid {
|
||||
vec![db
|
||||
.get_video_by_uuid(uuid)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Video not found: {}", uuid))?]
|
||||
} else {
|
||||
db.list_videos().await?
|
||||
};
|
||||
|
||||
let output_dir = std::path::PathBuf::from("thumbnails");
|
||||
let extractor = momentry_core::ThumbnailExtractor::new(output_dir, count);
|
||||
|
||||
for video in videos {
|
||||
println!(
|
||||
"\nGenerating thumbnails for: {} ({})",
|
||||
video.file_name, video.uuid
|
||||
);
|
||||
|
||||
match extractor.get_or_create(&video.file_path, &video.uuid) {
|
||||
Ok(result) => {
|
||||
println!(" Generated {} thumbnails", result.count);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" Error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nThumbnails generated successfully!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/player/mod.rs
Normal file
3
src/player/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod player;
|
||||
|
||||
pub use player::{play_video, PlayerConfig};
|
||||
22
src/player/player.rs
Normal file
22
src/player/player.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::core::overlay::OverlayFlags;
|
||||
|
||||
pub struct PlayerConfig {
|
||||
pub sync_delay_ms: u64,
|
||||
pub overlay_flags: OverlayFlags,
|
||||
}
|
||||
|
||||
impl Default for PlayerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sync_delay_ms: 800,
|
||||
overlay_flags: OverlayFlags::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn play_video(path: &str, config: PlayerConfig) -> anyhow::Result<()> {
|
||||
// TODO: Implement video player with overlay
|
||||
// Will integrate existing video_player functionality
|
||||
println!("Playing: {} with config: {:?}", path, config);
|
||||
Ok(())
|
||||
}
|
||||
3
src/watcher/mod.rs
Normal file
3
src/watcher/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod watcher;
|
||||
|
||||
pub use watcher::{watch_directories, WatcherConfig};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user