Merge remote-tracking branch 'refs/remotes/origin/main'

# Conflicts:
#	.gitignore
#	Makefile
#	backend/etc/gateway.dev.example.yaml
#	backend/internal/library/matrix/generate.go
#	backend/internal/library/prompt/files/matrix_copy.system.md
#	backend/internal/library/prompt/files/matrix_copy.user.md
#	backend/internal/library/viral/discover_accounts_test.go
#	backend/internal/model/copy_mission/domain/entity/mission.go
#	backend/internal/model/copy_mission/domain/usecase/usecase.go
#	backend/internal/model/job/usecase/usecase.go
#	backend/internal/model/threads_account/domain/entity/account.go
#	backend/internal/model/threads_account/domain/usecase/usecase.go
#	backend/internal/worker/job/analyze_copy_mission.go
#	backend/worker/package-lock.json
#	deploy/.env.example
#	deploy/Dockerfile.node-worker
#	deploy/Dockerfile.web
#	deploy/README.md
#	deploy/docker-compose.yml
#	deploy/nginx.conf
#	frontend/src/components/AccountAiSettings.tsx
#	frontend/src/components/CopyMissionResearchOverview.tsx
#	frontend/src/components/JobMonitor.tsx
#	frontend/src/lib/aiCredentials.ts
#	frontend/src/lib/jobMonitorUi.ts
#	frontend/src/pages/CopyMissionDetailPage.tsx
#	frontend/src/types/copyMission.ts
This commit is contained in:
王性驊 2026-06-30 00:37:57 +08:00
commit 71d2384bf5
12 changed files with 422 additions and 0 deletions

View File

