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:
commit
71d2384bf5
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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`(不進 repo),yaml 用 `${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 binary(backend/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(機敏資料落地加密)
|
||||
```
|
||||
|
|
@ -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:
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# 部署到目標主機的 /opt/haixun/etc/haixun.env(chmod 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 secret(gateway 與 node worker 必須一致)
|
||||
HAIXUN_WORKER_SECRET=replace-with-strong-random
|
||||
|
||||
# 機敏資料落地加密金鑰(base64 編碼的 32 bytes;openssl rand -base64 32)
|
||||
HAIXUN_SECRETS_KEY=replace-with-base64-32-bytes
|
||||
|
||||
# Node worker 連線設定
|
||||
HAIXUN_BACKEND_URL=http://127.0.0.1:8890
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# 巡樓 Console nginx 設定
|
||||
# 安裝:cp infra/nginx/haixun.conf /etc/nginx/conf.d/haixun.conf && nginx -t && systemctl reload nginx
|
||||
# 前端靜態檔部署在 /var/www/haixun(make 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;
|
||||
|
||||
# Authorization(provider 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
# secrets(JWT / 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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. 初始化 DB(indexes + admin,冪等)
|
||||
echo "[6/7] 初始化 DB(admin: 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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
Loading…
Reference in New Issue