fix2
This commit is contained in:
parent
184e7763ac
commit
eaf029a16f
|
|
@ -1,9 +1,11 @@
|
|||
.run
|
||||
.git
|
||||
web/node_modules
|
||||
# web/dist 保留給 deploy/Dockerfile.web.static(本機 make web-build 後 COPY)
|
||||
worker/node_modules
|
||||
.run
|
||||
.cursor
|
||||
**/node_modules
|
||||
backend/bin
|
||||
frontend/dist
|
||||
**/*_test.go
|
||||
**/.DS_Store
|
||||
infra
|
||||
*.md
|
||||
!deploy/**
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
# --- secrets / env(不要 commit 實值)---
|
||||
infra/.env
|
||||
infra/etc/haixun.env
|
||||
/opt/haixun/
|
||||
deploy/.env
|
||||
|
||||
# --- build 產物 ---
|
||||
backend/bin/
|
||||
|
|
|
|||
303
Makefile
303
Makefile
|
|
@ -1,33 +1,32 @@
|
|||
# 巡樓 monorepo Makefile
|
||||
# 兩種模式:
|
||||
# dev - 本機開發(docker 起 Mongo/Redis,go run / vite dev)
|
||||
# prod - 建置產物(前端 dist + linux Go binary),並可部署成 systemd 服務 + nginx
|
||||
# dev - 本機開發(docker 起 Mongo/Redis,go run / vite dev 在本機跑)
|
||||
# docker - 全棧一鍵啟動(Mongo/Redis/gateway/worker/node-worker/web 全在 docker)
|
||||
#
|
||||
# 常用:
|
||||
# make dev-infra # 起本機 Mongo/Redis
|
||||
# make dev-backend # 跑 gateway (:8890)
|
||||
# make dev-frontend # 跑前端 (:5173,proxy 到 :8890)
|
||||
# make build # 產出 frontend/dist + backend/bin/{gateway,worker}
|
||||
# sudo make install # 部署到目標主機(見 infra/README.md)
|
||||
# make up # 一鍵:build + 啟動整套(含前後端、worker、DB)
|
||||
# make down # 停掉整套
|
||||
# make logs # 追全部日誌
|
||||
# make dev-infra # 本機開發:只起 Mongo/Redis
|
||||
# make dev-backend # 本機開發:go run gateway (:8890)
|
||||
# make dev-frontend # 本機開發:vite dev (:5173)
|
||||
|
||||
SHELL := /bin/bash
|
||||
|
||||
# --- 路徑 ---
|
||||
BACKEND_DIR := backend
|
||||
FRONTEND_DIR := frontend
|
||||
INFRA_DIR := infra
|
||||
BIN_DIR := $(BACKEND_DIR)/bin
|
||||
|
||||
# --- 部署目標(install 用)---
|
||||
DEPLOY_ROOT := /opt/haixun
|
||||
WEB_ROOT := /var/www/haixun
|
||||
# --- docker compose(prod 全棧)---
|
||||
COMPOSE_FILE := deploy/docker-compose.yml
|
||||
ENV_FILE := deploy/.env
|
||||
COMPOSE := docker compose -f $(COMPOSE_FILE) --env-file $(ENV_FILE)
|
||||
|
||||
# --- 交叉編譯目標(在 mac 上 build 給 linux 主機)---
|
||||
GOOS ?= linux
|
||||
GOARCH ?= amd64
|
||||
|
||||
# --- docker compose ---
|
||||
COMPOSE := docker compose -f $(INFRA_DIR)/docker-compose.yml --env-file $(INFRA_DIR)/.env
|
||||
# --- docker compose(本地測試;base + dev override + dev env)---
|
||||
DEV_ENV_FILE := deploy/.env.dev
|
||||
DEV_COMPOSE := docker compose -f $(COMPOSE_FILE) -f deploy/docker-compose.dev.yml --env-file $(DEV_ENV_FILE)
|
||||
# go worker 用 gateway 內嵌,dev 不啟動獨立 worker 容器
|
||||
DEV_SERVICES := mongo redis init gateway node-worker web
|
||||
|
||||
# --- dev 用 worker secret(對應 etc/gateway.yaml 的 InternalWorker.Secret)---
|
||||
DEV_WORKER_SECRET := haixun-dev-worker-secret
|
||||
|
|
@ -40,172 +39,140 @@ help: ## 顯示可用指令
|
|||
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
||||
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
$(ENV_FILE):
|
||||
@test -f $(ENV_FILE) || (cp deploy/.env.example $(ENV_FILE) && echo "已從 deploy/.env.example 建立 $(ENV_FILE),請填入密碼與 secret 後再 make up")
|
||||
|
||||
# ============================================================
|
||||
# DEV
|
||||
# DOCKER(全棧一鍵)
|
||||
# ============================================================
|
||||
|
||||
$(INFRA_DIR)/.env:
|
||||
@test -f $(INFRA_DIR)/.env || (cp $(INFRA_DIR)/.env.example $(INFRA_DIR)/.env && echo "已從 .env.example 建立 $(INFRA_DIR)/.env,請視需要修改密碼")
|
||||
.PHONY: up
|
||||
up: $(ENV_FILE) ## [docker] 一鍵啟動整套(build + up,背景)
|
||||
$(COMPOSE) up -d --build
|
||||
@echo ""
|
||||
@echo "=== 啟動中 ==="
|
||||
@echo " 前端: http://localhost:$$(grep -E '^WEB_PORT=' $(ENV_FILE) | cut -d= -f2)"
|
||||
@echo " 健康檢查: curl http://localhost:$$(grep -E '^WEB_PORT=' $(ENV_FILE) | cut -d= -f2)/api/v1/health"
|
||||
@echo " 看狀態: make ps 看日誌: make logs"
|
||||
|
||||
.PHONY: dev-infra
|
||||
dev-infra: $(INFRA_DIR)/.env ## [dev] 起本機 Mongo + Redis (docker)
|
||||
$(COMPOSE) up -d
|
||||
$(COMPOSE) ps
|
||||
|
||||
.PHONY: dev-infra-down
|
||||
dev-infra-down: ## [dev] 停掉本機 Mongo + Redis
|
||||
.PHONY: down
|
||||
down: ## [docker] 停掉整套(保留資料 volume)
|
||||
$(COMPOSE) down
|
||||
|
||||
.PHONY: dev-backend
|
||||
dev-backend: ## [dev] 跑 gateway API (:8890)
|
||||
.PHONY: down-clean
|
||||
down-clean: ## [docker] 停掉整套並刪除資料 volume(清空 Mongo/Redis)
|
||||
$(COMPOSE) down -v
|
||||
|
||||
.PHONY: build
|
||||
build: $(ENV_FILE) ## [docker] 只 build image,不啟動
|
||||
$(COMPOSE) build
|
||||
|
||||
.PHONY: rebuild
|
||||
rebuild: $(ENV_FILE) ## [docker] 無快取重新 build 並重啟
|
||||
$(COMPOSE) build --no-cache
|
||||
$(COMPOSE) up -d
|
||||
|
||||
.PHONY: restart
|
||||
restart: ## [docker] 重啟所有服務
|
||||
$(COMPOSE) restart
|
||||
|
||||
.PHONY: ps
|
||||
ps: ## [docker] 看服務狀態
|
||||
$(COMPOSE) ps
|
||||
|
||||
.PHONY: logs
|
||||
logs: ## [docker] 追全部日誌(Ctrl-C 離開)
|
||||
$(COMPOSE) logs -f
|
||||
|
||||
.PHONY: logs-gateway
|
||||
logs-gateway: ## [docker] 只看 gateway 日誌
|
||||
$(COMPOSE) logs -f gateway
|
||||
|
||||
.PHONY: init
|
||||
init: $(ENV_FILE) ## [docker] 重跑一次 DB 初始化(索引/權限/admin,冪等)
|
||||
$(COMPOSE) run --rm init
|
||||
|
||||
.PHONY: config
|
||||
config: $(ENV_FILE) ## [docker] 驗證 compose 設定
|
||||
$(COMPOSE) config >/dev/null && echo "docker compose config OK"
|
||||
|
||||
# ============================================================
|
||||
# DEV(本地測試,全走 docker)
|
||||
# ============================================================
|
||||
|
||||
.PHONY: dev-up
|
||||
dev-up: ## [dev] 一鍵:本地 docker 測試(gateway 開 :8890,web 開 :8080,go worker 內嵌)
|
||||
$(DEV_COMPOSE) up -d --build $(DEV_SERVICES)
|
||||
@echo ""
|
||||
@echo "=== 本地測試啟動中 ==="
|
||||
@echo " 前端: http://localhost:$$(grep -E '^WEB_PORT=' $(DEV_ENV_FILE) | cut -d= -f2)"
|
||||
@echo " API: http://localhost:8890/api/v1/health"
|
||||
@echo " DevHeaderFallback 已開(可用 X-Tenant-ID / X-UID 模擬登入)"
|
||||
|
||||
.PHONY: dev-down
|
||||
dev-down: ## [dev] 停掉本地 docker(保留資料)
|
||||
$(DEV_COMPOSE) down
|
||||
|
||||
.PHONY: dev-down-clean
|
||||
dev-down-clean: ## [dev] 停掉本地 docker 並清空資料 volume
|
||||
$(DEV_COMPOSE) down -v
|
||||
|
||||
.PHONY: dev-rebuild
|
||||
dev-rebuild: ## [dev] 改 code 後重新 build 並重啟本地 docker
|
||||
$(DEV_COMPOSE) up -d --build $(DEV_SERVICES)
|
||||
|
||||
.PHONY: dev-restart
|
||||
dev-restart: ## [dev] 只重啟(改了 etc/*.yaml 後不用 rebuild,掛載即生效)
|
||||
$(DEV_COMPOSE) restart gateway node-worker
|
||||
|
||||
.PHONY: dev-ps
|
||||
dev-ps: ## [dev] 看本地 docker 狀態
|
||||
$(DEV_COMPOSE) ps
|
||||
|
||||
.PHONY: dev-logs
|
||||
dev-logs: ## [dev] 追本地 docker 日誌
|
||||
$(DEV_COMPOSE) logs -f
|
||||
|
||||
.PHONY: dev-init
|
||||
dev-init: ## [dev] 重跑一次 DB 初始化(索引/權限/admin,冪等)
|
||||
$(DEV_COMPOSE) run --rm init
|
||||
|
||||
.PHONY: dev
|
||||
dev: ## [dev] 本地 docker 測試說明
|
||||
@echo "本地測試(全 docker):"
|
||||
@echo " make dev-up # 一鍵起整套(前端 :8080、API :8890)"
|
||||
@echo " make dev-logs # 看日誌"
|
||||
@echo " make dev-restart # 改 etc/*.yaml 後重啟(免 rebuild)"
|
||||
@echo " make dev-rebuild # 改 Go/前端 code 後重 build"
|
||||
@echo " make dev-down # 停(保留資料)"
|
||||
|
||||
# --- 進階:不進 docker,直接本機 go run / vite(需自備 Go/Node)---
|
||||
.PHONY: local-infra
|
||||
local-infra: ## [local] 只起 Mongo + Redis(docker),其餘本機跑
|
||||
$(DEV_COMPOSE) up -d mongo redis
|
||||
$(DEV_COMPOSE) ps
|
||||
|
||||
.PHONY: local-backend
|
||||
local-backend: ## [local] 本機 go run gateway (:8890)
|
||||
cd $(BACKEND_DIR) && go run . -f etc/gateway.yaml
|
||||
|
||||
.PHONY: dev-worker
|
||||
dev-worker: ## [dev] 跑 Go job worker (:8891)
|
||||
.PHONY: local-worker
|
||||
local-worker: ## [local] 本機 go run Go job worker (:8891)
|
||||
cd $(BACKEND_DIR) && go run ./cmd/worker -f etc/gateway.worker.yaml
|
||||
|
||||
.PHONY: dev-node-worker
|
||||
dev-node-worker: ## [dev] 跑 Node playwright worker (style-8d)
|
||||
.PHONY: local-node-worker
|
||||
local-node-worker: ## [local] 本機跑 Node playwright worker (style-8d)
|
||||
cd $(BACKEND_DIR)/worker && npm install && \
|
||||
HAIXUN_WORKER_SECRET=$(DEV_WORKER_SECRET) HAIXUN_BACKEND_URL=$(DEV_BACKEND_URL) npm run style-8d
|
||||
|
||||
.PHONY: dev-frontend
|
||||
dev-frontend: ## [dev] 跑前端 dev server (:5173)
|
||||
.PHONY: local-frontend
|
||||
local-frontend: ## [local] 本機跑前端 dev server (:5173)
|
||||
cd $(FRONTEND_DIR) && npm install && npm run dev
|
||||
|
||||
.PHONY: dev-init
|
||||
dev-init: ## [dev] 初始化 DB(索引/權限)並建立 admin 帳號(可用 INIT_ADMIN_EMAIL / INIT_ADMIN_PASSWORD 覆寫)
|
||||
.PHONY: local-init
|
||||
local-init: ## [local] 本機初始化 DB(索引/權限)並建立 admin
|
||||
cd $(BACKEND_DIR) && go run ./cmd/tool init -f etc/gateway.yaml
|
||||
|
||||
.PHONY: dev
|
||||
dev: ## [dev] 顯示本機開發要開的終端
|
||||
@echo "本機開發請分開幾個終端執行:"
|
||||
@echo " 1) make dev-infra # Mongo/Redis"
|
||||
@echo " 2) make dev-backend # gateway :8890"
|
||||
@echo " 3) make dev-worker # go worker(可選)"
|
||||
@echo " 4) make dev-node-worker # node worker(需 style-8d 時)"
|
||||
@echo " 5) make dev-frontend # 前端 :5173"
|
||||
|
||||
# ============================================================
|
||||
# 安裝依賴(新機器 clone 後執行一次)
|
||||
# ============================================================
|
||||
|
||||
.PHONY: bootstrap-sys
|
||||
bootstrap-sys: ## [需 sudo] 安裝系統依賴(Go, Node.js, Docker, Nginx)— Ubuntu 專用
|
||||
@echo "=== 安裝系統套件: nginx, curl, gnupg ==="
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq nginx curl gnupg ca-certificates
|
||||
@echo ""
|
||||
@echo "=== 安裝 Go 1.22+ ==="
|
||||
@GO_VER=$$(curl -sL https://go.dev/VERSION?m=text 2>/dev/null | head -1 || echo "go1.22"); \
|
||||
echo "下載 $$GO_VER.linux-amd64.tar.gz"; \
|
||||
curl -sL "https://go.dev/dl/$$GO_VER.linux-amd64.tar.gz" -o /tmp/go.tar.gz && \
|
||||
sudo rm -rf /usr/local/go && \
|
||||
sudo tar -C /usr/local -xzf /tmp/go.tar.gz && \
|
||||
rm /tmp/go.tar.gz; \
|
||||
echo 'export PATH=/usr/local/go/bin:$$PATH' | sudo tee /etc/profile.d/go.sh
|
||||
@echo ""
|
||||
@echo "=== 安裝 Node.js LTS ==="
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
sudo apt-get install -y -qq nodejs
|
||||
@echo ""
|
||||
@echo "=== 安裝 Docker Engine + Compose ==="
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
sudo usermod -aG docker $(USER) 2>/dev/null || true
|
||||
@echo ""
|
||||
@echo "=== 安裝 Nginx ==="
|
||||
sudo apt-get install -y -qq nginx
|
||||
@echo ""
|
||||
@echo "✓ 系統依賴安裝完成。"
|
||||
@echo " 登出再登入後,Go/Node/Docker 即可使用(group 變更生效)。"
|
||||
@echo " 接著執行 make bootstrap 安裝專案層依賴。"
|
||||
@echo ""
|
||||
|
||||
.PHONY: bootstrap
|
||||
bootstrap: ## 安裝專案層依賴(Go modules + npm + Playwright),需先執行 make bootstrap-sys
|
||||
@echo "=== [1/4] Go modules ==="
|
||||
cd $(BACKEND_DIR) && go mod tidy
|
||||
@echo ""
|
||||
@echo "=== [2/4] 前端 npm ==="
|
||||
cd $(FRONTEND_DIR) && npm install
|
||||
@echo ""
|
||||
@echo "=== [3/4] Node worker npm ==="
|
||||
cd $(BACKEND_DIR)/worker && npm install
|
||||
@echo ""
|
||||
@echo "=== [4/4] Playwright 瀏覽器(chromium)+ 系統依賴 ==="
|
||||
cd $(BACKEND_DIR)/worker && sudo npx playwright install --with-deps chromium
|
||||
@echo ""
|
||||
@echo "✓ 全裝完成。後續步驟:"
|
||||
@echo " 1) make dev-infra # 起 Mongo/Redis (Docker)"
|
||||
@echo " 2) make dev-init # 初始化 DB + 建立 admin"
|
||||
@echo " 3) make dev-backend # 啟動 gateway (:8890)"
|
||||
@echo " 4) sudo make install # 正式部署(含 systemd + nginx)"
|
||||
|
||||
# ============================================================
|
||||
# BUILD (prod 產物)
|
||||
# ============================================================
|
||||
|
||||
.PHONY: build
|
||||
build: build-frontend build-backend ## [prod] 建置前端 dist 與 Go binary
|
||||
|
||||
.PHONY: build-frontend
|
||||
build-frontend: ## [prod] 前端靜態建置 (tsc + vite) -> frontend/dist
|
||||
cd $(FRONTEND_DIR) && npm ci && npm run build
|
||||
|
||||
.PHONY: build-backend
|
||||
build-backend: ## [prod] 交叉編譯 gateway + worker -> backend/bin (linux)
|
||||
cd $(BACKEND_DIR) && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
|
||||
go build -trimpath -ldflags "-s -w" -o bin/gateway .
|
||||
cd $(BACKEND_DIR) && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
|
||||
go build -trimpath -ldflags "-s -w" -o bin/worker ./cmd/worker
|
||||
cd $(BACKEND_DIR) && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
|
||||
go build -trimpath -ldflags "-s -w" -o bin/tool ./cmd/tool
|
||||
@echo "binary 已輸出到 $(BIN_DIR)/(gateway / worker / tool)"
|
||||
|
||||
# ============================================================
|
||||
# PROD (部署)
|
||||
# ============================================================
|
||||
|
||||
.PHONY: prod-infra
|
||||
prod-infra: $(INFRA_DIR)/.env ## [prod] 起 Mongo + Redis (docker,背景)
|
||||
$(COMPOSE) up -d
|
||||
|
||||
.PHONY: prod-infra-down
|
||||
prod-infra-down: ## [prod] 停掉 Mongo + Redis
|
||||
$(COMPOSE) down
|
||||
|
||||
.PHONY: prod-init
|
||||
prod-init: ## [prod] 目標主機初始化 DB + 建立 admin(讀 /opt/haixun/etc/haixun.env;可加 INIT_ADMIN_EMAIL/PASSWORD)
|
||||
@test -x $(DEPLOY_ROOT)/bin/tool || (echo "缺少 $(DEPLOY_ROOT)/bin/tool,請先 make install" && exit 1)
|
||||
set -a; . $(DEPLOY_ROOT)/etc/haixun.env; set +a; \
|
||||
$(DEPLOY_ROOT)/bin/tool init -f $(DEPLOY_ROOT)/etc/gateway.prod.yaml
|
||||
|
||||
.PHONY: install
|
||||
install: ## [prod] 安裝 binary/前端/設定/systemd/nginx(需 root,在目標主機執行)
|
||||
@test -f $(BIN_DIR)/gateway || (echo "缺少 $(BIN_DIR)/gateway,請先在能 build 的機器執行 make build" && exit 1)
|
||||
@test -d $(FRONTEND_DIR)/dist || (echo "缺少 $(FRONTEND_DIR)/dist,請先 make build-frontend" && exit 1)
|
||||
id haixun >/dev/null 2>&1 || useradd --system --home $(DEPLOY_ROOT) --shell /usr/sbin/nologin haixun
|
||||
install -d $(DEPLOY_ROOT)/bin $(DEPLOY_ROOT)/etc $(DEPLOY_ROOT)/node-worker $(WEB_ROOT)
|
||||
install -m 0755 $(BIN_DIR)/gateway $(DEPLOY_ROOT)/bin/gateway
|
||||
install -m 0755 $(BIN_DIR)/worker $(DEPLOY_ROOT)/bin/worker
|
||||
install -m 0755 $(BIN_DIR)/tool $(DEPLOY_ROOT)/bin/tool
|
||||
install -m 0644 $(BACKEND_DIR)/etc/gateway.prod.yaml $(DEPLOY_ROOT)/etc/gateway.prod.yaml
|
||||
install -m 0644 $(BACKEND_DIR)/etc/gateway.worker.prod.yaml $(DEPLOY_ROOT)/etc/gateway.worker.prod.yaml
|
||||
rm -rf $(WEB_ROOT)/* && cp -r $(FRONTEND_DIR)/dist/* $(WEB_ROOT)/
|
||||
cp -r $(BACKEND_DIR)/worker/* $(DEPLOY_ROOT)/node-worker/
|
||||
cd $(DEPLOY_ROOT)/node-worker && npm ci && npx playwright install --with-deps chromium
|
||||
install -m 0644 $(INFRA_DIR)/systemd/haixun-gateway.service /etc/systemd/system/haixun-gateway.service
|
||||
install -m 0644 $(INFRA_DIR)/systemd/haixun-worker.service /etc/systemd/system/haixun-worker.service
|
||||
install -m 0644 $(INFRA_DIR)/systemd/haixun-node-worker.service /etc/systemd/system/haixun-node-worker.service
|
||||
install -m 0644 $(INFRA_DIR)/nginx/haixun.conf /etc/nginx/conf.d/haixun.conf
|
||||
chown -R haixun:haixun $(DEPLOY_ROOT) $(WEB_ROOT)
|
||||
@echo "----"
|
||||
@echo "接著(只做一次)建立 secret 檔:"
|
||||
@echo " cp $(INFRA_DIR)/etc/haixun.env.example $(DEPLOY_ROOT)/etc/haixun.env && chmod 600 $(DEPLOY_ROOT)/etc/haixun.env && sudoedit $(DEPLOY_ROOT)/etc/haixun.env"
|
||||
@echo "再啟用服務:"
|
||||
@echo " systemctl daemon-reload && systemctl enable --now haixun-gateway haixun-worker haixun-node-worker"
|
||||
@echo " nginx -t && systemctl reload nginx"
|
||||
|
||||
# ============================================================
|
||||
# 驗證 / 維護
|
||||
# ============================================================
|
||||
|
|
@ -227,7 +194,7 @@ test: ## 跑後端測試
|
|||
cd $(BACKEND_DIR) && go test ./...
|
||||
|
||||
.PHONY: verify
|
||||
verify: ## 後端 build/test + 前端 build + compose 語法
|
||||
verify: $(ENV_FILE) ## 後端 build/test + 前端 build + compose 語法
|
||||
cd $(BACKEND_DIR) && go build ./... && go test ./...
|
||||
cd $(FRONTEND_DIR) && npm ci && npm run build
|
||||
$(COMPOSE) config >/dev/null && echo "docker compose config OK"
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@ Host: 0.0.0.0
|
|||
Port: 8890
|
||||
Timeout: 120000
|
||||
|
||||
# 本地「docker 測試」設定(make dev-up 使用)。
|
||||
# 連線走 compose service name(mongo / redis),密碼對齊 deploy/.env.dev。
|
||||
# 純本機 go run(不進 docker)請改用 etc/gateway.yaml(連 127.0.0.1)。
|
||||
Mongo:
|
||||
URI: mongodb://127.0.0.1:27017
|
||||
Database: haixun_dev
|
||||
URI: mongodb://haixun:haixun-dev-pass@mongo:27017/?authSource=admin
|
||||
Database: haixun
|
||||
TimeoutSeconds: 10
|
||||
|
||||
Redis:
|
||||
Addr: 127.0.0.1:6379
|
||||
Addr: redis:6379
|
||||
Password: haixun-dev-redis
|
||||
DB: 0
|
||||
|
||||
Auth:
|
||||
|
|
@ -17,4 +21,25 @@ Auth:
|
|||
RefreshSecret: haixun-dev-refresh-secret-change-me
|
||||
AccessExpireSeconds: 900
|
||||
RefreshExpireSeconds: 2592000
|
||||
# 僅本地開發開啟:允許用 X-Tenant-ID / X-UID header 模擬登入。正式環境必須 false。
|
||||
DevHeaderFallback: true
|
||||
|
||||
Secrets:
|
||||
# 留空 = 機敏資料不加密(本地測試方便)。正式環境用 ${HAIXUN_SECRETS_KEY}。
|
||||
EncryptionKey: ""
|
||||
|
||||
InternalWorker:
|
||||
Secret: haixun-dev-worker-secret
|
||||
|
||||
# dev 用 gateway 內嵌 go worker(勿另開 worker 容器,避免兩個 worker 搶 job)。
|
||||
JobWorker:
|
||||
Enabled: true
|
||||
WorkerType: go
|
||||
|
||||
JobScheduler:
|
||||
Enabled: true
|
||||
IntervalSeconds: 60
|
||||
|
||||
JobReaper:
|
||||
Enabled: true
|
||||
IntervalSeconds: 30
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# 巡樓全棧 docker compose 環境變數範本。
|
||||
# 複製成 deploy/.env 後填入實際值;deploy/.env 不要 commit。
|
||||
# cp deploy/.env.example deploy/.env
|
||||
|
||||
# === Web(對外) ===
|
||||
WEB_PORT=80
|
||||
|
||||
# === Mongo ===
|
||||
MONGO_PORT=27017
|
||||
MONGO_ROOT_USER=haixun
|
||||
# 建議只用英數字。若密碼含 @ : / $ 等字元,HAIXUN_MONGO_URI 會解析錯誤,
|
||||
# 需自行對 compose 內的 URI 做 URL-encode。
|
||||
MONGO_ROOT_PASSWORD=Fafafa54088
|
||||
MONGO_DATABASE=haixun
|
||||
|
||||
# === Redis ===
|
||||
REDIS_PASSWORD=Fafafa54088
|
||||
|
||||
# === JWT / Secrets ===
|
||||
# openssl rand -base64 48 # 三把 secret 各產一把
|
||||
# openssl rand -base64 32 # HAIXUN_SECRETS_KEY(機敏資料落地加密,需 base64 的 32 bytes)
|
||||
HAIXUN_JWT_ACCESS_SECRET=tI3g0UYzmAIC+N8vODem0bG9T9X5g0EEg15d0mH/wEJjbloca7YNo8pEHAsRW1BF
|
||||
HAIXUN_JWT_REFRESH_SECRET=tI3g0UYzmAIC+N8vODem0bG9T9X5g0EEg15d0mH/wEJjbloca7YNo8pEHAsRW1BF
|
||||
HAIXUN_WORKER_SECRET=tI3g0UYzmAIC+N8vODem0bG9T9X5g0EEg15d0mH/wEJjbloca7YNo8pEHAsRW1BF
|
||||
HAIXUN_SECRETS_KEY=+m0Ws4xJBrp+UzuWRyZ43/1P3sB91HmGhtW1FZrGr44=
|
||||
|
||||
# === 初始 admin(init 容器建立,冪等) ===
|
||||
INIT_ADMIN_EMAIL=admin@30cm.net
|
||||
INIT_ADMIN_PASSWORD=Fafafa54088
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# 巡樓全棧 docker compose 環境變數範本。
|
||||
# 複製成 deploy/.env 後填入實際值;deploy/.env 不要 commit。
|
||||
# cp deploy/.env.example deploy/.env
|
||||
|
||||
# === Web(對外) ===
|
||||
WEB_PORT=80
|
||||
|
||||
# === Mongo ===
|
||||
MONGO_PORT=27017
|
||||
MONGO_ROOT_USER=haixun
|
||||
# 建議只用英數字。若密碼含 @ : / $ 等字元,HAIXUN_MONGO_URI 會解析錯誤,
|
||||
# 需自行對 compose 內的 URI 做 URL-encode。
|
||||
MONGO_ROOT_PASSWORD=Fafafa54088
|
||||
MONGO_DATABASE=haixun
|
||||
|
||||
# === Redis ===
|
||||
REDIS_PASSWORD=Fafafa54088
|
||||
|
||||
# === JWT / Secrets ===
|
||||
# openssl rand -base64 48 # 三把 secret 各產一把
|
||||
# openssl rand -base64 32 # HAIXUN_SECRETS_KEY(機敏資料落地加密,需 base64 的 32 bytes)
|
||||
HAIXUN_JWT_ACCESS_SECRET=tI3g0UYzmAIC+N8vODem0bG9T9X5g0EEg15d0mH/wEJjbloca7YNo8pEHAsRW1BF
|
||||
HAIXUN_JWT_REFRESH_SECRET=tI3g0UYzmAIC+N8vODem0bG9T9X5g0EEg15d0mH/wEJjbloca7YNo8pEHAsRW1BF
|
||||
HAIXUN_WORKER_SECRET=tI3g0UYzmAIC+N8vODem0bG9T9X5g0EEg15d0mH/wEJjbloca7YNo8pEHAsRW1BF
|
||||
HAIXUN_SECRETS_KEY=+m0Ws4xJBrp+UzuWRyZ43/1P3sB91HmGhtW1FZrGr44=
|
||||
|
||||
# === 初始 admin(init 容器建立,冪等) ===
|
||||
INIT_ADMIN_EMAIL=admin@30cm.net
|
||||
INIT_ADMIN_PASSWORD=Fafafa54088
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# 後端共用 image:一次 build gateway / worker / tool 三個 binary。
|
||||
# compose 以 entrypoint/command 覆寫,決定該容器當 gateway、job worker 或一次性 init。
|
||||
# build context = repo root(見 deploy/docker-compose.yml)。
|
||||
|
||||
FROM golang:1.23-alpine AS build
|
||||
WORKDIR /src
|
||||
RUN apk add --no-cache git
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ ./
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-s -w" -o /out/gateway . \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-s -w" -o /out/worker ./cmd/worker \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-s -w" -o /out/tool ./cmd/tool
|
||||
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates tzdata wget
|
||||
ENV TZ=UTC
|
||||
WORKDIR /app
|
||||
COPY --from=build /out/ /app/
|
||||
COPY backend/etc/ /app/etc/
|
||||
|
||||
# 預設角色為 gateway;compose 會把 worker / init 用 entrypoint 覆寫。
|
||||
EXPOSE 8890
|
||||
ENTRYPOINT ["/app/gateway"]
|
||||
CMD ["-f", "/app/etc/gateway.prod.yaml"]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Node playwright worker(style-8d)。
|
||||
# 直接使用 Playwright 官方 image(自帶 chromium 與系統依賴),版本對齊 package.json 的 playwright ^1.49.1。
|
||||
# build context = repo root(見 deploy/docker-compose.yml)。
|
||||
|
||||
FROM mcr.microsoft.com/playwright:v1.49.1-jammy
|
||||
WORKDIR /app
|
||||
ENV TZ=UTC
|
||||
COPY backend/worker/package.json backend/worker/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY backend/worker/ ./
|
||||
|
||||
# HAIXUN_BACKEND_URL / HAIXUN_WORKER_SECRET 由 compose 注入。
|
||||
CMD ["npm", "run", "style-8d"]
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# 前端:vite build 出靜態檔,交給 nginx serve,並把 /api 反代到 gateway 容器。
|
||||
# build context = repo root(見 deploy/docker-compose.yml)。
|
||||
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY frontend/package.json frontend/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# 巡樓部署(Docker 一鍵啟動)
|
||||
|
||||
整套系統用單一 `docker compose` 拉起,對外只開前端 `:80`。
|
||||
|
||||
```
|
||||
瀏覽器 → web(nginx :80)
|
||||
├─ 前端靜態檔(vite build)
|
||||
└─ /api 反代 → gateway:8890
|
||||
gateway / worker(Go)─┐
|
||||
node-worker(playwright)┤→ Mongo / Redis(同網路,不對外)
|
||||
```
|
||||
|
||||
| 容器 | 角色 |
|
||||
|------|------|
|
||||
| `mongo` / `redis` | 資料層(只綁 `127.0.0.1`,volume 持久化) |
|
||||
| `init` | run-once:建索引 / 權限 / 第一個 admin(冪等,跑完即退) |
|
||||
| `gateway` | API + scheduler + reaper(:8890,僅內網) |
|
||||
| `worker` | Go job worker |
|
||||
| `node-worker` | playwright style-8d |
|
||||
| `web` | nginx,serve 前端 + 反代 `/api` |
|
||||
|
||||
## 一鍵啟動
|
||||
|
||||
```bash
|
||||
cp deploy/.env.example deploy/.env # 填密碼與 secret
|
||||
make up # = docker compose up -d --build
|
||||
```
|
||||
|
||||
啟動順序由 `depends_on` + healthcheck 保證:Mongo/Redis 健康 → `init` 跑完 → `gateway`/`worker` 起來 → `gateway` 健康 → `node-worker`/`web` 起來。
|
||||
|
||||
## 常用指令
|
||||
|
||||
```bash
|
||||
make ps # 狀態
|
||||
make logs # 全部日誌
|
||||
make logs-gateway # 只看 gateway
|
||||
make down # 停(保留資料)
|
||||
make down-clean # 停並清空 Mongo/Redis volume
|
||||
make rebuild # 無快取重建並重啟
|
||||
make init # 重跑一次 DB 初始化
|
||||
```
|
||||
|
||||
## 健康檢查
|
||||
|
||||
```bash
|
||||
curl http://localhost/api/v1/health
|
||||
```
|
||||
|
||||
## 產生 secret
|
||||
|
||||
```bash
|
||||
openssl rand -base64 48 # JWT access / refresh / worker secret
|
||||
openssl rand -base64 32 # HAIXUN_SECRETS_KEY(機敏資料落地加密)
|
||||
```
|
||||
|
||||
## 注意事項
|
||||
|
||||
- `deploy/.env` 內含 secret,不要 commit。
|
||||
- `MONGO_ROOT_PASSWORD` 建議只用英數字;含 `@ : / $` 等字元時,compose 內的 `HAIXUN_MONGO_URI` 需自行 URL-encode。
|
||||
- 全系統時間 UTC+0(容器 `TZ=UTC`)。
|
||||
- 本機開發(不全容器化)見根目錄 `make dev`。
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# 本地 docker 測試 override(搭配 docker-compose.yml 一起用):
|
||||
# docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.dev.yml --env-file deploy/.env.dev up -d --build
|
||||
# 或直接 `make dev-up`。
|
||||
#
|
||||
# 與正式版差異:
|
||||
# - 後端(gateway/init)改用 gateway.dev.example.yaml(DevHeaderFallback=true、不加密)。
|
||||
# - 掛載 backend/etc,改 yaml 後 restart 容器即可生效,不必 rebuild。
|
||||
# - gateway 對外開 :8890 方便直接打 API;web 走 .env.dev 的 WEB_PORT(預設 8080)。
|
||||
# - go job worker 用 gateway 內嵌(不啟動獨立 worker 容器)。
|
||||
name: haixun
|
||||
|
||||
services:
|
||||
# 本地測試不對外 publish DB port(容器間走 service name),
|
||||
# 避免與其他本機 Mongo/Redis(含舊 haixun-infra)搶 127.0.0.1:27017。
|
||||
mongo:
|
||||
ports: !reset []
|
||||
|
||||
init:
|
||||
command: ["init", "-f", "/app/etc/gateway.dev.example.yaml"]
|
||||
volumes:
|
||||
- ../backend/etc:/app/etc:ro
|
||||
|
||||
gateway:
|
||||
command: ["-f", "/app/etc/gateway.dev.example.yaml"]
|
||||
ports:
|
||||
- "8890:8890"
|
||||
volumes:
|
||||
- ../backend/etc:/app/etc:ro
|
||||
|
||||
node-worker:
|
||||
environment:
|
||||
HAIXUN_BACKEND_URL: "http://gateway:8890"
|
||||
HAIXUN_WORKER_SECRET: "haixun-dev-worker-secret"
|
||||
TZ: "UTC"
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# 巡樓全棧一鍵啟動:Mongo + Redis + gateway + go-worker + node-worker + web(nginx)。
|
||||
# 啟動:docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
|
||||
# 或直接用 repo 根目錄的 `make up`。
|
||||
name: haixun
|
||||
|
||||
# ---- 後端三個容器(gateway / worker / init)共用同一 image 與環境變數 ----
|
||||
x-backend-build: &backend-build
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.backend
|
||||
|
||||
x-backend-env: &backend-env
|
||||
# mongo 走 service name;若密碼含特殊字元請改用 URL-encode(見 .env.example 說明)。
|
||||
HAIXUN_MONGO_URI: "mongodb://${MONGO_ROOT_USER}:${MONGO_ROOT_PASSWORD}@mongo:27017/?authSource=admin"
|
||||
HAIXUN_MONGO_DB: "${MONGO_DATABASE}"
|
||||
HAIXUN_REDIS_ADDR: "redis:6379"
|
||||
HAIXUN_REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
HAIXUN_JWT_ACCESS_SECRET: "${HAIXUN_JWT_ACCESS_SECRET}"
|
||||
HAIXUN_JWT_REFRESH_SECRET: "${HAIXUN_JWT_REFRESH_SECRET}"
|
||||
HAIXUN_SECRETS_KEY: "${HAIXUN_SECRETS_KEY}"
|
||||
HAIXUN_WORKER_SECRET: "${HAIXUN_WORKER_SECRET}"
|
||||
TZ: "UTC"
|
||||
|
||||
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"]
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
# 一次性:建索引 / 權限 catalog / role_permissions / 第一個 admin(冪等,跑完即退出)。
|
||||
init:
|
||||
build: *backend-build
|
||||
image: haixun-backend:latest
|
||||
restart: "no"
|
||||
depends_on:
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *backend-env
|
||||
INIT_ADMIN_EMAIL: "${INIT_ADMIN_EMAIL:-admin@30cm.net}"
|
||||
INIT_ADMIN_PASSWORD: "${INIT_ADMIN_PASSWORD:?INIT_ADMIN_PASSWORD is required}"
|
||||
entrypoint: ["/app/tool"]
|
||||
command: ["init", "-f", "/app/etc/gateway.prod.yaml"]
|
||||
|
||||
gateway:
|
||||
build: *backend-build
|
||||
image: haixun-backend:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
init:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
<<: *backend-env
|
||||
expose:
|
||||
- "8890"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8890/api/v1/health"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
worker:
|
||||
build: *backend-build
|
||||
image: haixun-backend:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
init:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
<<: *backend-env
|
||||
entrypoint: ["/app/worker"]
|
||||
command: ["-f", "/app/etc/gateway.worker.prod.yaml"]
|
||||
|
||||
node-worker:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.node-worker
|
||||
image: haixun-node-worker:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
gateway:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
HAIXUN_BACKEND_URL: "http://gateway:8890"
|
||||
HAIXUN_WORKER_SECRET: "${HAIXUN_WORKER_SECRET}"
|
||||
TZ: "UTC"
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.web
|
||||
image: haixun-web:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
gateway:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${WEB_PORT:-80}:80"
|
||||
|
||||
volumes:
|
||||
mongo_data:
|
||||
redis_data:
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
# 巡樓 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 串流設定。
|
||||
# 巡樓 Console nginx(容器內):serve 前端靜態檔 + /api 反代到 gateway 容器。
|
||||
# upstream 走 compose service name(gateway:8890),不是 127.0.0.1。
|
||||
|
||||
upstream haixun_gateway {
|
||||
server 127.0.0.1:8890;
|
||||
server gateway:8890;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +11,7 @@ server {
|
|||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
root /var/www/haixun;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# 安全標頭
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# 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
103
infra/README.md
|
|
@ -1,103 +0,0 @@
|
|||
# 巡樓部署 (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(機敏資料落地加密)
|
||||
```
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# 巡樓資料服務: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:
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# 部署到目標主機的 /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
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
[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
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
[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
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
[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
|
||||
94
start-dev.sh
94
start-dev.sh
|
|
@ -1,94 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== 啟動巡樓開發環境服務 (不含 infra) ==="
|
||||
echo "前置條件: infra (Mongo/Redis) 必須已啟動,請先執行: make dev-infra"
|
||||
echo ""
|
||||
|
||||
# 檢查 infra 是否運行
|
||||
if ! docker compose -f infra/docker-compose.yml ps --status running 2>/dev/null | grep -q "mongo\|redis"; then
|
||||
echo "⚠️ 警告: infra (Mongo/Redis) 似乎未運行"
|
||||
echo " 請先執行: make dev-infra"
|
||||
echo ""
|
||||
read -p "是否繼續啟動服務? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1. 啟動 Go gateway (port 8890) - 背景執行
|
||||
echo "[1/4] 啟動 Go gateway (:8890)..."
|
||||
cd backend
|
||||
if [ ! -f etc/gateway.yaml ]; then
|
||||
echo " ✗ 找不到 etc/gateway.yaml"
|
||||
exit 1
|
||||
fi
|
||||
nohup go run . -f etc/gateway.yaml > /tmp/haixun-gateway.log 2>&1 &
|
||||
GATEWAY_PID=$!
|
||||
cd ..
|
||||
sleep 2
|
||||
if kill -0 $GATEWAY_PID 2>/dev/null; then
|
||||
echo " ✓ Gateway 已啟動 (PID: $GATEWAY_PID, log: /tmp/haixun-gateway.log)"
|
||||
else
|
||||
echo " ✗ Gateway 啟動失敗,查看日誌: cat /tmp/haixun-gateway.log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Go job worker 已內嵌在 gateway(etc/gateway.yaml JobWorker.Enabled=true)
|
||||
echo "[2/4] Go job worker..."
|
||||
echo " ✓ 使用 gateway 內嵌 worker(勿另開 cmd/worker,避免殘留舊進程搶 job)"
|
||||
|
||||
# 3. 啟動 Node worker (style-8d) - 背景執行
|
||||
echo "[3/4] 啟動 Node worker (style-8d)..."
|
||||
cd backend/worker
|
||||
if [ ! -f package.json ]; then
|
||||
echo " ✗ 找不到 package.json"
|
||||
exit 1
|
||||
fi
|
||||
export HAIXUN_WORKER_SECRET=haixun-dev-worker-secret
|
||||
export HAIXUN_BACKEND_URL=http://127.0.0.1:8890
|
||||
nohup npm run style-8d > /tmp/haixun-node-worker.log 2>&1 &
|
||||
NODE_WORKER_PID=$!
|
||||
cd ../..
|
||||
sleep 3
|
||||
if kill -0 $NODE_WORKER_PID 2>/dev/null; then
|
||||
echo " ✓ Node worker 已啟動 (PID: $NODE_WORKER_PID, log: /tmp/haixun-node-worker.log)"
|
||||
else
|
||||
echo " ✗ Node worker 啟動失敗,查看日誌: cat /tmp/haixun-node-worker.log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. 啟動前端 dev server (port 5173) - 背景執行
|
||||
echo "[4/4] 啟動前端 dev server (:5173)..."
|
||||
cd frontend
|
||||
if [ ! -f package.json ]; then
|
||||
echo " ✗ 找不到 package.json"
|
||||
exit 1
|
||||
fi
|
||||
nohup npm run dev > /tmp/haixun-frontend.log 2>&1 &
|
||||
FRONTEND_PID=$!
|
||||
cd ..
|
||||
sleep 3
|
||||
if kill -0 $FRONTEND_PID 2>/dev/null; then
|
||||
echo " ✓ 前端已啟動 (PID: $FRONTEND_PID, log: /tmp/haixun-frontend.log)"
|
||||
else
|
||||
echo " ✗ 前端啟動失敗,查看日誌: cat /tmp/haixun-frontend.log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 所有開發環境服務已啟動 ==="
|
||||
echo ""
|
||||
echo "服務狀態:"
|
||||
echo " Gateway (API): http://localhost:8890 (PID: $GATEWAY_PID, 內嵌 Go worker)"
|
||||
echo " Node Worker: style-8d worker (PID: $NODE_WORKER_PID)"
|
||||
echo " Frontend (Vite): http://localhost:5173 (PID: $FRONTEND_PID)"
|
||||
echo ""
|
||||
echo "日誌檔案:"
|
||||
echo " Gateway: tail -f /tmp/haixun-gateway.log"
|
||||
echo " Node Worker: tail -f /tmp/haixun-node-worker.log"
|
||||
echo " Frontend: tail -f /tmp/haixun-frontend.log"
|
||||
echo ""
|
||||
echo "停止所有服務: ./stop-dev.sh"
|
||||
echo "查看 infra: make dev-infra # 或 docker compose -f infra/docker-compose.yml ps"
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#!/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"
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "=== 巡樓開發環境服務狀態 ==="
|
||||
echo ""
|
||||
|
||||
# Infra (Mongo/Redis)
|
||||
echo "📦 Infra (Docker):"
|
||||
docker compose -f infra/docker-compose.yml ps 2>/dev/null || echo " 未運行或 docker compose 不可用"
|
||||
echo ""
|
||||
|
||||
# Port 佔用檢查
|
||||
check_port() {
|
||||
local port=$1
|
||||
local name=$2
|
||||
if lsof -ti:$port >/dev/null 2>&1; then
|
||||
local pid=$(lsof -ti:$port | head -1)
|
||||
local cmd=$(ps -p $pid -o comm= 2>/dev/null || echo "unknown")
|
||||
echo " ✅ $name (port $port) - PID: $pid ($cmd)"
|
||||
else
|
||||
echo " ❌ $name (port $port) - 未運行"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "🔌 服務 Port 狀態:"
|
||||
check_port 8890 "Gateway (API)"
|
||||
check_port 8891 "Worker (Go)"
|
||||
check_port 5173 "Frontend (Vite)"
|
||||
|
||||
# Node worker 沒有固定 port,檢查進程
|
||||
echo ""
|
||||
echo "🔧 Node Worker (style-8d):"
|
||||
if pgrep -f "style-8d" >/dev/null 2>&1; then
|
||||
pgrep -f "style-8d" | while read pid; do
|
||||
echo " ✅ 運行中 - PID: $pid"
|
||||
done
|
||||
else
|
||||
echo " ❌ 未運行"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📋 管理指令:"
|
||||
echo " 啟動所有服務: ./start-dev.sh"
|
||||
echo " 停止所有服務: ./stop-dev.sh"
|
||||
echo " 啟動 infra: make dev-infra"
|
||||
echo " 停止 infra: make dev-infra-down"
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#!/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"
|
||||
48
stop-dev.sh
48
stop-dev.sh
|
|
@ -1,48 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== 停止巡樓開發環境服務 (保留 infra) ==="
|
||||
|
||||
# 1. 停止前端 dev server (Vite on :5173)
|
||||
echo "[1/4] 停止前端 dev server..."
|
||||
pkill -f "vite.*dev" 2>/dev/null && echo " ✓ Vite dev server 已停止" || echo " - Vite dev server 未在執行"
|
||||
|
||||
# 2. 停止 Node worker (style-8d)
|
||||
echo "[2/4] 停止 Node worker (style-8d)..."
|
||||
pkill -f "style-8d-worker" 2>/dev/null && echo " ✓ Node worker 已停止" || echo " - Node worker 未在執行"
|
||||
pkill -f "tsx.*style-8d" 2>/dev/null && echo " ✓ tsx worker 已停止" || true
|
||||
|
||||
# 3. 停止 Go worker(含編譯後殘留的 exe/worker 與 go run ./cmd/worker)
|
||||
echo "[3/4] 停止 Go worker..."
|
||||
pkill -f "go run ./cmd/worker" 2>/dev/null || true
|
||||
pkill -f "cmd/worker.*gateway.worker.yaml" 2>/dev/null || true
|
||||
pkill -f "exe/worker -f etc/gateway.worker.yaml" 2>/dev/null || true
|
||||
pkill -f "worker -f etc/gateway.worker.yaml" 2>/dev/null || true
|
||||
if pgrep -f "gateway.worker.yaml" >/dev/null 2>&1; then
|
||||
echo " ⚠ 仍有 worker 殘留,強制結束..."
|
||||
pkill -9 -f "gateway.worker.yaml" 2>/dev/null || true
|
||||
fi
|
||||
echo " ✓ Go worker 已停止"
|
||||
|
||||
# 4. 停止 Go gateway (port 8890)
|
||||
echo "[4/4] 停止 Go gateway..."
|
||||
pkill -f "go run . -f etc/gateway.yaml" 2>/dev/null || true
|
||||
pkill -f "exe/haixun-backend -f etc/gateway.yaml" 2>/dev/null || true
|
||||
pkill -f "haixun-backend -f etc/gateway.yaml" 2>/dev/null || true
|
||||
if pgrep -f "etc/gateway.yaml" >/dev/null 2>&1; then
|
||||
echo " ⚠ 仍有 gateway 殘留,強制結束..."
|
||||
pkill -9 -f "etc/gateway.yaml" 2>/dev/null || true
|
||||
fi
|
||||
echo " ✓ Go gateway 已停止"
|
||||
|
||||
# 5. 清理可能殘留的 port 佔用 (optional, 避免 port 衝突)
|
||||
for port in 8890 8891 5173; do
|
||||
if lsof -ti:$port >/dev/null 2>&1; then
|
||||
echo " ⚠ Port $port 仍被佔用,強制釋放..."
|
||||
lsof -ti:$port | xargs kill -9 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== 開發環境服務已停止 (infra: Mongo/Redis 保持執行) ==="
|
||||
echo "提示: 若需停止 infra,請執行: make dev-infra-down"
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#!/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