---
document_type: "implementation_guide"
service: "MOMENTRY_CORE"
title: "Momentry 服務添加規範 v2.1"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
  - "momentry"
  - "服務添加規範"
ai_query_hints:
  - "查詢 Momentry 服務添加規範 v2.1 的內容"
  - "Momentry 服務添加規範 v2.1 的主要目的是什麼？"
  - "如何操作或實施 Momentry 服務添加規範 v2.1？"
---

# Momentry 服務添加規範 v2.1

| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 更新時間 | 2026-03-24 |
| 文件版本 | V2.1 |

---

## 版本歷史

| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V2.1 | 2026-03-24 | 更新 launchctl 命令，使用 bootstrap | OpenCode | OpenCode / big-pickle |

---

## 一、概述

本文檔定義在 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
# 啟動服務 (使用 launchctl bootstrap)
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.{service}.plist

# 停止服務 (使用 launchctl bootout)
sudo launchctl bootout system/com.momentry.{service}.plist

# 重新載入服務
sudo launchctl bootout system/com.momentry.{service}.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.{service}.plist

# 查看服務狀態
launchctl list | grep com.momentry

# 查看特定服務狀態
launchctl list | grep com.momentry.{service}

# 查看服務日誌
tail -f /Users/accusys/momentry/log/{service}.log
tail -f /Users/accusys/momentry/log/{service}.error.log
```

### 8.2 批量管理

```bash
# 啟動所有 Momentry 服務
for plist in /Library/LaunchDaemons/com.momentry.*.plist; do
    sudo launchctl bootstrap system "$plist"
done

# 停止所有 Momentry 服務
for svc in $(launchctl list | grep com.momentry | awk '{print $3}'); do
    sudo launchctl bootout system/$svc 2>/dev/null
done

# 查看所有 Momentry 服務狀態
launchctl list | grep com.momentry
```

### 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 | 新增服務備份作業、服務完整刪除作業 |
| 2.1 | 2026-03-24 | 更新 launchctl 命令，使用 `bootstrap`/`bootout` 替代 `load`/`unload` | |
