From 8b0985f604358ac6f0a8695e6537dc134e8c14ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Fri, 26 Jun 2026 16:56:07 +0800 Subject: [PATCH] first commit --- Makefile | 98 --------- deploy/.env | 40 ---- deploy/.env.example | 40 ---- deploy/Dockerfile.api | 22 -- deploy/Dockerfile.node-worker | 9 - deploy/Dockerfile.web | 14 -- deploy/Dockerfile.web.static | 7 - deploy/README.md | 64 ------ deploy/config/gateway.runtime.yaml.tpl | 35 --- deploy/config/gateway.worker.runtime.yaml.tpl | 35 --- deploy/docker-compose.prod.yml | 110 ---------- deploy/docker-compose.yml | 37 ---- deploy/docker/entrypoint-api.sh | 15 -- deploy/docker/entrypoint-init.sh | 22 -- deploy/docker/entrypoint-worker.sh | 15 -- deploy/mongo/init/01-gateway-indexes.js | 31 --- deploy/nginx.conf | 55 ----- scripts/debug-opencode-raw.sh | 71 ------- scripts/dev-with-style-8d.sh | 72 ------- scripts/package-extension.sh | 24 --- scripts/prod-common.sh | 201 ------------------ scripts/prod-deps.sh | 16 -- scripts/prod-down.sh | 11 - scripts/prod-logs.sh | 8 - scripts/prod-up.sh | 23 -- scripts/prod-update.sh | 29 --- scripts/prod-wipe-data.sh | 22 -- scripts/restart-all.sh | 7 - scripts/start-all.sh | 75 ------- scripts/status-all.sh | 50 ----- scripts/stop-all.sh | 51 ----- scripts/test-job-cancel.sh | 76 ------- scripts/test-job-concurrency.sh | 87 -------- 33 files changed, 1472 deletions(-) delete mode 100644 Makefile delete mode 100644 deploy/.env delete mode 100644 deploy/.env.example delete mode 100644 deploy/Dockerfile.api delete mode 100644 deploy/Dockerfile.node-worker delete mode 100644 deploy/Dockerfile.web delete mode 100644 deploy/Dockerfile.web.static delete mode 100644 deploy/README.md delete mode 100644 deploy/config/gateway.runtime.yaml.tpl delete mode 100644 deploy/config/gateway.worker.runtime.yaml.tpl delete mode 100644 deploy/docker-compose.prod.yml delete mode 100644 deploy/docker-compose.yml delete mode 100644 deploy/docker/entrypoint-api.sh delete mode 100644 deploy/docker/entrypoint-init.sh delete mode 100644 deploy/docker/entrypoint-worker.sh delete mode 100644 deploy/mongo/init/01-gateway-indexes.js delete mode 100644 deploy/nginx.conf delete mode 100755 scripts/debug-opencode-raw.sh delete mode 100644 scripts/dev-with-style-8d.sh delete mode 100755 scripts/package-extension.sh delete mode 100755 scripts/prod-common.sh delete mode 100755 scripts/prod-deps.sh delete mode 100755 scripts/prod-down.sh delete mode 100755 scripts/prod-logs.sh delete mode 100755 scripts/prod-up.sh delete mode 100755 scripts/prod-update.sh delete mode 100755 scripts/prod-wipe-data.sh delete mode 100755 scripts/restart-all.sh delete mode 100755 scripts/start-all.sh delete mode 100755 scripts/status-all.sh delete mode 100755 scripts/stop-all.sh delete mode 100755 scripts/test-job-cancel.sh delete mode 100755 scripts/test-job-concurrency.sh diff --git a/Makefile b/Makefile deleted file mode 100644 index 8570fa2..0000000 --- a/Makefile +++ /dev/null @@ -1,98 +0,0 @@ -GO ?= go -GOFMT ?= gofmt -GOCTL ?= goctl -GO_ZERO_STYLE := go_zero -API_ENTRY := ./generate/api/gateway.api -GOFILES := $(shell find . -name '*.go') - -.DEFAULT_GOAL := help - -help: ## 顯示可用指令 - @echo "Haixun Backend" - @echo "" - @grep -E '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf " make %-12s %s\n", $$1, $$2}' - -tools: ## 安裝 goctl / goimports - @command -v $(GOCTL) >/dev/null 2>&1 || (echo ">> installing goctl" && $(GO) install github.com/zeromicro/go-zero/tools/goctl@latest) - @command -v goimports >/dev/null 2>&1 || (echo ">> installing goimports" && $(GO) install golang.org/x/tools/cmd/goimports@latest) - -gen-api: tools ## 由 .api 生成 handler / logic / types - $(GOCTL) api go -api $(API_ENTRY) -dir . -style $(GO_ZERO_STYLE) -home generate/goctl - -fmt: ## gofmt + goimports - $(GOFMT) -s -w $(GOFILES) - @command -v goimports >/dev/null 2>&1 && goimports -w . || true - -test: ## 執行測試 - $(GO) test ./... - -run: ## 啟動 API(前景) - $(GO) run ./gateway.go -f etc/gateway.yaml - -dev-all: ## 一鍵啟動 Mongo/Redis + API + 前端 + 8D worker(背景) - bash scripts/start-all.sh - -stop-all: ## 一鍵停止全部開發服務 - bash scripts/stop-all.sh - -restart-all: ## 一鍵重啟全部開發服務 - bash scripts/restart-all.sh - -status-all: ## 查看全部開發服務狀態 - bash scripts/status-all.sh - -stop: stop-all ## 同 stop-all - -restart: restart-all ## 同 restart-all - -dev-8d: ## 一鍵啟動 API + Node 8D worker(前景,Ctrl+C 結束) - bash scripts/dev-with-style-8d.sh - -CONFIG ?= etc/gateway.yaml -INIT_TENANT ?= default -INIT_EMAIL ?= admin@30cm.net -INIT_PASSWORD ?= Fafafa54088 - -tool-init: ## 初始化 Mongo indexes、預設權限與 admin 帳號 - $(GO) run ./cmd/tool init -f $(CONFIG) -tenant $(INIT_TENANT) -email $(INIT_EMAIL) -password '$(INIT_PASSWORD)' - -tool: ## 執行 cmd/tool(例:make tool ARGS="init -f etc/gateway.yaml") - $(GO) run ./cmd/tool $(ARGS) - -web-install: ## 安裝前端依賴 - cd web && npm install - -web-dev: web-install ## 啟動前端 dev server(proxy 到 :8890) - cd web && npm run dev - -extension-pack: ## 打包 Chrome 擴充為 web/public/downloads/*.zip - bash scripts/package-extension.sh - -web-build: web-install extension-pack ## 建置前端靜態檔 - cd web && npm run build - -node-worker-style-8d: ## 啟動 Node 8D 爬蟲 worker - cd .. && npm run worker:style-8d - -check: fmt test ## 格式化並測試 - -prod: ## 一鍵啟動 production Docker(API + Web + workers,分身數見 deploy/.env) - bash scripts/prod-up.sh - -prod-update: ## 只重建/重啟 API+Web+Workers;mongo/redis 不重啟,資料留在 volume - bash scripts/prod-update.sh - -prod-deps: ## 只啟動 mongo+redis(named volume 持久化) - bash scripts/prod-deps.sh - -prod-down: ## 停止 stack(不刪 volume;Mongo/Redis 資料保留) - bash scripts/prod-down.sh - -prod-wipe-data: ## 停止並刪除 mongo/redis volume(危險,需輸入 yes) - bash scripts/prod-wipe-data.sh - -prod-logs: ## 追蹤 production logs(可傳 service 名,例:make prod-logs ARGS=api) - bash scripts/prod-logs.sh $(ARGS) - -prod-build: web-build ## 建置靜態前端 + production images(不啟動) - cd deploy && docker compose -f docker-compose.prod.yml build diff --git a/deploy/.env b/deploy/.env deleted file mode 100644 index 973bdbe..0000000 --- a/deploy/.env +++ /dev/null @@ -1,40 +0,0 @@ -# 複製為 deploy/.env 後再啟動:cp deploy/.env.example deploy/.env - -# ── 對外埠 ── -HAIXUN_WEB_PORT=8080 - -# ── 前端打包模式 ── -# static = 本機 make web-build 後 nginx 只 COPY dist(預設,最快) -# docker = 在 Docker 內跑 npm build(需改 compose 用 Dockerfile.web) -# HAIXUN_WEB_BUILD_MODE=static - -# ── Worker 分身數(make prod 會帶入 docker compose --scale)── -GO_WORKER_REPLICAS=5 -NODE_STYLE8D_WORKER_REPLICAS=5 - -# ── Mongo / Redis(容器內預設,通常不用改)── -# 資料存在 Docker named volume:haixun-prod_mongo_data、haixun-prod_redis_data -# prod-down 不會刪 volume;重啟 container 資料仍在。 -# 只改版程式:make prod-update(不碰 mongo/redis) -HAIXUN_MONGO_URI=mongodb://mongo:27017 -HAIXUN_MONGO_DATABASE=haixun -HAIXUN_REDIS_ADDR=redis:6379 - -# ── 安全金鑰(正式環境務必更換)── -HAIXUN_AUTH_ACCESS_SECRET=change-me-access-secret -HAIXUN_AUTH_REFRESH_SECRET=change-me-refresh-secret -HAIXUN_WORKER_SECRET=change-me-worker-secret - -# ── 首次初始化管理員(make prod 會自動跑 init;已存在則跳過建立)── -INIT_TENANT_ID=default -INIT_ADMIN_EMAIL=admin@30cm.net -INIT_ADMIN_PASSWORD=Fafafa54088 - -# ── Node 8D worker 選項 ── -# HAIXUN_NODE_WORKER_ID=custom-node-worker-1 -# HAIXUN_WORKER_POLL_MS=3000 - -# ── 略過自動 init ── -# 預設:若 Mongo 已有 members 會自動跳過 init。 -# 強制重跑 init:PROD_FORCE_INIT=1 make prod -# HAIXUN_SKIP_INIT=1 \ No newline at end of file diff --git a/deploy/.env.example b/deploy/.env.example deleted file mode 100644 index 88ff0f0..0000000 --- a/deploy/.env.example +++ /dev/null @@ -1,40 +0,0 @@ -# 複製為 deploy/.env 後再啟動:cp deploy/.env.example deploy/.env - -# ── 對外埠 ── -HAIXUN_WEB_PORT=8080 - -# ── 前端打包模式 ── -# static = 本機 make web-build 後 nginx 只 COPY dist(預設,最快) -# docker = 在 Docker 內跑 npm build(需改 compose 用 Dockerfile.web) -# HAIXUN_WEB_BUILD_MODE=static - -# ── Worker 分身數(make prod 會帶入 docker compose --scale)── -GO_WORKER_REPLICAS=1 -NODE_STYLE8D_WORKER_REPLICAS=1 - -# ── Mongo / Redis(容器內預設,通常不用改)── -# 資料存在 Docker named volume:haixun-prod_mongo_data、haixun-prod_redis_data -# prod-down 不會刪 volume;重啟 container 資料仍在。 -# 只改版程式:make prod-update(不碰 mongo/redis) -HAIXUN_MONGO_URI=mongodb://mongo:27017 -HAIXUN_MONGO_DATABASE=haixun -HAIXUN_REDIS_ADDR=redis:6379 - -# ── 安全金鑰(正式環境務必更換)── -HAIXUN_AUTH_ACCESS_SECRET=change-me-access-secret -HAIXUN_AUTH_REFRESH_SECRET=change-me-refresh-secret -HAIXUN_WORKER_SECRET=change-me-worker-secret - -# ── 首次初始化管理員(make prod 會自動跑 init;已存在則跳過建立)── -INIT_TENANT_ID=default -INIT_ADMIN_EMAIL=admin@haixun.local -INIT_ADMIN_PASSWORD=Admin-Pass-1! - -# ── Node 8D worker 選項 ── -# HAIXUN_NODE_WORKER_ID=custom-node-worker-1 -# HAIXUN_WORKER_POLL_MS=3000 - -# ── 略過自動 init ── -# 預設:若 Mongo 已有 members 會自動跳過 init。 -# 強制重跑 init:PROD_FORCE_INIT=1 make prod -# HAIXUN_SKIP_INIT=1 \ No newline at end of file diff --git a/deploy/Dockerfile.api b/deploy/Dockerfile.api deleted file mode 100644 index dafaee4..0000000 --- a/deploy/Dockerfile.api +++ /dev/null @@ -1,22 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM golang:1.22-bookworm AS builder -WORKDIR /src -COPY go.mod go.sum ./ -RUN go mod download -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/haixun-api . -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/haixun-worker ./cmd/worker -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/haixun-tool ./cmd/tool - -FROM debian:bookworm-slim -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates gettext-base curl \ - && rm -rf /var/lib/apt/lists/* -WORKDIR /app -COPY --from=builder /out/haixun-api /out/haixun-worker /out/haixun-tool /app/ -COPY deploy/config/gateway.runtime.yaml.tpl deploy/config/gateway.worker.runtime.yaml.tpl /app/deploy/config/ -COPY deploy/docker/entrypoint-api.sh deploy/docker/entrypoint-worker.sh deploy/docker/entrypoint-init.sh /app/deploy/docker/ -RUN chmod +x /app/deploy/docker/entrypoint-api.sh /app/deploy/docker/entrypoint-worker.sh /app/deploy/docker/entrypoint-init.sh -EXPOSE 8890 -ENTRYPOINT ["/app/deploy/docker/entrypoint-api.sh"] \ No newline at end of file diff --git a/deploy/Dockerfile.node-worker b/deploy/Dockerfile.node-worker deleted file mode 100644 index c15ec93..0000000 --- a/deploy/Dockerfile.node-worker +++ /dev/null @@ -1,9 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM mcr.microsoft.com/playwright:v1.49.1-noble AS base -WORKDIR /app -COPY worker/package.json ./ -RUN npm install -COPY worker/ ./ -ENV NODE_ENV=production -CMD ["npx", "tsx", "style-8d-worker.ts"] \ No newline at end of file diff --git a/deploy/Dockerfile.web b/deploy/Dockerfile.web deleted file mode 100644 index 0159b78..0000000 --- a/deploy/Dockerfile.web +++ /dev/null @@ -1,14 +0,0 @@ -# syntax=docker/dockerfile:1 -# 備用:無本機 Node 時在 Docker 內編譯。預設請用 Dockerfile.web.static + make web-build。 - -FROM node:22-bookworm AS web-builder -WORKDIR /src/web -COPY web/package.json web/package-lock.json ./ -RUN npm ci -COPY web/ ./ -RUN npm run build - -FROM nginx:1.27-alpine -COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=web-builder /src/web/dist /usr/share/nginx/html -EXPOSE 80 \ No newline at end of file diff --git a/deploy/Dockerfile.web.static b/deploy/Dockerfile.web.static deleted file mode 100644 index e6f5999..0000000 --- a/deploy/Dockerfile.web.static +++ /dev/null @@ -1,7 +0,0 @@ -# syntax=docker/dockerfile:1 -# 本機先執行 make web-build,再打包純靜態檔 + nginx(無 Node 編譯,建置最快) - -FROM nginx:1.27-alpine -COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf -COPY web/dist /usr/share/nginx/html -EXPOSE 80 \ No newline at end of file diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index e0f0a17..0000000 --- a/deploy/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# 本機依賴(Docker Compose) - -Gateway 啟用 **Notification** / **Member OTP** 需要: - -| 服務 | 用途 | 預設埠 | -|------|------|--------| -| **MongoDB** | `notifications`、`notification_dlq` collections | 27017 | -| **Redis** | 冪等、配額、異步重試佇列、member OTP challenge | 6379 | -| MailHog(選用) | 本機 SMTP 測試 | 1025 / 8025 | -| OpenLDAP(`make ldap-up` / `make k6-up`) | ZITADEL LDAP IdP 本機目錄 | 389 | -| ZITADEL(`make k6-up`) | OIDC / Social / LDAP 登入 | 8080 | - -Mongo **不需要**事先手動建 collection;應用程式寫入時會自動建立。索引由 init script 或 `make mongo-index` 建立。 - -## 快速開始 - -```bash -# 1. 啟動 Mongo + Redis -make deps-up - -# 2.(選用)含 MailHog -make deps-up-smtp - -# 3. 確認索引(首次 docker volume 通常已由 init 建立;可再跑一次保險) -make mongo-index - -# 4. 啟動 Gateway(使用 etc/gateway.dev.yaml) -make run-dev -``` - -## Mongo collections - -| Collection | 模組 | 說明 | -|------------|------|------| -| `notifications` | notification | 發送紀錄、冪等 | -| `notification_dlq` | notification | 超過 MaxRetry 的死信 | - -索引定義見 [`deploy/mongo/init/01-gateway-indexes.js`](mongo/init/01-gateway-indexes.js),與 Go 的 `Index20260520001UP` 一致。 - -## 常用指令 - -```bash -make deps-up # docker compose up -d mongo redis -make deps-up-smtp # 再加上 mailhog(profile smtp) -make ldap-up # 只起 OpenLDAP(profile ldap) -make k6-up # 全棧含 OpenLDAP + ZITADEL(見 deploy/zitadel、deploy/openldap README) -make ldap-test # 確認 LDAP 測試帳號 alice/bob -make deps-down # 停止並移除容器(保留 volume) -make deps-down-v # 停止並刪除 volume(會清掉 Mongo 資料) -make deps-logs # 查看 log -make mongo-index # 手動建立/補齊索引 -``` - -LDAP 本機測試:[deploy/openldap/README.md](openldap/README.md) - -## 連線設定 - -設定說明:[`etc/README.md`](../etc/README.md) - -| 檔案 | 用途 | -|------|------| -| [`etc/gateway.yaml`](../etc/gateway.yaml) | 預設,無需 Docker | -| [`etc/gateway.dev.example.yaml`](../etc/gateway.dev.example.yaml) | 範例(可提交) | -| `etc/gateway.dev.yaml` | 本機專用(**勿提交**,見 `.gitignore`) | diff --git a/deploy/config/gateway.runtime.yaml.tpl b/deploy/config/gateway.runtime.yaml.tpl deleted file mode 100644 index 83a5f82..0000000 --- a/deploy/config/gateway.runtime.yaml.tpl +++ /dev/null @@ -1,35 +0,0 @@ -Name: haixun-backend -Host: 0.0.0.0 -Port: 8890 -Timeout: 120000 - -Mongo: - URI: ${HAIXUN_MONGO_URI} - Database: ${HAIXUN_MONGO_DATABASE} - TimeoutSeconds: 10 - -Redis: - Addr: ${HAIXUN_REDIS_ADDR} - DB: 0 - -Auth: - AccessSecret: ${HAIXUN_AUTH_ACCESS_SECRET} - RefreshSecret: ${HAIXUN_AUTH_REFRESH_SECRET} - AccessExpireSeconds: 900 - RefreshExpireSeconds: 2592000 - DevHeaderFallback: false - -InternalWorker: - Secret: ${HAIXUN_WORKER_SECRET} - -JobWorker: - Enabled: false - WorkerType: go - -JobScheduler: - Enabled: true - IntervalSeconds: 60 - -JobReaper: - Enabled: true - IntervalSeconds: 30 \ No newline at end of file diff --git a/deploy/config/gateway.worker.runtime.yaml.tpl b/deploy/config/gateway.worker.runtime.yaml.tpl deleted file mode 100644 index c301ec5..0000000 --- a/deploy/config/gateway.worker.runtime.yaml.tpl +++ /dev/null @@ -1,35 +0,0 @@ -Name: haixun-worker -Host: 0.0.0.0 -Port: 8891 -Timeout: 120000 - -Mongo: - URI: ${HAIXUN_MONGO_URI} - Database: ${HAIXUN_MONGO_DATABASE} - TimeoutSeconds: 10 - -Redis: - Addr: ${HAIXUN_REDIS_ADDR} - DB: 0 - -Auth: - AccessSecret: ${HAIXUN_AUTH_ACCESS_SECRET} - RefreshSecret: ${HAIXUN_AUTH_REFRESH_SECRET} - AccessExpireSeconds: 900 - RefreshExpireSeconds: 2592000 - DevHeaderFallback: false - -InternalWorker: - Secret: ${HAIXUN_WORKER_SECRET} - -JobWorker: - Enabled: true - WorkerType: go - -JobScheduler: - Enabled: false - IntervalSeconds: 60 - -JobReaper: - Enabled: false - IntervalSeconds: 30 \ No newline at end of file diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml deleted file mode 100644 index 6333d6d..0000000 --- a/deploy/docker-compose.prod.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: haixun-prod - -services: - mongo: - image: mongo:7 - restart: unless-stopped - environment: - MONGO_INITDB_DATABASE: haixun - # named volume:重啟/改版不會清資料(只有 prod-wipe-data 或 docker volume rm 才會) - volumes: - - mongo_data:/data/db - healthcheck: - test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"] - interval: 5s - timeout: 5s - retries: 12 - start_period: 15s - - redis: - image: redis:7-alpine - restart: unless-stopped - command: ["redis-server", "--appendonly", "yes"] - # AOF + named volume:重啟後 queue/lock 狀態可從磁碟恢復 - volumes: - - redis_data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 12 - - api: - build: - context: .. - dockerfile: deploy/Dockerfile.api - restart: unless-stopped - env_file: - - .env - depends_on: - mongo: - condition: service_healthy - redis: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8890/api/v1/health >/dev/null || exit 1"] - interval: 10s - timeout: 5s - retries: 12 - start_period: 20s - - go-worker: - build: - context: .. - dockerfile: deploy/Dockerfile.api - restart: unless-stopped - entrypoint: ["/app/deploy/docker/entrypoint-worker.sh"] - env_file: - - .env - depends_on: - mongo: - condition: service_healthy - redis: - condition: service_healthy - api: - condition: service_healthy - - node-worker-style-8d: - build: - context: .. - dockerfile: deploy/Dockerfile.node-worker - restart: unless-stopped - env_file: - - .env - environment: - HAIXUN_BACKEND_URL: http://api:8890 - HAIXUN_WORKER_SECRET: ${HAIXUN_WORKER_SECRET} - HAIXUN_NODE_WORKER_ID: ${HAIXUN_NODE_WORKER_ID:-} - HAIXUN_WORKER_POLL_MS: ${HAIXUN_WORKER_POLL_MS:-3000} - depends_on: - api: - condition: service_healthy - - web: - build: - context: .. - dockerfile: deploy/Dockerfile.web.static - restart: unless-stopped - ports: - - "${HAIXUN_WEB_PORT:-8080}:80" - depends_on: - api: - condition: service_healthy - - init: - profiles: ["init"] - build: - context: .. - dockerfile: deploy/Dockerfile.api - entrypoint: ["/app/deploy/docker/entrypoint-init.sh"] - env_file: - - .env - depends_on: - mongo: - condition: service_healthy - redis: - condition: service_healthy - -volumes: - mongo_data: - redis_data: \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml deleted file mode 100644 index 3313722..0000000 --- a/deploy/docker-compose.yml +++ /dev/null @@ -1,37 +0,0 @@ -services: - mongo: - image: mongo:7 - container_name: gateway-mongo - restart: unless-stopped - ports: - - "27017:27017" - environment: - MONGO_INITDB_DATABASE: gateway - volumes: - - mongo_data:/data/db - - ./mongo/init:/docker-entrypoint-initdb.d:ro - healthcheck: - test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"] - interval: 5s - timeout: 5s - retries: 10 - start_period: 10s - - redis: - image: redis:7-alpine - container_name: gateway-redis - restart: unless-stopped - ports: - - "6379:6379" - command: ["redis-server", "--appendonly", "yes"] - volumes: - - redis_data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 10 - -volumes: - mongo_data: - redis_data: \ No newline at end of file diff --git a/deploy/docker/entrypoint-api.sh b/deploy/docker/entrypoint-api.sh deleted file mode 100644 index daf8b4d..0000000 --- a/deploy/docker/entrypoint-api.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -set -eu - -: "${HAIXUN_MONGO_URI:=mongodb://mongo:27017}" -: "${HAIXUN_MONGO_DATABASE:=haixun}" -: "${HAIXUN_REDIS_ADDR:=redis:6379}" -: "${HAIXUN_AUTH_ACCESS_SECRET:?HAIXUN_AUTH_ACCESS_SECRET is required}" -: "${HAIXUN_AUTH_REFRESH_SECRET:?HAIXUN_AUTH_REFRESH_SECRET is required}" -: "${HAIXUN_WORKER_SECRET:?HAIXUN_WORKER_SECRET is required}" - -export HAIXUN_MONGO_URI HAIXUN_MONGO_DATABASE HAIXUN_REDIS_ADDR -export HAIXUN_AUTH_ACCESS_SECRET HAIXUN_AUTH_REFRESH_SECRET HAIXUN_WORKER_SECRET - -envsubst < /app/deploy/config/gateway.runtime.yaml.tpl > /tmp/gateway.runtime.yaml -exec /app/haixun-api -f /tmp/gateway.runtime.yaml \ No newline at end of file diff --git a/deploy/docker/entrypoint-init.sh b/deploy/docker/entrypoint-init.sh deleted file mode 100644 index 7e730f1..0000000 --- a/deploy/docker/entrypoint-init.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -set -eu - -: "${HAIXUN_MONGO_URI:=mongodb://mongo:27017}" -: "${HAIXUN_MONGO_DATABASE:=haixun}" -: "${HAIXUN_REDIS_ADDR:=redis:6379}" -: "${HAIXUN_AUTH_ACCESS_SECRET:?HAIXUN_AUTH_ACCESS_SECRET is required}" -: "${HAIXUN_AUTH_REFRESH_SECRET:?HAIXUN_AUTH_REFRESH_SECRET is required}" -: "${HAIXUN_WORKER_SECRET:?HAIXUN_WORKER_SECRET is required}" -: "${INIT_TENANT_ID:=default}" -: "${INIT_ADMIN_EMAIL:=admin@haixun.local}" -: "${INIT_ADMIN_PASSWORD:?INIT_ADMIN_PASSWORD is required}" - -export HAIXUN_MONGO_URI HAIXUN_MONGO_DATABASE HAIXUN_REDIS_ADDR -export HAIXUN_AUTH_ACCESS_SECRET HAIXUN_AUTH_REFRESH_SECRET HAIXUN_WORKER_SECRET - -envsubst < /app/deploy/config/gateway.runtime.yaml.tpl > /tmp/gateway.runtime.yaml -exec /app/haixun-tool init \ - -f /tmp/gateway.runtime.yaml \ - -tenant "$INIT_TENANT_ID" \ - -email "$INIT_ADMIN_EMAIL" \ - -password "$INIT_ADMIN_PASSWORD" \ No newline at end of file diff --git a/deploy/docker/entrypoint-worker.sh b/deploy/docker/entrypoint-worker.sh deleted file mode 100644 index bb6bd0b..0000000 --- a/deploy/docker/entrypoint-worker.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -set -eu - -: "${HAIXUN_MONGO_URI:=mongodb://mongo:27017}" -: "${HAIXUN_MONGO_DATABASE:=haixun}" -: "${HAIXUN_REDIS_ADDR:=redis:6379}" -: "${HAIXUN_AUTH_ACCESS_SECRET:?HAIXUN_AUTH_ACCESS_SECRET is required}" -: "${HAIXUN_AUTH_REFRESH_SECRET:?HAIXUN_AUTH_REFRESH_SECRET is required}" -: "${HAIXUN_WORKER_SECRET:?HAIXUN_WORKER_SECRET is required}" - -export HAIXUN_MONGO_URI HAIXUN_MONGO_DATABASE HAIXUN_REDIS_ADDR -export HAIXUN_AUTH_ACCESS_SECRET HAIXUN_AUTH_REFRESH_SECRET HAIXUN_WORKER_SECRET - -envsubst < /app/deploy/config/gateway.worker.runtime.yaml.tpl > /tmp/gateway.worker.runtime.yaml -exec /app/haixun-worker -f /tmp/gateway.worker.runtime.yaml \ No newline at end of file diff --git a/deploy/mongo/init/01-gateway-indexes.js b/deploy/mongo/init/01-gateway-indexes.js deleted file mode 100644 index 82ff7b3..0000000 --- a/deploy/mongo/init/01-gateway-indexes.js +++ /dev/null @@ -1,31 +0,0 @@ -// Gateway MongoDB 初始化(僅在 data volume 首次建立時執行) -// 與 internal/model/notification/repository/* Index20260520001UP 對齊 -// 既有 volume 請執行:make mongo-index - -db = db.getSiblingDB('gateway'); - -print('Creating indexes on notifications...'); - -db.notifications.createIndex( - { tenant_id: 1, kind: 1, idempotency_key: 1 }, - { unique: true, name: 'idx_notifications_tenant_kind_idempotency' } -); - -db.notifications.createIndex( - { tenant_id: 1, uid: 1, occurred_at: -1 }, - { name: 'idx_notifications_tenant_uid_occurred' } -); - -db.notifications.createIndex( - { status: 1, attempts: 1, occurred_at: 1 }, - { name: 'idx_notifications_status_attempts_occurred' } -); - -print('Creating indexes on notification_dlq...'); - -db.notification_dlq.createIndex( - { tenant_id: 1, occurred_at: -1 }, - { name: 'idx_notification_dlq_tenant_occurred' } -); - -print('Gateway Mongo init done.'); diff --git a/deploy/nginx.conf b/deploy/nginx.conf deleted file mode 100644 index 7ea24d1..0000000 --- a/deploy/nginx.conf +++ /dev/null @@ -1,55 +0,0 @@ -server { - listen 80; - server_name _; - root /usr/share/nginx/html; - index index.html; - - gzip on; - gzip_comp_level 5; - gzip_min_length 256; - gzip_types - text/css - text/javascript - application/javascript - application/json - application/xml - image/svg+xml; - - # Vite 產物:檔名含 hash,可長期快取 - location /assets/ { - add_header Cache-Control "public, max-age=31536000, immutable"; - try_files $uri =404; - } - - location /downloads/ { - add_header Cache-Control "public, max-age=86400"; - try_files $uri =404; - } - - location /illustrations/ { - add_header Cache-Control "public, max-age=86400"; - try_files $uri =404; - } - - # SPA 入口與路由:不快取,避免部署後仍載入舊版 shell - location = /index.html { - add_header Cache-Control "no-cache"; - try_files $uri =404; - } - - location /api/ { - proxy_pass http://api:8890; - 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; - proxy_buffering off; - proxy_read_timeout 3600s; - proxy_send_timeout 3600s; - } - - location / { - try_files $uri $uri/ /index.html; - } -} \ No newline at end of file diff --git a/scripts/debug-opencode-raw.sh b/scripts/debug-opencode-raw.sh deleted file mode 100755 index b92e53c..0000000 --- a/scripts/debug-opencode-raw.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash -# 直接對比 OpenCode upstream 與本機 gateway 的原始 JSON/SSE,方便排查空回應 -# -# 用法: -# OPENCODE_TOKEN=sk-xxx ./scripts/debug-opencode-raw.sh - -set -u - -BASE_URL="${BASE_URL:-http://127.0.0.1:8890}" -OPENCODE_TOKEN="${OPENCODE_TOKEN:-}" -MODEL="${MODEL:-deepseek-v4-flash}" -MESSAGE="${MESSAGE:-Introduce yourself in one sentence.}" - -if [[ -z "$OPENCODE_TOKEN" ]]; then - echo "OPENCODE_TOKEN is required" - exit 1 -fi - -require_cmd() { - command -v "$1" >/dev/null 2>&1 || { echo "missing command: $1"; exit 1; } -} - -require_cmd curl -require_cmd jq - -payload="$(jq -n \ - --arg model "$MODEL" \ - --arg message "$MESSAGE" \ - '{ - model: $model, - messages: [{role: "user", content: $message}], - max_tokens: 2048, - thinking: {type: "disabled"} - }')" - -echo "=== OpenCode upstream (non-stream) ===" -curl -sS -m 120 -X POST "https://opencode.ai/zen/go/v1/chat/completions" \ - -H "Authorization: Bearer ${OPENCODE_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$payload" | jq . - -echo "" -echo "=== Local gateway (non-stream) ===" -curl -sS -m 120 -X POST "${BASE_URL}/api/v1/ai/chat" \ - -H "Authorization: Bearer ${OPENCODE_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg provider "opencode-go" \ - --arg model "$MODEL" \ - --arg message "$MESSAGE" \ - '{provider:$provider,model:$model,messages:[{role:"user",content:$message}],max_tokens:2048}')" | jq . - -echo "" -echo "=== OpenCode upstream (stream, first 20 lines) ===" -curl -sS -N -m 120 -X POST "https://opencode.ai/zen/go/v1/chat/completions" \ - -H "Authorization: Bearer ${OPENCODE_TOKEN}" \ - -H "Content-Type: application/json" \ - -H "Accept: text/event-stream" \ - -d "$(echo "$payload" | jq '.stream=true')" | head -n 20 - -echo "" -echo "=== Local gateway (stream, first 20 lines) ===" -curl -sS -N -m 120 -X POST "${BASE_URL}/api/v1/ai/chat/stream" \ - -H "Authorization: Bearer ${OPENCODE_TOKEN}" \ - -H "Content-Type: application/json" \ - -H "Accept: text/event-stream" \ - -d "$(jq -n \ - --arg provider "opencode-go" \ - --arg model "$MODEL" \ - --arg message "$MESSAGE" \ - '{provider:$provider,model:$model,messages:[{role:"user",content:$message}],max_tokens:2048}')" | head -n 20 \ No newline at end of file diff --git a/scripts/dev-with-style-8d.sh b/scripts/dev-with-style-8d.sh deleted file mode 100644 index 94997b6..0000000 --- a/scripts/dev-with-style-8d.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -BACKEND_DIR="$ROOT_DIR/haixun-backend" -CONFIG_FILE="${HAIXUN_BACKEND_CONFIG:-etc/gateway.yaml}" -BACKEND_URL="${HAIXUN_BACKEND_URL:-http://127.0.0.1:8890}" - -pids=() -owned_pids=() - -cleanup() { - local code=$? - if ((${#owned_pids[@]} > 0)); then - echo "" - echo "[dev-8d] stopping backend and worker..." - kill "${owned_pids[@]}" 2>/dev/null || true - wait "${owned_pids[@]}" 2>/dev/null || true - fi - exit "$code" -} - -trap cleanup EXIT INT TERM - -if curl -fsS "$BACKEND_URL/api/v1/health" >/dev/null 2>&1; then - echo "[dev-8d] backend already running: $BACKEND_URL" -else - echo "[dev-8d] starting Go backend: $CONFIG_FILE" - ( - cd "$BACKEND_DIR" - go run ./gateway.go -f "$CONFIG_FILE" - ) & - pids+=("$!") - owned_pids+=("$!") - - echo "[dev-8d] waiting for backend health..." - for _ in $(seq 1 30); do - if curl -fsS "$BACKEND_URL/api/v1/health" >/dev/null 2>&1; then - break - fi - if ! kill -0 "${pids[0]}" 2>/dev/null; then - wait "${pids[0]}" - fi - sleep 1 - done - - if ! curl -fsS "$BACKEND_URL/api/v1/health" >/dev/null 2>&1; then - echo "[dev-8d] backend health check timed out: $BACKEND_URL" >&2 - exit 1 - fi -fi - -echo "[dev-8d] starting Node 8D worker" -( - cd "$ROOT_DIR" - npm run worker:style-8d -) & -pids+=("$!") -owned_pids+=("$!") - -echo "[dev-8d] running pids=${pids[*]}" -echo "[dev-8d] press Ctrl+C to stop both" - -while true; do - for pid in "${owned_pids[@]}"; do - if ! kill -0 "$pid" 2>/dev/null; then - wait "$pid" - exit $? - fi - done - sleep 1 -done diff --git a/scripts/package-extension.sh b/scripts/package-extension.sh deleted file mode 100755 index 47f378c..0000000 --- a/scripts/package-extension.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -EXT_DIR="$ROOT_DIR/extension/haixun-threads-sync" -OUT_DIR="$ROOT_DIR/haixun-backend/web/public/downloads" -OUT_FILE="$OUT_DIR/haixun-threads-sync.zip" - -if [[ ! -f "$EXT_DIR/manifest.json" ]]; then - echo "extension not found: $EXT_DIR" >&2 - exit 1 -fi - -mkdir -p "$OUT_DIR" -rm -f "$OUT_FILE" - -( - cd "$(dirname "$EXT_DIR")" - zip -qr "$OUT_FILE" "$(basename "$EXT_DIR")" \ - -x "*.DS_Store" -x "*__MACOSX*" -) - -VERSION="$(python3 -c "import json; print(json.load(open('$EXT_DIR/manifest.json'))['version'])")" -echo "packed haixun-threads-sync v$VERSION -> $OUT_FILE" \ No newline at end of file diff --git a/scripts/prod-common.sh b/scripts/prod-common.sh deleted file mode 100755 index 51e9577..0000000 --- a/scripts/prod-common.sh +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2034 -# Shared helpers for production Docker scripts. - -_PROD_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BACKEND_DIR="$(cd "$_PROD_SCRIPT_DIR/.." && pwd)" -DEPLOY_DIR="$BACKEND_DIR/deploy" -COMPOSE_FILE="$DEPLOY_DIR/docker-compose.prod.yml" -ENV_FILE="$DEPLOY_DIR/.env" -ENV_EXAMPLE="$DEPLOY_DIR/.env.example" - -prod_common_init() { - : -} - -prod_load_env() { - prod_common_init - - if [[ ! -f "$ENV_FILE" ]]; then - if [[ -f "$ENV_EXAMPLE" ]]; then - cp "$ENV_EXAMPLE" "$ENV_FILE" - echo "[prod] created $ENV_FILE from .env.example — 請先修改密鑰與管理員密碼" - else - echo "[prod] missing $ENV_FILE" >&2 - exit 1 - fi - fi - - set -a - # shellcheck disable=SC1090 - source "$ENV_FILE" - set +a - - GO_REPLICAS="${GO_WORKER_REPLICAS:-1}" - NODE_REPLICAS="${NODE_STYLE8D_WORKER_REPLICAS:-1}" - WEB_PORT="${HAIXUN_WEB_PORT:-8080}" - MONGO_DB="${HAIXUN_MONGO_DATABASE:-haixun}" -} - -prod_require_docker() { - if ! command -v docker >/dev/null 2>&1; then - echo "[prod] docker is required" >&2 - exit 1 - fi -} - -prod_compose() { - prod_common_init - docker compose -f "$COMPOSE_FILE" "$@" -} - -prod_service_health() { - local service="$1" - prod_compose ps --format json "$service" 2>/dev/null \ - | grep -o '"Health":"[^"]*"' \ - | head -1 \ - | cut -d'"' -f4 \ - || true -} - -prod_named_container_health() { - local name="$1" - docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{if .State.Running}}running{{else}}stopped{{end}}{{end}}' \ - "$name" 2>/dev/null || true -} - -prod_named_container_running() { - local name="$1" - [[ "$(docker inspect --format '{{.State.Running}}' "$name" 2>/dev/null || echo false)" == "true" ]] -} - -prod_deps_healthy() { - local mongo_ok redis_ok - mongo_ok="$(prod_service_health mongo)" - redis_ok="$(prod_service_health redis)" - if [[ "$mongo_ok" == "healthy" && "$redis_ok" == "healthy" ]]; then - return 0 - fi - - # compose ps 偶發失敗時,改查實際 container(避免重複 create 撞名) - mongo_ok="$(prod_named_container_health haixun-prod-mongo-1)" - redis_ok="$(prod_named_container_health haixun-prod-redis-1)" - [[ "$mongo_ok" == "healthy" && "$redis_ok" == "healthy" ]] -} - -prod_wait_deps_healthy() { - echo "[prod] waiting for mongo/redis..." - for _ in $(seq 1 90); do - if prod_deps_healthy; then - return 0 - fi - sleep 1 - done - echo "[prod] mongo/redis did not become healthy in time" >&2 - exit 1 -} - -prod_ensure_deps() { - if prod_deps_healthy; then - echo "[prod] mongo + redis already healthy — 略過重啟(資料在 named volume)" - return 0 - fi - - echo "[prod] starting mongo + redis..." - if prod_named_container_running haixun-prod-mongo-1 && prod_named_container_running haixun-prod-redis-1; then - prod_compose start mongo redis 2>/dev/null \ - || prod_compose up -d --no-recreate mongo redis - else - prod_compose up -d mongo redis - fi - prod_wait_deps_healthy -} - -prod_mongo_has_members() { - prod_compose exec -T mongo mongosh --quiet "$MONGO_DB" --eval \ - 'db.members.countDocuments({})' 2>/dev/null \ - | tr -d '\r' \ - | grep -Eq '^[1-9][0-9]*$' -} - -prod_should_skip_init() { - if [[ "${HAIXUN_SKIP_INIT:-0}" == "1" ]]; then - return 0 - fi - if [[ "${PROD_FORCE_INIT:-0}" == "1" ]]; then - return 1 - fi - if prod_mongo_has_members; then - return 0 - fi - return 1 -} - -prod_run_init_if_needed() { - if prod_should_skip_init; then - if [[ "${HAIXUN_SKIP_INIT:-0}" == "1" ]]; then - echo "[prod] skip init (HAIXUN_SKIP_INIT=1)" - else - echo "[prod] skip init (Mongo 已有資料;若要強制重跑請設 PROD_FORCE_INIT=1)" - fi - return 0 - fi - - echo "[prod] running bootstrap init..." - prod_compose --profile init run --rm init -} - -prod_build_web_if_static() { - prod_common_init - if [[ "${HAIXUN_WEB_BUILD_MODE:-static}" == "static" ]]; then - echo "[prod] building frontend static files (vite → web/dist)..." - (cd "$BACKEND_DIR" && make web-build) - else - echo "[prod] HAIXUN_WEB_BUILD_MODE=docker — web image will compile inside Docker" - fi -} - -prod_start_app_services() { - local build_flag=() - if [[ "${PROD_SKIP_BUILD:-0}" != "1" ]]; then - build_flag=(--build) - fi - - echo "[prod] starting api, web, workers (go=${GO_REPLICAS}, node-style-8d=${NODE_REPLICAS})..." - prod_compose up -d "${build_flag[@]}" \ - --no-deps \ - --scale "go-worker=${GO_REPLICAS}" \ - --scale "node-worker-style-8d=${NODE_REPLICAS}" \ - api web go-worker node-worker-style-8d -} - -prod_wait_api_health() { - echo "[prod] waiting for API health..." - for _ in $(seq 1 60); do - if curl -fsS "http://127.0.0.1:${WEB_PORT}/api/v1/health" >/dev/null 2>&1; then - return 0 - fi - sleep 1 - done - echo "[prod] API health check timed out" >&2 - exit 1 -} - -prod_print_volume_hint() { - echo " Data: Mongo/Redis 使用 named volume(重啟 container 不會清資料)" - echo " Update app: make -C haixun-backend prod-update" - echo " Wipe data: make -C haixun-backend prod-wipe-data # 會刪除 volume" -} - -prod_print_stack_summary() { - echo "" - echo "[prod] stack is up" - echo " Web: http://127.0.0.1:${WEB_PORT}" - echo " API: http://127.0.0.1:${WEB_PORT}/api/v1/health (via nginx)" - echo " Go worker: ${GO_REPLICAS} replica(s)" - echo " Node 8D: ${NODE_REPLICAS} replica(s)" - echo " Env: ${ENV_FILE}" - echo " Stop: make -C haixun-backend prod-down" - echo " Logs: make -C haixun-backend prod-logs" - prod_print_volume_hint -} \ No newline at end of file diff --git a/scripts/prod-deps.sh b/scripts/prod-deps.sh deleted file mode 100755 index 3ac8b63..0000000 --- a/scripts/prod-deps.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# shellcheck source=scripts/prod-common.sh -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/prod-common.sh" - -prod_load_env -prod_require_docker - -cd "$DEPLOY_DIR" - -prod_ensure_deps - -echo "" -echo "[prod] mongo + redis ready" -prod_print_volume_hint \ No newline at end of file diff --git a/scripts/prod-down.sh b/scripts/prod-down.sh deleted file mode 100755 index 94e9cf1..0000000 --- a/scripts/prod-down.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# shellcheck source=scripts/prod-common.sh -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/prod-common.sh" - -prod_common_init -cd "$DEPLOY_DIR" - -prod_compose down --remove-orphans -echo "[prod] stopped(Mongo/Redis 資料仍在 named volume,下次 prod / prod-deps 會沿用)" \ No newline at end of file diff --git a/scripts/prod-logs.sh b/scripts/prod-logs.sh deleted file mode 100755 index 43fa9e5..0000000 --- a/scripts/prod-logs.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -COMPOSE_FILE="$BACKEND_DIR/deploy/docker-compose.prod.yml" - -cd "$BACKEND_DIR/deploy" -docker compose -f "$COMPOSE_FILE" logs -f --tail=200 "${@:-}" \ No newline at end of file diff --git a/scripts/prod-up.sh b/scripts/prod-up.sh deleted file mode 100755 index e3dce92..0000000 --- a/scripts/prod-up.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# shellcheck source=scripts/prod-common.sh -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/prod-common.sh" - -prod_load_env -prod_require_docker - -cd "$BACKEND_DIR" -prod_build_web_if_static - -cd "$DEPLOY_DIR" - -echo "[prod] building images..." -prod_compose build - -prod_ensure_deps -prod_run_init_if_needed - -prod_start_app_services -prod_wait_api_health -prod_print_stack_summary \ No newline at end of file diff --git a/scripts/prod-update.sh b/scripts/prod-update.sh deleted file mode 100755 index 5398784..0000000 --- a/scripts/prod-update.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# 只重建/重啟 API、Web、Workers;不碰 mongo/redis(資料留在 volume)。 - -# shellcheck source=scripts/prod-common.sh -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/prod-common.sh" - -prod_load_env -prod_require_docker - -cd "$BACKEND_DIR" -prod_build_web_if_static - -cd "$DEPLOY_DIR" - -if ! prod_deps_healthy; then - echo "[prod] mongo/redis 未在運行,先啟動依賴(不會清 volume)..." - prod_ensure_deps -else - echo "[prod] mongo + redis 維持運行 — 只更新應用層" -fi - -echo "[prod] building app images (api, web, workers)..." -prod_compose build api web go-worker node-worker-style-8d - -prod_start_app_services -prod_wait_api_health -prod_print_stack_summary \ No newline at end of file diff --git a/scripts/prod-wipe-data.sh b/scripts/prod-wipe-data.sh deleted file mode 100755 index 1bbb805..0000000 --- a/scripts/prod-wipe-data.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# 危險:停止 stack 並刪除 Mongo/Redis named volume。 - -# shellcheck source=scripts/prod-common.sh -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/prod-common.sh" - -prod_load_env -prod_require_docker - -cd "$DEPLOY_DIR" - -echo "[prod] 這會刪除 haixun-prod_mongo_data 與 haixun-prod_redis_data 內所有資料。" -read -r -p "輸入 yes 才會繼續: " confirm -if [[ "$confirm" != "yes" ]]; then - echo "[prod] cancelled" - exit 1 -fi - -prod_compose down -v --remove-orphans -echo "[prod] volumes removed — 下次 make prod 會是全新資料庫" \ No newline at end of file diff --git a/scripts/restart-all.sh b/scripts/restart-all.sh deleted file mode 100755 index f8c1d04..0000000 --- a/scripts/restart-all.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -bash "$BACKEND_DIR/scripts/stop-all.sh" -bash "$BACKEND_DIR/scripts/start-all.sh" \ No newline at end of file diff --git a/scripts/start-all.sh b/scripts/start-all.sh deleted file mode 100755 index 98b9dba..0000000 --- a/scripts/start-all.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -BACKEND_DIR="$ROOT_DIR/haixun-backend" -RUN_DIR="$BACKEND_DIR/.run" -LOG_DIR="$RUN_DIR/logs" -COMPOSE_FILE="$BACKEND_DIR/deploy/docker-compose.yml" -CONFIG_FILE="${HAIXUN_BACKEND_CONFIG:-etc/gateway.yaml}" -BACKEND_URL="${HAIXUN_BACKEND_URL:-http://127.0.0.1:8890}" -WEB_URL="${HAIXUN_WEB_URL:-http://127.0.0.1:5173}" - -mkdir -p "$RUN_DIR" "$LOG_DIR" - -bash "$BACKEND_DIR/scripts/stop-all.sh" - -if ! command -v docker >/dev/null 2>&1; then - echo "[start-all] docker not found; skip mongo/redis" >&2 -else - echo "[start-all] starting mongo + redis..." - docker compose -f "$COMPOSE_FILE" up -d -fi - -echo "[start-all] starting Go API ($CONFIG_FILE)..." -( - cd "$BACKEND_DIR" - go run ./gateway.go -f "$CONFIG_FILE" -) >"$LOG_DIR/api.log" 2>&1 & -echo $! >"$RUN_DIR/api.pid" - -echo "[start-all] waiting for API health ($BACKEND_URL)..." -for _ in $(seq 1 40); do - if curl -fsS "$BACKEND_URL/api/v1/health" >/dev/null 2>&1; then - break - fi - if ! kill -0 "$(cat "$RUN_DIR/api.pid")" 2>/dev/null; then - echo "[start-all] API exited early; see $LOG_DIR/api.log" >&2 - tail -n 20 "$LOG_DIR/api.log" >&2 || true - exit 1 - fi - sleep 1 -done -if ! curl -fsS "$BACKEND_URL/api/v1/health" >/dev/null 2>&1; then - echo "[start-all] API health check timed out; see $LOG_DIR/api.log" >&2 - exit 1 -fi - -if [[ ! -d "$BACKEND_DIR/web/node_modules" ]]; then - echo "[start-all] installing web dependencies..." - (cd "$BACKEND_DIR/web" && npm install) -fi - -echo "[start-all] starting web dev server..." -( - cd "$BACKEND_DIR/web" - npm run dev -) >"$LOG_DIR/web.log" 2>&1 & -echo $! >"$RUN_DIR/web.pid" - -echo "[start-all] starting Node 8D worker..." -( - cd "$ROOT_DIR" - npm run worker:style-8d -) >"$LOG_DIR/worker.log" 2>&1 & -echo $! >"$RUN_DIR/worker.pid" - -sleep 2 - -echo "" -echo "[start-all] all services started" -echo " API: $BACKEND_URL" -echo " Web: $WEB_URL" -echo " Logs: $LOG_DIR/{api,web,worker}.log" -echo " Stop: make -C haixun-backend stop-all" -echo " Status: make -C haixun-backend status-all" \ No newline at end of file diff --git a/scripts/status-all.sh b/scripts/status-all.sh deleted file mode 100755 index 7d740be..0000000 --- a/scripts/status-all.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -RUN_DIR="$BACKEND_DIR/.run" -COMPOSE_FILE="$BACKEND_DIR/deploy/docker-compose.yml" -BACKEND_URL="${HAIXUN_BACKEND_URL:-http://127.0.0.1:8890}" -WEB_URL="${HAIXUN_WEB_URL:-http://127.0.0.1:5173}" - -check_pid() { - local name="$1" - local file="$RUN_DIR/${name}.pid" - if [[ -f "$file" ]]; then - local pid - pid="$(cat "$file" 2>/dev/null || true)" - if [[ -n "${pid:-}" ]] && kill -0 "$pid" 2>/dev/null; then - echo " $name: running (pid=$pid)" - return 0 - fi - fi - echo " $name: stopped" - return 1 -} - -echo "Haixun dev services" -echo "" - -if command -v docker >/dev/null 2>&1 && [[ -f "$COMPOSE_FILE" ]]; then - echo "Docker:" - docker compose -f "$COMPOSE_FILE" ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " (docker compose not running)" - echo "" -fi - -echo "Processes:" -check_pid api || true -check_pid web || true -check_pid worker || true -echo "" - -echo "Health:" -if curl -fsS "$BACKEND_URL/api/v1/health" >/dev/null 2>&1; then - echo " API health: OK ($BACKEND_URL)" -else - echo " API health: down ($BACKEND_URL)" -fi -if curl -fsS "$WEB_URL" >/dev/null 2>&1; then - echo " Web: OK ($WEB_URL)" -else - echo " Web: down ($WEB_URL)" -fi \ No newline at end of file diff --git a/scripts/stop-all.sh b/scripts/stop-all.sh deleted file mode 100755 index e336405..0000000 --- a/scripts/stop-all.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -BACKEND_DIR="$ROOT_DIR/haixun-backend" -RUN_DIR="$BACKEND_DIR/.run" -COMPOSE_FILE="$BACKEND_DIR/deploy/docker-compose.yml" - -stop_pid_file() { - local name="$1" - local file="$RUN_DIR/${name}.pid" - if [[ ! -f "$file" ]]; then - return 0 - fi - local pid - pid="$(cat "$file" 2>/dev/null || true)" - if [[ -n "${pid:-}" ]] && kill -0 "$pid" 2>/dev/null; then - echo "[stop-all] stopping $name (pid=$pid)" - kill "$pid" 2>/dev/null || true - for _ in $(seq 1 10); do - kill -0 "$pid" 2>/dev/null || break - sleep 0.2 - done - kill -9 "$pid" 2>/dev/null || true - fi - rm -f "$file" -} - -echo "[stop-all] stopping tracked processes..." -for name in worker web api; do - stop_pid_file "$name" -done - -echo "[stop-all] stopping stray processes..." -pkill -f "haixun-backend/worker/style-8d-worker" 2>/dev/null || true -pkill -f "worker:style-8d" 2>/dev/null || true -pkill -f "haixun-backend/web/node_modules/.bin/vite" 2>/dev/null || true -pkill -f "go run ./gateway.go -f etc/gateway.yaml" 2>/dev/null || true -# `go run` spawns a compiled binary child under the go-build cache (e.g. -# ~/Library/Caches/go-build/.../gateway) that is NOT killed when the parent -# wrapper dies; kill the orphan too so it stops serving stale routes on the API -# port and frees the port for the freshly built binary. -pkill -f "/gateway -f etc/gateway.yaml" 2>/dev/null || true -pkill -f "dev-with-style-8d.sh" 2>/dev/null || true - -if command -v docker >/dev/null 2>&1 && [[ -f "$COMPOSE_FILE" ]]; then - echo "[stop-all] stopping docker compose (mongo + redis)..." - docker compose -f "$COMPOSE_FILE" down >/dev/null 2>&1 || true -fi - -echo "[stop-all] done" \ No newline at end of file diff --git a/scripts/test-job-cancel.sh b/scripts/test-job-cancel.sh deleted file mode 100755 index abdad27..0000000 --- a/scripts/test-job-cancel.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -# 示範 job 建立與取消:running 時會透過 Redis jobs:cancel: 戳 worker -# -# 用法: -# ./scripts/test-job-cancel.sh - -set -u - -BASE_URL="${BASE_URL:-http://127.0.0.1:8890}" -SCOPE="${SCOPE:-user}" -SCOPE_ID="${SCOPE_ID:-demo_user_1}" - -require_cmd() { - command -v "$1" >/dev/null 2>&1 || { echo "missing command: $1"; exit 1; } -} - -require_cmd curl -require_cmd jq - -echo "== create demo_long_task ==" -CREATE_BODY="$(curl -sS -X POST "${BASE_URL}/api/v1/jobs" \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg scope "$SCOPE" --arg scope_id "$SCOPE_ID" '{ - template_type: "demo_long_task", - scope: $scope, - scope_id: $scope_id, - payload: {target: "demo"} - }')")" - -echo "$CREATE_BODY" | jq . -JOB_ID="$(echo "$CREATE_BODY" | jq -r '.data.id // empty')" -if [[ -z "$JOB_ID" ]]; then - echo "failed to create job" - exit 1 -fi - -echo "" -echo "== wait until running (max 10s) ==" -for _ in $(seq 1 20); do - STATUS="$(curl -sS "${BASE_URL}/api/v1/jobs/${JOB_ID}" | jq -r '.data.status')" - echo "status: $STATUS" - if [[ "$STATUS" == "running" || "$STATUS" == "cancel_requested" ]]; then - break - fi - if [[ "$STATUS" == "succeeded" || "$STATUS" == "cancelled" ]]; then - echo "job finished before cancel test: $STATUS" - exit 0 - fi - sleep 0.5 -done - -echo "" -echo "== cancel while worker is active ==" -CANCEL_BODY="$(curl -sS -X POST "${BASE_URL}/api/v1/jobs/${JOB_ID}/cancel" \ - -H "Content-Type: application/json" \ - -d '{"reason":"demo cancel from script"}')" -echo "$CANCEL_BODY" | jq . - -echo "" -echo "== poll until cancelled (max 20s) ==" -for _ in $(seq 1 40); do - BODY="$(curl -sS "${BASE_URL}/api/v1/jobs/${JOB_ID}")" - STATUS="$(echo "$BODY" | jq -r '.data.status')" - PHASE="$(echo "$BODY" | jq -r '.data.phase')" - SUMMARY="$(echo "$BODY" | jq -r '.data.progress.summary')" - echo "status=$STATUS phase=$PHASE summary=$SUMMARY" - if [[ "$STATUS" == "cancelled" ]]; then - echo "" - echo "ok: worker acknowledged cancel" - exit 0 - fi - sleep 0.5 -done - -echo "timeout waiting for cancelled status" -exit 1 \ No newline at end of file diff --git a/scripts/test-job-concurrency.sh b/scripts/test-job-concurrency.sh deleted file mode 100755 index 02dd3db..0000000 --- a/scripts/test-job-concurrency.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash -# Verify scheme B: same scope_id+target blocks concurrent runs; different targets allow parallel. -# -# Usage: -# ./scripts/test-job-concurrency.sh - -set -u - -BASE_URL="${BASE_URL:-http://127.0.0.1:8890}" -SCOPE="${SCOPE:-user}" -SCOPE_ID="${SCOPE_ID:-concurrency_demo_user}" -TEMPLATE_TYPE="${TEMPLATE_TYPE:-demo_long_task}" -TARGET_A="${TARGET_A:-building_A}" -TARGET_B="${TARGET_B:-building_B}" - -require_cmd() { - command -v "$1" >/dev/null 2>&1 || { echo "missing command: $1"; exit 1; } -} - -require_cmd curl -require_cmd jq - -create_job() { - local target="$1" - curl -sS -X POST "${BASE_URL}/api/v1/jobs" \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg scope "$SCOPE" --arg scope_id "$SCOPE_ID" --arg template "$TEMPLATE_TYPE" --arg target "$target" '{ - template_type: $template, - scope: $scope, - scope_id: $scope_id, - payload: {target: $target} - }')" -} - -echo "== configure template for scheme B ==" -PUT_BODY="$(curl -sS -X PUT "${BASE_URL}/api/v1/job/templates/${TEMPLATE_TYPE}" \ - -H "Content-Type: application/json" \ - -d "$(jq -n '{ - name: "Demo Long Task", - enabled: true, - repeatable: true, - concurrency_policy: "allow_parallel", - dedupe_keys: ["scope_id", "target"], - timeout_seconds: 600, - cancel_policy: {supported: true, mode: "cooperative", grace_seconds: 30}, - retry_policy: {max_attempts: 2, backoff_seconds: [30, 120]}, - steps: [ - {id: "prepare", name: "Prepare", worker_type: "go", timeout_seconds: 60, cancelable: true}, - {id: "execute", name: "Execute", worker_type: "go", timeout_seconds: 300, cancelable: true}, - {id: "finalize", name: "Finalize", worker_type: "go", timeout_seconds: 30, cancelable: false} - ] - }')")" -echo "$PUT_BODY" | jq . -if [[ "$(echo "$PUT_BODY" | jq -r '.code')" != "102000" ]]; then - echo "failed to upsert template" - exit 1 -fi - -echo "" -echo "== same target: first create should succeed ==" -FIRST="$(create_job "$TARGET_A")" -echo "$FIRST" | jq . -if [[ "$(echo "$FIRST" | jq -r '.code')" != "102000" ]]; then - echo "first create failed" - exit 1 -fi - -echo "" -echo "== same target: second create should fail while first is active ==" -SECOND="$(create_job "$TARGET_A")" -echo "$SECOND" | jq . -if [[ "$(echo "$SECOND" | jq -r '.code')" == "102000" ]]; then - echo "expected duplicate create to fail" - exit 1 -fi - -echo "" -echo "== different target: should succeed in parallel ==" -THIRD="$(create_job "$TARGET_B")" -echo "$THIRD" | jq . -if [[ "$(echo "$THIRD" | jq -r '.code')" != "102000" ]]; then - echo "different target create failed" - exit 1 -fi - -echo "" -echo "ok: scheme B concurrency behaves as expected" \ No newline at end of file