@ -1305,6 +1305,28 @@ type ThreadsAccountAiSettingsData struct {
ApiKeysConfigured map[string]interface{} `json:"api_keys_configured"`
}
type ThreadsAccountAiProviderPath struct {
ID string `path:"id" validate:"required"`
Provider string `path:"provider" validate:"required,oneof=opencode-go xai"`
}
type ListThreadsAccountAiProviderModelsReq struct {
ApiKey string `json:"api_key,optional"`
}
type ThreadsAccountAiProviderModelsHandlerReq struct {
ThreadsAccountAiProviderPath
ListThreadsAccountAiProviderModelsReq
}
type ThreadsAccountAiProviderModelsData struct {
ID string `json:"id"`
Label string `json:"label"`
Models []string `json:"models"`
Streams bool `json:"streams"`
Error string `json:"error,optional"`
}
type ThreadsAccountConnectionData struct {
AccountID string `json:"account_id"`
AccountName string `json:"account_name"`

20
infra/.env.example Normal file
View File

@ -0,0 +1,20 @@
# Infra docker compose 環境變數範本
# 複製成 infra/.env 後填入實際值;.env 不要 commit。
# --- Mongo ---
MONGO_PORT=27017
MONGO_ROOT_USER=haixun
MONGO_ROOT_PASSWORD=1qaz@WSX3edc$RFV
MONGO_DATABASE=haixun
# --- Redis ---
REDIS_PORT=6379
REDIS_PASSWORD=change-me-redis-pass
INIT_ADMIN_EMAIL=admin@30cm.net
PASSWORD=Fafafafa54088
HAIXUN_JWT_ACCESS_SECRET=1qaz@WSX3edc$RFV
HAIXUN_JWT_REFRESH_SECRET=1qaz@WSX3edc$RFV
HAIXUN_SECRETS_KEY=1qaz@WSX3edc$RFV
HAIXUN_WORKER_SECRET=1qaz@WSX3edc$RFV

103
infra/README.md Normal file
View File

@ -0,0 +1,103 @@
# 巡樓部署 (infra)
部署拓樸:
```
瀏覽器 → nginx(systemd, :80/:443)
├─ 靜態前端 /var/www/haixun (frontend/dist)
└─ /api 反向代理 → Go gateway (systemd, 127.0.0.1:8890)
Go gateway / Go worker (systemd) → Mongo / Redis (docker compose, 綁 127.0.0.1)
Node playwright worker (systemd) → 透過 HTTP 打 gateway
```
- 資料服務Mongo/Redis用 docker compose只綁 `127.0.0.1`
- Go gateway / Go worker / Node worker 都是 systemd 原生服務。
- secret 一律放 `/opt/haixun/etc/haixun.env`(不進 repoyaml 用 `${VAR}` 讀取。
## 目錄
```
infra/
docker-compose.yml # mongo + redis
.env.example # compose 用環境變數
etc/haixun.env.example # systemd EnvironmentFile 範本secret
nginx/haixun.conf # 靜態前端 + /api 反代 + SSE
systemd/
haixun-gateway.service
haixun-worker.service
haixun-node-worker.service
```
## 1. 起資料服務 (docker)
```bash
cd infra
cp .env.example .env # 填入 Mongo/Redis 密碼
docker compose --env-file .env up -d
docker compose ps
```
## 2. 建置產物(本機或 CI
```bash
make build # 前端 dist + 兩個 linux Go binarybackend/bin/
```
## 3. 安裝到目標主機
於目標主機(需 root
```bash
sudo make install
```
`make install` 會:
1. 建立使用者 `haixun` 與目錄 `/opt/haixun/{bin,etc,node-worker}`、`/var/www/haixun`。
2. 複製 `backend/bin/{gateway,worker}`、`backend/etc/gateway.prod.yaml`、`backend/etc/gateway.worker.prod.yaml`。
3. 複製 `frontend/dist/*``/var/www/haixun`
4. 複製 `backend/worker/*`Node worker`/opt/haixun/node-worker`,並 `npm ci` + `npx playwright install`
5. 安裝 `infra/systemd/*.service``infra/nginx/haixun.conf`
接著手動建立 secret 檔(**只做一次**
```bash
sudo cp infra/etc/haixun.env.example /opt/haixun/etc/haixun.env
sudo chmod 600 /opt/haixun/etc/haixun.env
sudoedit /opt/haixun/etc/haixun.env # 填入實際 secret
```
## 4. 初始化資料庫與 admin 帳號(只做一次)
Mongo 起來、secret 填好後,建立索引 / 權限 catalog / role_permissions並建立第一個 admin
```bash
# 可在 haixun.env 內設定 INIT_ADMIN_EMAIL / INIT_ADMIN_PASSWORD或在這裡用環境變數覆寫
sudo make prod-init
# 等同source /opt/haixun/etc/haixun.env 後執行 /opt/haixun/bin/tool init -f /opt/haixun/etc/gateway.prod.yaml
```
之後一般使用者可走 `POST /api/v1/auth/register` 自助註冊(前端登入頁)。
## 5. 啟用服務
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now haixun-gateway haixun-worker haixun-node-worker
sudo nginx -t && sudo systemctl reload nginx
```
## 6. 健康檢查
```bash
curl http://127.0.0.1:8890/api/v1/health
sudo systemctl status haixun-gateway haixun-worker haixun-node-worker
journalctl -u haixun-gateway -f
```
## 產生 secret
```bash
openssl rand -base64 48 # JWT access / refresh / worker secret
openssl rand -base64 32 # HAIXUN_SECRETS_KEY機敏資料落地加密
```

42
infra/docker-compose.yml Normal file
View File

@ -0,0 +1,42 @@
# 巡樓資料服務Mongo + Redis
# 只綁 127.0.0.1,給同主機上以 systemd 跑的 Go gateway / worker 連線。
# 啟動docker compose -f infra/docker-compose.yml --env-file infra/.env up -d
name: haixun-infra
services:
mongo:
image: mongo:7
restart: unless-stopped
ports:
- "127.0.0.1:${MONGO_PORT:-27017}:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER:-haixun}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD:?MONGO_ROOT_PASSWORD is required}
MONGO_INITDB_DATABASE: ${MONGO_DATABASE:-haixun}
volumes:
- mongo_data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
redis:
image: redis:7
restart: unless-stopped
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD is required}", "--appendonly", "yes"]
ports:
- "127.0.0.1:${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
volumes:
mongo_data:
redis_data:

View File

@ -0,0 +1,23 @@
# 部署到目標主機的 /opt/haixun/etc/haixun.envchmod 600不要 commit 實值)
# gateway.prod.yaml / gateway.worker.yaml 用 ${VAR} 讀取這些值go-zero conf.UseEnv
# Mongo含 docker compose 設定的帳密authSource=admin
HAIXUN_MONGO_URI=mongodb://haixun:change-me-mongo-pass@127.0.0.1:27017/?authSource=admin
HAIXUN_MONGO_DB=haixun
# Redis
HAIXUN_REDIS_ADDR=127.0.0.1:6379
HAIXUN_REDIS_PASSWORD=change-me-redis-pass
# JWT secret請用 openssl rand -base64 48 產生,兩把不同)
HAIXUN_JWT_ACCESS_SECRET=replace-with-strong-random
HAIXUN_JWT_REFRESH_SECRET=replace-with-another-strong-random
# 內部 worker secretgateway 與 node worker 必須一致)
HAIXUN_WORKER_SECRET=replace-with-strong-random
# 機敏資料落地加密金鑰base64 編碼的 32 bytesopenssl rand -base64 32
HAIXUN_SECRETS_KEY=replace-with-base64-32-bytes
# Node worker 連線設定
HAIXUN_BACKEND_URL=http://127.0.0.1:8890

56
infra/nginx/haixun.conf Normal file
View File

@ -0,0 +1,56 @@
# 巡樓 Console nginx 設定
# 安裝cp infra/nginx/haixun.conf /etc/nginx/conf.d/haixun.conf && nginx -t && systemctl reload nginx
# 前端靜態檔部署在 /var/www/haixunmake install 會放 frontend/dist 內容)。
# /api 反向代理到本機 systemd 跑的 Go gateway (127.0.0.1:8890),含 SSE 串流設定。
upstream haixun_gateway {
server 127.0.0.1:8890;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name _;
root /var/www/haixun;
index index.html;
# 安全標頭
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "0" always;
# 靜態資源快取vite build 帶 hash 檔名)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# API 反向代理(一般 JSON 與 SSE 共用)
location /api/ {
proxy_pass http://haixun_gateway;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Authorizationprovider token與 X-Member-Authorization會員 JWT由 nginx 預設轉發;
# 以下確保 SSE 串流不被緩衝、長連線不被提前中斷。
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# SPA所有非檔案路徑都回 index.html交給 react-router
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@ -0,0 +1,25 @@
[Unit]
Description=Haixun Gateway API (go-zero)
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=simple
User=haixun
Group=haixun
WorkingDirectory=/opt/haixun
# secretsJWT / Mongo URI / Redis 密碼 / worker secret / 加密金鑰)放這裡,不進 repo
EnvironmentFile=/opt/haixun/etc/haixun.env
ExecStart=/opt/haixun/bin/gateway -f /opt/haixun/etc/gateway.prod.yaml
Restart=always
RestartSec=5
LimitNOFILE=65535
# 加固
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,23 @@
[Unit]
Description=Haixun Node Playwright Worker (style-8d)
After=network-online.target haixun-gateway.service
Wants=network-online.target
[Service]
Type=simple
User=haixun
Group=haixun
WorkingDirectory=/opt/haixun/node-worker
# 至少需要 HAIXUN_BACKEND_URL 與 HAIXUN_WORKER_SECRET與 gateway 的 InternalWorker.Secret 一致)
EnvironmentFile=/opt/haixun/etc/haixun.env
ExecStart=/usr/bin/npx tsx style-8d-worker.ts
Restart=always
RestartSec=10
LimitNOFILE=65535
NoNewPrivileges=true
ProtectSystem=full
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,23 @@
[Unit]
Description=Haixun Go Job Worker
After=network-online.target docker.service haixun-gateway.service
Wants=network-online.target
[Service]
Type=simple
User=haixun
Group=haixun
WorkingDirectory=/opt/haixun
EnvironmentFile=/opt/haixun/etc/haixun.env
ExecStart=/opt/haixun/bin/worker -f /opt/haixun/etc/gateway.worker.prod.yaml
Restart=always
RestartSec=5
LimitNOFILE=65535
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target

59
start-prod.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
set -e
echo "=== 巡樓 Production 啟動 ==="
# 1. 確認 nginx 不會被 default site 搶走
echo "[1/7] 確保 nginx config..."
if [ -f /etc/nginx/sites-enabled/default ]; then
sudo rm /etc/nginx/sites-enabled/default
fi
# 2. 起 infra (Mongo + Redis)
echo "[2/7] 啟動 Mongo + Redis..."
docker compose -f infra/docker-compose.yml --env-file infra/.env up -d
# 3. 確保 Playwright 瀏覽器已安裝
echo "[3/7] 確認 Playwright chromium..."
if [ ! -d /opt/haixun/.cache/ms-playwright/chromium-1228 ]; then
sudo -u haixun bash -c 'cd /opt/haixun/node-worker && npx playwright install chromium'
fi
# 4. 清除 systemd 失敗計數(之前 crash loop 的話需要 reset
echo "[4/7] 重置 systemd 狀態..."
for svc in haixun-gateway haixun-worker haixun-node-worker; do
sudo systemctl reset-failed "$svc" 2>/dev/null || true
done
# 5. 啟動 systemd 服務
echo "[5/7] 啟動 backend gateway..."
sudo systemctl start haixun-gateway
echo "[5/7] 啟動 worker..."
sudo systemctl start haixun-worker
echo "[5/7] 啟動 node worker..."
sudo systemctl start haixun-node-worker
# 6. 初始化 DBindexes + admin冪等
echo "[6/7] 初始化 DBadmin: admin@30cm.net..."
set -a; source /opt/haixun/etc/haixun.env; set +a
sudo -E /opt/haixun/bin/tool init -f /opt/haixun/etc/gateway.prod.yaml
# 7. 確認 nginx 執行 + reload config
echo "[7/7] 確認 nginx..."
sudo nginx -t && sudo systemctl start nginx && sudo systemctl reload nginx
echo ""
echo "=== 啟動完成 ==="
echo " nginx: $(systemctl is-active nginx)"
echo " gateway: $(systemctl is-active haixun-gateway)"
echo " worker: $(systemctl is-active haixun-worker)"
echo " node-wkr: $(systemctl is-active haixun-node-worker)"
echo " docker: $(docker compose -f infra/docker-compose.yml ps --status running 2>/dev/null | wc -l) containers running"
echo ""
echo "健康檢查:"
echo " curl http://localhost/api/v1/health"
echo ""
echo "查看日誌:"
echo " journalctl -u haixun-gateway -n 50 --no-pager -f"
echo " journalctl -u haixun-worker -n 50 --no-pager -f"
echo " docker compose -f infra/docker-compose.yml logs -f"

20
status-prod.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
echo "=== 巡樓 Production 狀態 ==="
echo ""
echo "--- Nginx ---"
systemctl status nginx --no-pager -n 5 2>&1 | head -5
echo ""
echo "--- Gateway ---"
systemctl status haixun-gateway --no-pager -n 5 2>&1 | head -5
echo ""
echo "--- Worker ---"
systemctl status haixun-worker --no-pager -n 5 2>&1 | head -5
echo ""
echo "--- Node Worker ---"
systemctl status haixun-node-worker --no-pager -n 5 2>&1 | head -5
echo ""
echo "--- Docker Infra ---"
docker compose -f /home/daniel/thread-master/infra/docker-compose.yml ps 2>&1
echo ""
echo "--- Port 8890 ---"
ss -tlnp | grep 8890 || echo "gateway not listening"

6
stop-prod.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
echo "=== 巡樓 Production 停止 ==="
sudo systemctl stop haixun-worker
sudo systemctl stop haixun-gateway
docker compose -f /home/daniel/thread-master/infra/docker-compose.yml down
echo "done"