Merge pull request #3 from cloud-neutral-toolkit/refactor/makefile-scripts
Refactor/makefile scripts
This commit is contained in:
commit
c364fcf8ff
1
.gitignore
vendored
1
.gitignore
vendored
@ -61,3 +61,4 @@ dist/
|
||||
out/
|
||||
target/
|
||||
xcontrol-account
|
||||
account-export.yaml
|
||||
|
||||
237
Makefile
237
Makefile
@ -7,11 +7,15 @@ MAIN_FILE := ./cmd/accountsvc/main.go
|
||||
PORT ?= 8080
|
||||
OS := $(shell uname -s)
|
||||
|
||||
# Load local environment overrides if present.
|
||||
-include .env
|
||||
export
|
||||
|
||||
DB_NAME := account
|
||||
DB_USER := shenlan
|
||||
DB_PASS := password
|
||||
DB_USER ?= $(or $(POSTGRES_USER),postgres)
|
||||
DB_PASS ?= $(or $(POSTGRES_PASSWORD),password)
|
||||
DB_HOST := 127.0.0.1
|
||||
DB_PORT := 5432
|
||||
DB_PORT := 15432
|
||||
DB_URL := postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable
|
||||
|
||||
REPLICATION_MODE ?= pgsync
|
||||
@ -40,13 +44,23 @@ SUPERADMIN_PASSWORD ?= ChangeMe
|
||||
SUPERADMIN_EMAIL ?= admin@svc.plus
|
||||
|
||||
export PATH := /usr/local/go/bin:$(PATH)
|
||||
export APP_NAME MAIN_FILE PORT OS \
|
||||
DB_NAME DB_USER DB_PASS DB_HOST DB_PORT DB_URL \
|
||||
REPLICATION_MODE DB_ADMIN_USER DB_ADMIN_PASS \
|
||||
GCP_PROJECT GCP_REGION CLOUD_RUN_SERVICE CLOUD_RUN_SERVICE_YAML CLOUD_RUN_STUNNEL_CONF CLOUD_RUN_IMAGE \
|
||||
SCHEMA_FILE PGLOGICAL_INIT_FILE PGLOGICAL_PATCH_FILE PGLOGICAL_REGION_FILE \
|
||||
ACCOUNT_EXPORT_FILE ACCOUNT_IMPORT_FILE ACCOUNT_EMAIL_KEYWORD ACCOUNT_SYNC_CONFIG \
|
||||
SUPERADMIN_USERNAME SUPERADMIN_PASSWORD SUPERADMIN_EMAIL \
|
||||
ACCOUNT_IMPORT_MERGE ACCOUNT_IMPORT_MERGE_STRATEGY ACCOUNT_IMPORT_DRY_RUN \
|
||||
ACCOUNT_IMPORT_MERGE_ALLOWLIST ACCOUNT_IMPORT_EXTRA_FLAGS
|
||||
|
||||
# =========================================
|
||||
# 🧩 基础命令
|
||||
# =========================================
|
||||
|
||||
.PHONY: all init build clean start stop restart dev test help \
|
||||
init-db-core init-db-replication init-db-pglogical \
|
||||
.PHONY: all init build clean start stop restart dev test help integration-test \
|
||||
init-go init-db init-db-core init-db-replication init-db-pglogical \
|
||||
stunnel-start \
|
||||
reinit-pglogical account-sync-push account-sync-pull account-sync-mirror create-db-user db-reset \
|
||||
cloudrun-build cloudrun-deploy cloudrun-stunnel
|
||||
|
||||
@ -67,6 +81,7 @@ help:
|
||||
@echo "make reinit-pglogical 重新初始化 pglogical schema"
|
||||
@echo "make dev 热重载开发模式"
|
||||
@echo "make clean 清理构建产物"
|
||||
@echo "make integration-test 运行集成测试用例(初始化 + 创建管理员)"
|
||||
@echo "make cloudrun-build 构建并推送 Cloud Run 镜像"
|
||||
@echo "make cloudrun-deploy 部署 Cloud Run Service"
|
||||
@echo "make cloudrun-stunnel 更新 Cloud Run stunnel 配置 secret"
|
||||
@ -78,257 +93,125 @@ help:
|
||||
init: init-go init-db
|
||||
|
||||
init-go:
|
||||
@if [ ! -f go.mod ]; then \
|
||||
echo ">>> go.mod not found, initializing module"; \
|
||||
go mod init account; \
|
||||
fi
|
||||
go mod tidy
|
||||
@echo ">>> 检查 Go 环境"
|
||||
@if ! command -v go >/dev/null; then \
|
||||
echo "未安装 Go,自动安装中..."; \
|
||||
([ "$(OS)" = "Darwin" ] && brew install go@1.24 && brew link --overwrite --force go@1.24) || \
|
||||
(sudo apt-get update && sudo apt-get install -y golang); \
|
||||
fi
|
||||
@echo ">>> 配置 Go Proxy"
|
||||
@(curl -fsSL --max-time 5 https://goproxy.cn >/dev/null && go env -w GOPROXY=https://goproxy.cn,direct) || \
|
||||
(go env -w GOPROXY=https://proxy.golang.org,direct)
|
||||
@go mod tidy
|
||||
@bash scripts/init-go.sh
|
||||
|
||||
init-db:
|
||||
@echo ">>> 初始化数据库 schema"
|
||||
@command -v psql >/dev/null || (echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端" && exit 1)
|
||||
@$(MAKE) init-db-core
|
||||
@$(MAKE) init-db-replication
|
||||
@bash scripts/init-db.sh
|
||||
|
||||
init-db-core:
|
||||
@echo ">>> 初始化业务 schema ($(SCHEMA_FILE))"
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(SCHEMA_FILE)
|
||||
@bash scripts/init-db-core.sh
|
||||
|
||||
init-db-replication:
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
$(MAKE) init-db-pglogical; \
|
||||
else \
|
||||
echo ">>> 跳过 pglogical 初始化 (REPLICATION_MODE=$(REPLICATION_MODE))"; \
|
||||
fi
|
||||
@bash scripts/init-db-replication.sh
|
||||
|
||||
init-db-pglogical:
|
||||
@if [ -f $(PGLOGICAL_INIT_FILE) ]; then \
|
||||
echo ">>> 初始化 pglogical schema (REPLICATION_MODE=pglogical)"; \
|
||||
if PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" 2>/dev/null | grep -qx 't'; then \
|
||||
PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-v ON_ERROR_STOP=1 -f $(PGLOGICAL_INIT_FILE); \
|
||||
elif psql "$(DB_URL)" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then \
|
||||
psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_INIT_FILE); \
|
||||
else \
|
||||
echo "⚠️ 当前用户非超级用户,跳过 pglogical 初始化"; \
|
||||
fi; \
|
||||
fi; \
|
||||
if [ -f $(PGLOGICAL_PATCH_FILE) ]; then \
|
||||
echo ">>> 应用 pglogical 默认值补丁"; \
|
||||
psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_PATCH_FILE); \
|
||||
fi
|
||||
@bash scripts/init-db-pglogical.sh
|
||||
|
||||
# =========================================
|
||||
# 🧠 PGLogical 双节点初始化
|
||||
# =========================================
|
||||
|
||||
init-pglogical-region:
|
||||
@[ -n "$(REGION_DB_URL)" ] || (echo "❌ 缺少 REGION_DB_URL"; exit 1)
|
||||
@[ -n "$(NODE_NAME)" ] || (echo "❌ 缺少 NODE_NAME"; exit 1)
|
||||
@[ -n "$(NODE_DSN)" ] || (echo "❌ 缺少 NODE_DSN"; exit 1)
|
||||
@[ -n "$(SUBSCRIPTION_NAME)" ] || (echo "❌ 缺少 SUBSCRIPTION_NAME"; exit 1)
|
||||
@[ -n "$(PROVIDER_DSN)" ] || (echo "❌ 缺少 PROVIDER_DSN"; exit 1)
|
||||
@psql "$(REGION_DB_URL)" -v ON_ERROR_STOP=1 \
|
||||
-v NODE_NAME="$(NODE_NAME)" \
|
||||
-v NODE_DSN="$(NODE_DSN)" \
|
||||
-v SUBSCRIPTION_NAME="$(SUBSCRIPTION_NAME)" \
|
||||
-v PROVIDER_DSN="$(PROVIDER_DSN)" \
|
||||
-f $(PGLOGICAL_REGION_FILE)
|
||||
@bash scripts/init-pglogical-region.sh
|
||||
|
||||
init-pglogical-region-cn:
|
||||
@$(MAKE) init-pglogical-region \
|
||||
REGION_DB_URL="$(DB_URL)" \
|
||||
NODE_NAME="node_cn" \
|
||||
NODE_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
SUBSCRIPTION_NAME="sub_from_global" \
|
||||
PROVIDER_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx"
|
||||
@bash scripts/init-pglogical-region-cn.sh
|
||||
|
||||
init-pglogical-region-global:
|
||||
@$(MAKE) init-pglogical-region \
|
||||
REGION_DB_URL="$(DB_URL)" \
|
||||
NODE_NAME="node_global" \
|
||||
NODE_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
SUBSCRIPTION_NAME="sub_from_cn" \
|
||||
PROVIDER_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx"
|
||||
@bash scripts/init-pglogical-region-global.sh
|
||||
|
||||
# =========================================
|
||||
# 📦 数据库迁移与管理
|
||||
# =========================================
|
||||
|
||||
create-db-user:
|
||||
@echo ">>> 创建数据库用户 $(DB_USER)"
|
||||
@command -v psql >/dev/null || (echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端" && exit 1)
|
||||
@echo "正在以 postgres 超级用户身份创建用户..."
|
||||
@sudo -u postgres psql -c "CREATE USER $(DB_USER) WITH PASSWORD '$(DB_PASS)';" || echo "⚠️ 用户可能已存在"
|
||||
@sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $(DB_NAME) TO $(DB_USER);"
|
||||
@echo "✓ 数据库用户创建完成"
|
||||
@bash scripts/create-db-user.sh
|
||||
|
||||
migrate-db:
|
||||
@echo ">>> 执行数据库迁移"
|
||||
@go run ./cmd/migratectl/main.go migrate --dsn "$(DB_URL)" --dir sql/migrations
|
||||
@bash scripts/migrate-db.sh
|
||||
|
||||
dump-schema:
|
||||
@echo ">>> 导出 schema 到 $(SCHEMA_FILE)"
|
||||
@pg_dump -s -O -x "$(DB_URL)" > $(SCHEMA_FILE)
|
||||
@bash scripts/dump-schema.sh
|
||||
|
||||
db-reset:
|
||||
@echo "⚠️ 即将重置整个 PostgreSQL 数据库集群 ..."
|
||||
@read -p "确定要重置数据库集群? 这将删除所有数据! [y/N] " confirm && \
|
||||
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||
echo ">>> 停止 PostgreSQL 服务 ..."; \
|
||||
sudo systemctl stop postgresql; \
|
||||
echo ">>> 删除数据库集群 16 main ..."; \
|
||||
sudo pg_dropcluster --stop 16 main; \
|
||||
echo ">>> 清理数据目录 ..."; \
|
||||
sudo rm -rf /var/lib/postgresql/16/main; \
|
||||
echo ">>> 清理配置目录 ..."; \
|
||||
sudo rm -rf /etc/postgresql/16/main; \
|
||||
echo ">>> 创建新的数据库集群 ..."; \
|
||||
sudo pg_createcluster 16 main --start; \
|
||||
echo "✓ PostgreSQL 集群重置完成"; \
|
||||
else \
|
||||
echo "取消重置"; \
|
||||
fi
|
||||
@bash scripts/db-reset.sh
|
||||
|
||||
drop-db:
|
||||
@echo "⚠️ 即将删除数据库 $(DB_NAME) ..."
|
||||
@read -p "确定要删除数据库 $(DB_NAME)? [y/N] " confirm && \
|
||||
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||
echo ">>> 强制断开现有连接 ..."; \
|
||||
if ! PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d postgres \
|
||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$(DB_NAME)' AND pid <> pg_backend_pid();"; then \
|
||||
echo "⚠️ 无法断开所有连接(需要超级用户权限)"; \
|
||||
fi; \
|
||||
echo ">>> 清理 pglogical schema ..."; \
|
||||
PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-c "DROP SCHEMA IF EXISTS pglogical CASCADE;" >/dev/null 2>&1 || \
|
||||
echo "⚠️ 无法删除 pglogical schema(数据库可能不存在或缺少权限)"; \
|
||||
echo ">>> 删除数据库 $(DB_NAME) ..."; \
|
||||
if PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d postgres \
|
||||
-c "DROP DATABASE IF EXISTS $(DB_NAME);"; then \
|
||||
echo ">>> 数据库已删除"; \
|
||||
else \
|
||||
echo ">>> 删除失败"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "取消删除"; \
|
||||
fi
|
||||
@bash scripts/drop-db.sh
|
||||
|
||||
reset-public-schema:
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -v db_user="$(DB_USER)" -f sql/reset_public_schema.sql
|
||||
@bash scripts/reset-public-schema.sh
|
||||
|
||||
reinit-db:
|
||||
@echo ">>> 重置业务 schema (sql/schema.sql)"
|
||||
@$(MAKE) reset-public-schema
|
||||
@$(MAKE) init-db-core
|
||||
@bash scripts/reinit-db.sh
|
||||
|
||||
reinit-pglogical:
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
echo ">>> 重新初始化 pglogical schema"; \
|
||||
$(MAKE) init-db-pglogical; \
|
||||
else \
|
||||
echo ">>> 当前 REPLICATION_MODE=$(REPLICATION_MODE),无需 pglogical 处理"; \
|
||||
fi
|
||||
@bash scripts/reinit-pglogical.sh
|
||||
|
||||
stunnel-start:
|
||||
@bash scripts/stunnel-start.sh
|
||||
|
||||
# =========================================
|
||||
# 💾 账号导入导出
|
||||
# =========================================
|
||||
|
||||
account-export:
|
||||
@go run ./cmd/migratectl/main.go export --dsn "$(DB_URL)" --output "$(ACCOUNT_EXPORT_FILE)" $(if $(ACCOUNT_EMAIL_KEYWORD),--email "$(ACCOUNT_EMAIL_KEYWORD)")
|
||||
@bash scripts/account-export.sh
|
||||
|
||||
account-import:
|
||||
@[ -f "$(ACCOUNT_IMPORT_FILE)" ] || (echo "❌ 未找到文件 $(ACCOUNT_IMPORT_FILE)"; exit 1)
|
||||
@go run ./cmd/migratectl/main.go import --dsn "$(DB_URL)" --file "$(ACCOUNT_IMPORT_FILE)" \
|
||||
$(if $(ACCOUNT_IMPORT_MERGE),--merge) \
|
||||
$(if $(ACCOUNT_IMPORT_MERGE_STRATEGY),--merge-strategy "$(ACCOUNT_IMPORT_MERGE_STRATEGY)") \
|
||||
$(if $(ACCOUNT_IMPORT_DRY_RUN),--dry-run) \
|
||||
$(foreach UUID,$(ACCOUNT_IMPORT_MERGE_ALLOWLIST),--merge-allowlist $(UUID)) \
|
||||
$(ACCOUNT_IMPORT_EXTRA_FLAGS)
|
||||
@bash scripts/account-import.sh
|
||||
|
||||
account-sync-push:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go push --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
@bash scripts/account-sync-push.sh
|
||||
|
||||
account-sync-pull:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go pull --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
@bash scripts/account-sync-pull.sh
|
||||
|
||||
account-sync-mirror:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go mirror --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
@bash scripts/account-sync-mirror.sh
|
||||
|
||||
create-super-admin:
|
||||
@[ -n "$(SUPERADMIN_USERNAME)" ] && [ -n "$(SUPERADMIN_PASSWORD)" ] || (echo "❌ 请指定用户名与密码"; exit 1)
|
||||
@go run ./cmd/createadmin/main.go \
|
||||
--driver postgres \
|
||||
--dsn "$(DB_URL)" \
|
||||
--username "$(SUPERADMIN_USERNAME)" \
|
||||
--password "$(SUPERADMIN_PASSWORD)" \
|
||||
--email "$(SUPERADMIN_EMAIL)"
|
||||
@bash scripts/create-super-admin.sh
|
||||
|
||||
integration-test:
|
||||
@bash integration-test/superadmin-login/run-test-scripts.sh
|
||||
|
||||
# =========================================
|
||||
# ⚙️ 编译与运行
|
||||
# =========================================
|
||||
|
||||
build: init-go
|
||||
@go build -o $(APP_NAME) $(MAIN_FILE)
|
||||
@bash scripts/build.sh
|
||||
|
||||
upgrade: build
|
||||
systemctl stop xcontrol-account
|
||||
cp xcontrol-account /usr/bin/xcontrol-account
|
||||
systemctl start xcontrol-account
|
||||
@bash scripts/upgrade.sh
|
||||
|
||||
start: build
|
||||
@./$(APP_NAME) --config config/account.yaml
|
||||
@bash scripts/start.sh
|
||||
|
||||
stop:
|
||||
@pkill -f "$(APP_NAME)" || echo "⚠️ 未找到运行进程"
|
||||
@bash scripts/stop.sh
|
||||
|
||||
restart: stop start
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
@bash scripts/test.sh
|
||||
|
||||
clean:
|
||||
rm -f $(APP_NAME) *.pid *.log
|
||||
@bash scripts/clean.sh
|
||||
|
||||
dev:
|
||||
@bash scripts/dev.sh
|
||||
|
||||
# =========================================
|
||||
# ☁️ GCP Cloud Run
|
||||
# =========================================
|
||||
|
||||
cloudrun-build:
|
||||
@if [ -z "$(GCP_PROJECT)" ]; then \
|
||||
echo "❌ GCP_PROJECT 不能为空"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@gcloud builds submit --tag "$(CLOUD_RUN_IMAGE)" .
|
||||
@bash scripts/cloudrun-build.sh
|
||||
|
||||
cloudrun-deploy:
|
||||
@if [ -z "$(GCP_PROJECT)" ]; then \
|
||||
echo "❌ GCP_PROJECT 不能为空"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@gcloud run services replace "$(CLOUD_RUN_SERVICE_YAML)" --region "$(GCP_REGION)" --project "$(GCP_PROJECT)"
|
||||
@bash scripts/cloudrun-deploy.sh
|
||||
|
||||
cloudrun-stunnel:
|
||||
@if [ -z "$(GCP_PROJECT)" ]; then \
|
||||
echo "❌ GCP_PROJECT 不能为空"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f "$(CLOUD_RUN_STUNNEL_CONF)" ]; then \
|
||||
echo "❌ 未找到 stunnel 配置: $(CLOUD_RUN_STUNNEL_CONF)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@gcloud secrets versions add stunnel-config --data-file "$(CLOUD_RUN_STUNNEL_CONF)" --project "$(GCP_PROJECT)"
|
||||
@bash scripts/cloudrun-stunnel.sh
|
||||
|
||||
@ -31,7 +31,8 @@ server:
|
||||
|
||||
store:
|
||||
driver: "postgres"
|
||||
dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable"
|
||||
# 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD
|
||||
dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable"
|
||||
maxOpenConns: 30
|
||||
maxIdleConns: 10
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ server:
|
||||
- "https://www.svc.plus"
|
||||
- "https://global-homepage.svc.plus"
|
||||
- "https://accounts.svc.plus"
|
||||
- "https://console.svc.plus"
|
||||
- "https://localhost:8443"
|
||||
- "http://localhost:8080"
|
||||
- "http://127.0.0.1:8080"
|
||||
@ -41,7 +42,8 @@ server:
|
||||
|
||||
store:
|
||||
driver: "postgres"
|
||||
dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable"
|
||||
# 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD
|
||||
dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable"
|
||||
maxOpenConns: 30
|
||||
maxIdleConns: 10
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
|
||||
local:
|
||||
# 本地 PostgreSQL 连接地址,用于导入/导出账号数据
|
||||
dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable"
|
||||
# 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD
|
||||
dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable"
|
||||
# 可选:按 email 关键字过滤导出的账号
|
||||
email_keyword: ""
|
||||
# 导出的快照文件路径(默认 account-export.yaml)
|
||||
@ -47,4 +48,3 @@ remote:
|
||||
|
||||
# SSH 连接超时时间
|
||||
timeout: 30s
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
|
||||
local:
|
||||
# 本地 PostgreSQL 连接地址,用于导入/导出账号数据
|
||||
dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable"
|
||||
# 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD
|
||||
dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable"
|
||||
# 可选:按 email 关键字过滤导出的账号
|
||||
email_keyword: ""
|
||||
# 导出的快照文件路径(默认 account-export.yaml)
|
||||
@ -47,4 +48,3 @@ remote:
|
||||
|
||||
# SSH 连接超时时间
|
||||
timeout: 30s
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ spec:
|
||||
containers:
|
||||
# --- 主应用容器 ---
|
||||
- name: accounts-api
|
||||
image: asia-northeast1-docker.pkg.dev/xzerolab-480008/cloud-run-source-deploy/accounts.svc.plus/accounts-svc-plus:d5b6edcaa76150f2489bbdb2a46a41bc98baa87f
|
||||
image: asia-northeast1-docker.pkg.dev/xzerolab-480008/cloud-run-source-deploy/accounts.svc.plus/accounts-svc-plus:latest
|
||||
ports:
|
||||
- name: http1
|
||||
containerPort: 8080
|
||||
@ -48,7 +48,7 @@ spec:
|
||||
- name: POSTGRES_USER
|
||||
value: postgres
|
||||
- name: DB_NAME
|
||||
value: postgres
|
||||
value: account
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
|
||||
66
integration-test/superadmin-login/README.md
Normal file
66
integration-test/superadmin-login/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# superadmin-login
|
||||
|
||||
## 说明
|
||||
|
||||
该用例用于本地集成测试,按顺序执行:
|
||||
|
||||
1. `make init-db`
|
||||
2. `make create-db-user`
|
||||
3. `make create-super-admin`
|
||||
|
||||
脚本会自动读取项目根目录 `.env` 中的环境变量(如 `POSTGRES_USER` / `POSTGRES_PASSWORD`),用于联动数据库配置。
|
||||
若未设置 `SUPERADMIN_PASSWORD`,脚本会生成随机密码并写回 `.env`,便于后续登录测试复用。
|
||||
|
||||
## 运行方式
|
||||
|
||||
```bash
|
||||
make integration-test
|
||||
```
|
||||
|
||||
或直接运行脚本:
|
||||
|
||||
```bash
|
||||
bash integration-test/superadmin-login/run-test-scripts.sh
|
||||
```
|
||||
|
||||
## API 自动化测试
|
||||
|
||||
```bash
|
||||
bash integration-test/superadmin-login/api-test.sh
|
||||
```
|
||||
|
||||
可选环境变量:
|
||||
|
||||
- `API_BASE_URL`:API 入口地址(默认 `https://accounts.svc.plus`)
|
||||
- `LOGIN_EMAIL`:登录邮箱(默认 `admin@svc.plus`)
|
||||
- `SUPERADMIN_PASSWORD`:登录密码(从 `.env` 读取)
|
||||
|
||||
## UI 自动化测试(Playwright)
|
||||
|
||||
```bash
|
||||
bash integration-test/superadmin-login/ui-test.sh
|
||||
```
|
||||
|
||||
可选环境变量:
|
||||
|
||||
- `UI_BASE_URL`:UI 入口地址(默认 `https://console.svc.plus`)
|
||||
- `LOGIN_EMAIL`:登录邮箱(默认 `admin@svc.plus`)
|
||||
- `SUPERADMIN_PASSWORD`:登录密码(从 `.env` 读取)
|
||||
|
||||
## 预期输出(示例)
|
||||
|
||||
```
|
||||
✅ 已读取 .env
|
||||
>>> init-db
|
||||
...(略)
|
||||
>>> create-super-admin
|
||||
...(略)
|
||||
✅ 集成测试步骤完成(DB 初始化 + 用户创建 + 超级管理员创建)
|
||||
|
||||
接下来手动登录验证:
|
||||
- 网址:https://console.svc.plus/login
|
||||
- 邮箱:admin@svc.plus
|
||||
- 密码:<generated-or-provided>
|
||||
```
|
||||
|
||||
> 注意:登录验证需要手动在浏览器完成。
|
||||
80
integration-test/superadmin-login/api-test.sh
Executable file
80
integration-test/superadmin-login/api-test.sh
Executable file
@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
set +a
|
||||
echo "✅ 已读取 .env"
|
||||
fi
|
||||
|
||||
if ! command -v curl >/dev/null; then
|
||||
echo "❌ 未找到 curl,请先安装或确保在 PATH 中" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API_BASE_URL="${API_BASE_URL:-${BASE_URL:-https://accounts.svc.plus}}"
|
||||
LOGIN_EMAIL="${LOGIN_EMAIL:-admin@svc.plus}"
|
||||
LOGIN_PASSWORD="${SUPERADMIN_PASSWORD:-}"
|
||||
|
||||
if [ -z "$LOGIN_PASSWORD" ]; then
|
||||
echo "❌ 缺少 SUPERADMIN_PASSWORD(可写入 .env)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
login_payload=$(cat <<JSON
|
||||
{"email":"${LOGIN_EMAIL}","identifier":"${LOGIN_EMAIL}","password":"${LOGIN_PASSWORD}"}
|
||||
JSON
|
||||
)
|
||||
|
||||
login_response=$(curl -sS -w "\n%{http_code}" -X POST "${API_BASE_URL}/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$login_payload")
|
||||
|
||||
login_body=$(printf "%s" "$login_response" | sed '$d')
|
||||
login_status=$(printf "%s" "$login_response" | tail -n1)
|
||||
|
||||
if [ "$login_status" != "200" ]; then
|
||||
echo "❌ 登录失败: HTTP ${login_status}" >&2
|
||||
echo "$login_body" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v python3 >/dev/null; then
|
||||
token=$(printf "%s" "$login_body" | python3 - <<'PY'
|
||||
import json, sys
|
||||
try:
|
||||
payload = json.load(sys.stdin)
|
||||
print(payload.get('token',''))
|
||||
except Exception:
|
||||
print('')
|
||||
PY
|
||||
)
|
||||
else
|
||||
echo "❌ 未找到 python3,无法解析登录 token" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
echo "❌ 登录响应中未包含 token" >&2
|
||||
echo "$login_body" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
session_response=$(curl -sS -w "\n%{http_code}" -X GET "${API_BASE_URL}/api/auth/session" \
|
||||
-H "Authorization: Bearer ${token}")
|
||||
|
||||
session_body=$(printf "%s" "$session_response" | sed '$d')
|
||||
session_status=$(printf "%s" "$session_response" | tail -n1)
|
||||
|
||||
if [ "$session_status" != "200" ]; then
|
||||
echo "❌ session 校验失败: HTTP ${session_status}" >&2
|
||||
echo "$session_body" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ API 登录测试通过"
|
||||
74
integration-test/superadmin-login/run-test-scripts.sh
Executable file
74
integration-test/superadmin-login/run-test-scripts.sh
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
set +a
|
||||
echo "✅ 已读取 .env"
|
||||
fi
|
||||
|
||||
ensure_superadmin_password() {
|
||||
if [ -n "${SUPERADMIN_PASSWORD:-}" ] && [ "${SUPERADMIN_PASSWORD}" != "ChangeMe" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v openssl >/dev/null; then
|
||||
SUPERADMIN_PASSWORD="$(openssl rand -base64 12 | tr -d '\n' | tr '/+' 'Aa' | cut -c1-10)"
|
||||
else
|
||||
SUPERADMIN_PASSWORD="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 10)"
|
||||
fi
|
||||
|
||||
if [ -f .env ]; then
|
||||
if grep -q '^SUPERADMIN_PASSWORD=' .env; then
|
||||
tmpfile="$(mktemp)"
|
||||
sed "s/^SUPERADMIN_PASSWORD=.*/SUPERADMIN_PASSWORD=${SUPERADMIN_PASSWORD}/" .env > "$tmpfile"
|
||||
mv "$tmpfile" .env
|
||||
else
|
||||
printf "\nSUPERADMIN_PASSWORD=%s\n" "$SUPERADMIN_PASSWORD" >> .env
|
||||
fi
|
||||
else
|
||||
printf "SUPERADMIN_PASSWORD=%s\n" "$SUPERADMIN_PASSWORD" > .env
|
||||
fi
|
||||
echo "✅ 已写入 .env 的 SUPERADMIN_PASSWORD"
|
||||
}
|
||||
|
||||
if ! command -v make >/dev/null; then
|
||||
echo "❌ 未找到 make,请先安装或确保在 PATH 中" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">>> init-db"
|
||||
make init-db
|
||||
|
||||
echo ">>> create-db-user"
|
||||
make create-db-user
|
||||
|
||||
echo ">>> create-super-admin"
|
||||
ensure_superadmin_password
|
||||
export SUPERADMIN_PASSWORD
|
||||
create_output="$(make create-super-admin 2>&1 | tee /dev/stderr)"
|
||||
|
||||
superadmin_password=""
|
||||
if echo "$create_output" | grep -q "已生成随机初始密码"; then
|
||||
superadmin_password="$(echo "$create_output" | sed -n 's/.*已生成随机初始密码: \([A-Za-z0-9]\+\).*/\1/p' | tail -n1)"
|
||||
fi
|
||||
if [ -z "$superadmin_password" ]; then
|
||||
superadmin_password="$(echo "$create_output" | sed -n 's/.*密码:\s*//p' | tail -n1)"
|
||||
fi
|
||||
|
||||
cat <<EOM
|
||||
|
||||
✅ 集成测试步骤完成(DB 初始化 + 用户创建 + 超级管理员创建)
|
||||
|
||||
接下来手动登录验证:
|
||||
- 网址:https://console.svc.plus/login
|
||||
- 邮箱:admin@svc.plus
|
||||
- 密码:${superadmin_password:-请查看上方 create-super-admin 输出}
|
||||
|
||||
提示:如果页面无法访问,请先确保 account 服务已启动并对外可用。
|
||||
EOM
|
||||
101
integration-test/superadmin-login/ui-test.sh
Executable file
101
integration-test/superadmin-login/ui-test.sh
Executable file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
set +a
|
||||
echo "✅ 已读取 .env"
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null; then
|
||||
echo "❌ 未找到 node,请先安装" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npx >/dev/null; then
|
||||
echo "❌ 未找到 npx,请先安装" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UI_BASE_URL="${UI_BASE_URL:-${BASE_URL:-https://console.svc.plus}}"
|
||||
LOGIN_EMAIL="${LOGIN_EMAIL:-admin@svc.plus}"
|
||||
LOGIN_PASSWORD="${SUPERADMIN_PASSWORD:-}"
|
||||
|
||||
if [ -z "$LOGIN_PASSWORD" ]; then
|
||||
echo "❌ 缺少 SUPERADMIN_PASSWORD(可写入 .env)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DIR="${TMPDIR:-/tmp}"
|
||||
PLAYWRIGHT_TEST_DIR="${TMP_DIR}/xcontrol-playwright-login"
|
||||
mkdir -p "$PLAYWRIGHT_TEST_DIR"
|
||||
|
||||
cat <<'TEST' > "$PLAYWRIGHT_TEST_DIR/login.spec.mjs"
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.use({ screenshot: 'only-on-failure' });
|
||||
|
||||
test('superadmin login', async ({ page }) => {
|
||||
const baseUrl = process.env.UI_BASE_URL || process.env.BASE_URL || 'https://console.svc.plus';
|
||||
const email = process.env.LOGIN_EMAIL || 'admin@svc.plus';
|
||||
const password = process.env.SUPERADMIN_PASSWORD;
|
||||
|
||||
if (!password) {
|
||||
throw new Error('missing SUPERADMIN_PASSWORD');
|
||||
}
|
||||
|
||||
await page.goto(`${baseUrl}/login`, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const emailByRole = page.getByRole('textbox', { name: /email|邮箱|账号|用户名|identifier/i });
|
||||
const emailBySelector = page.locator(
|
||||
'input[type="email"], input[name="email"], input[name="identifier"], input[placeholder*="邮箱"], input[placeholder*="Email"], input[type="text"]'
|
||||
);
|
||||
const emailInput = (await emailByRole.count()) > 0 ? emailByRole.first() : emailBySelector.first();
|
||||
await expect(emailInput).toBeVisible({ timeout: 15000 });
|
||||
await emailInput.fill(email);
|
||||
|
||||
const passwordInput = page.locator(
|
||||
'input[type="password"], input[name="password"], input[placeholder*="密码"], input[placeholder*="Password"]'
|
||||
);
|
||||
await expect(passwordInput).toBeVisible({ timeout: 15000 });
|
||||
await passwordInput.fill(password);
|
||||
|
||||
const submitBtn = page.locator(
|
||||
'button[type="submit"], button:has-text("登录"), button:has-text("Log in"), button:has-text("Sign in")'
|
||||
);
|
||||
await expect(submitBtn).toBeVisible({ timeout: 15000 });
|
||||
await submitBtn.click();
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
await expect(page).not.toHaveURL(/login/);
|
||||
});
|
||||
TEST
|
||||
|
||||
cd "$PLAYWRIGHT_TEST_DIR"
|
||||
|
||||
if [ ! -f package.json ]; then
|
||||
cat <<'PKG' > package.json
|
||||
{
|
||||
"name": "xcontrol-playwright-login",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.49.0"
|
||||
}
|
||||
}
|
||||
PKG
|
||||
fi
|
||||
|
||||
npm install --silent
|
||||
npx playwright install chromium
|
||||
|
||||
UI_BASE_URL="$UI_BASE_URL" BASE_URL="$UI_BASE_URL" LOGIN_EMAIL="$LOGIN_EMAIL" SUPERADMIN_PASSWORD="$LOGIN_PASSWORD" \
|
||||
npx playwright test login.spec.mjs
|
||||
|
||||
echo "✅ UI 登录测试通过"
|
||||
6
scripts/_common.sh
Executable file
6
scripts/_common.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
11
scripts/account-export.sh
Executable file
11
scripts/account-export.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
args=(export --dsn "${DB_URL}" --output "${ACCOUNT_EXPORT_FILE}")
|
||||
if [ -n "${ACCOUNT_EMAIL_KEYWORD-}" ]; then
|
||||
args+=(--email "${ACCOUNT_EMAIL_KEYWORD}")
|
||||
fi
|
||||
|
||||
go run ./cmd/migratectl/main.go "${args[@]}"
|
||||
32
scripts/account-import.sh
Executable file
32
scripts/account-import.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ ! -f "${ACCOUNT_IMPORT_FILE}" ]; then
|
||||
echo "❌ 未找到文件 ${ACCOUNT_IMPORT_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
args=(import --dsn "${DB_URL}" --file "${ACCOUNT_IMPORT_FILE}")
|
||||
if [ -n "${ACCOUNT_IMPORT_MERGE-}" ]; then
|
||||
args+=(--merge)
|
||||
fi
|
||||
if [ -n "${ACCOUNT_IMPORT_MERGE_STRATEGY-}" ]; then
|
||||
args+=(--merge-strategy "${ACCOUNT_IMPORT_MERGE_STRATEGY}")
|
||||
fi
|
||||
if [ -n "${ACCOUNT_IMPORT_DRY_RUN-}" ]; then
|
||||
args+=(--dry-run)
|
||||
fi
|
||||
if [ -n "${ACCOUNT_IMPORT_MERGE_ALLOWLIST-}" ]; then
|
||||
for uuid in ${ACCOUNT_IMPORT_MERGE_ALLOWLIST}; do
|
||||
args+=(--merge-allowlist "${uuid}")
|
||||
done
|
||||
fi
|
||||
if [ -n "${ACCOUNT_IMPORT_EXTRA_FLAGS-}" ]; then
|
||||
# shellcheck disable=SC2206
|
||||
extra=( ${ACCOUNT_IMPORT_EXTRA_FLAGS} )
|
||||
args+=("${extra[@]}")
|
||||
fi
|
||||
|
||||
go run ./cmd/migratectl/main.go "${args[@]}"
|
||||
11
scripts/account-sync-mirror.sh
Executable file
11
scripts/account-sync-mirror.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ ! -f "${ACCOUNT_SYNC_CONFIG}" ]; then
|
||||
echo "❌ 未找到配置文件 ${ACCOUNT_SYNC_CONFIG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go run ./cmd/syncctl/main.go mirror --config "${ACCOUNT_SYNC_CONFIG}"
|
||||
11
scripts/account-sync-pull.sh
Executable file
11
scripts/account-sync-pull.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ ! -f "${ACCOUNT_SYNC_CONFIG}" ]; then
|
||||
echo "❌ 未找到配置文件 ${ACCOUNT_SYNC_CONFIG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go run ./cmd/syncctl/main.go pull --config "${ACCOUNT_SYNC_CONFIG}"
|
||||
11
scripts/account-sync-push.sh
Executable file
11
scripts/account-sync-push.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ ! -f "${ACCOUNT_SYNC_CONFIG}" ]; then
|
||||
echo "❌ 未找到配置文件 ${ACCOUNT_SYNC_CONFIG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go run ./cmd/syncctl/main.go push --config "${ACCOUNT_SYNC_CONFIG}"
|
||||
6
scripts/build.sh
Executable file
6
scripts/build.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
go build -o "${APP_NAME}" "${MAIN_FILE}"
|
||||
6
scripts/clean.sh
Executable file
6
scripts/clean.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
rm -f "${APP_NAME}" *.pid *.log
|
||||
11
scripts/cloudrun-build.sh
Executable file
11
scripts/cloudrun-build.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ -z "${GCP_PROJECT}" ]; then
|
||||
echo "⚠️ GCP_PROJECT 不能为空,跳过 Cloud Run 构建"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gcloud builds submit --tag "${CLOUD_RUN_IMAGE}" .
|
||||
11
scripts/cloudrun-deploy.sh
Executable file
11
scripts/cloudrun-deploy.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ -z "${GCP_PROJECT}" ]; then
|
||||
echo "⚠️ GCP_PROJECT 不能为空,跳过 Cloud Run 部署"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gcloud run services replace "${CLOUD_RUN_SERVICE_YAML}" --region "${GCP_REGION}" --project "${GCP_PROJECT}"
|
||||
15
scripts/cloudrun-stunnel.sh
Executable file
15
scripts/cloudrun-stunnel.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ -z "${GCP_PROJECT}" ]; then
|
||||
echo "⚠️ GCP_PROJECT 不能为空,跳过 stunnel secret 更新"
|
||||
exit 0
|
||||
fi
|
||||
if [ ! -f "${CLOUD_RUN_STUNNEL_CONF}" ]; then
|
||||
echo "❌ 未找到 stunnel 配置: ${CLOUD_RUN_STUNNEL_CONF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gcloud secrets versions add stunnel-config --data-file "${CLOUD_RUN_STUNNEL_CONF}" --project "${GCP_PROJECT}"
|
||||
22
scripts/create-db-user.sh
Executable file
22
scripts/create-db-user.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo ">>> 创建数据库用户 ${DB_USER}"
|
||||
if ! command -v psql >/dev/null; then
|
||||
echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "正在以管理员身份创建用户..."
|
||||
if PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-Atc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" | grep -qx '1'; then
|
||||
echo "⚠️ 用户可能已存在"
|
||||
else
|
||||
PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';"
|
||||
fi
|
||||
PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
|
||||
echo "✓ 数据库用户创建完成"
|
||||
47
scripts/create-super-admin.sh
Executable file
47
scripts/create-super-admin.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
SUPERADMIN_EMAIL="admin@svc.plus"
|
||||
SUPERADMIN_USERNAME="${SUPERADMIN_USERNAME:-Admin}"
|
||||
|
||||
if [ -z "${SUPERADMIN_PASSWORD:-}" ] || [ "${SUPERADMIN_PASSWORD}" = "ChangeMe" ]; then
|
||||
if command -v openssl >/dev/null; then
|
||||
SUPERADMIN_PASSWORD="$(openssl rand -base64 12 | tr -d '\n' | tr '/+' 'Aa' | cut -c1-10)"
|
||||
else
|
||||
SUPERADMIN_PASSWORD="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 10)"
|
||||
fi
|
||||
echo "⚠️ 未指定 SUPERADMIN_PASSWORD,已生成随机初始密码: ${SUPERADMIN_PASSWORD}"
|
||||
fi
|
||||
|
||||
if psql "${DB_URL}" -Atc "SELECT 1 FROM users WHERE username='${SUPERADMIN_USERNAME}' OR email='${SUPERADMIN_EMAIL}' LIMIT 1" | grep -qx '1'; then
|
||||
if [ -z "${SUPERADMIN_CURRENT_PASSWORD:-}" ]; then
|
||||
echo "⚠️ 超级管理员已存在,未提供 SUPERADMIN_CURRENT_PASSWORD,跳过更新"
|
||||
exit 0
|
||||
fi
|
||||
go run ./cmd/createadmin/main.go \
|
||||
--driver postgres \
|
||||
--dsn "${DB_URL}" \
|
||||
--username "${SUPERADMIN_USERNAME}" \
|
||||
--password "${SUPERADMIN_PASSWORD}" \
|
||||
--email "${SUPERADMIN_EMAIL}" \
|
||||
--current-password "${SUPERADMIN_CURRENT_PASSWORD}"
|
||||
echo "✅ 登录信息(仅本次输出)"
|
||||
echo " 网址:https://console.svc.plus/login"
|
||||
echo " 邮箱:admin@svc.plus"
|
||||
echo " 密码:${SUPERADMIN_PASSWORD}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
go run ./cmd/createadmin/main.go \
|
||||
--driver postgres \
|
||||
--dsn "${DB_URL}" \
|
||||
--username "${SUPERADMIN_USERNAME}" \
|
||||
--password "${SUPERADMIN_PASSWORD}" \
|
||||
--email "${SUPERADMIN_EMAIL}"
|
||||
|
||||
echo "✅ 登录信息(仅本次输出)"
|
||||
echo " 网址:https://console.svc.plus/login"
|
||||
echo " 邮箱:admin@svc.plus"
|
||||
echo " 密码:${SUPERADMIN_PASSWORD}"
|
||||
26
scripts/db-reset.sh
Executable file
26
scripts/db-reset.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo "⚠️ 即将重置整个 PostgreSQL 数据库集群 ..."
|
||||
if [ "${OS}" = "Darwin" ]; then
|
||||
echo "⚠️ macOS 不支持 pg_dropcluster/systemctl,跳过重置"
|
||||
exit 0
|
||||
fi
|
||||
read -r -p "确定要重置数据库集群? 这将删除所有数据! [y/N] " confirm
|
||||
if [ "${confirm}" = "y" ] || [ "${confirm}" = "Y" ]; then
|
||||
echo ">>> 停止 PostgreSQL 服务 ..."
|
||||
sudo systemctl stop postgresql
|
||||
echo ">>> 删除数据库集群 16 main ..."
|
||||
sudo pg_dropcluster --stop 16 main
|
||||
echo ">>> 清理数据目录 ..."
|
||||
sudo rm -rf /var/lib/postgresql/16/main
|
||||
echo ">>> 清理配置目录 ..."
|
||||
sudo rm -rf /etc/postgresql/16/main
|
||||
echo ">>> 创建新的数据库集群 ..."
|
||||
sudo pg_createcluster 16 main --start
|
||||
echo "✓ PostgreSQL 集群重置完成"
|
||||
else
|
||||
echo "取消重置"
|
||||
fi
|
||||
12
scripts/dev.sh
Executable file
12
scripts/dev.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if command -v air >/dev/null; then
|
||||
air
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "❌ 未检测到 air (热重载工具),请先安装: https://github.com/cosmtrek/air"
|
||||
exit 0
|
||||
27
scripts/drop-db.sh
Executable file
27
scripts/drop-db.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo "⚠️ 即将删除数据库 ${DB_NAME} ..."
|
||||
read -r -p "确定要删除数据库 ${DB_NAME}? [y/N] " confirm
|
||||
if [ "${confirm}" = "y" ] || [ "${confirm}" = "Y" ]; then
|
||||
echo ">>> 强制断开现有连接 ..."
|
||||
if ! PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='${DB_NAME}' AND pid <> pg_backend_pid();"; then
|
||||
echo "⚠️ 无法断开所有连接(需要超级用户权限)"
|
||||
fi
|
||||
echo ">>> 清理 pglogical schema ..."
|
||||
PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -U "${DB_ADMIN_USER}" -d "${DB_NAME}" \
|
||||
-c "DROP SCHEMA IF EXISTS pglogical CASCADE;" >/dev/null 2>&1 || \
|
||||
echo "⚠️ 无法删除 pglogical schema(数据库可能不存在或缺少权限)"
|
||||
echo ">>> 删除数据库 ${DB_NAME} ..."
|
||||
if PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-c "DROP DATABASE IF EXISTS ${DB_NAME};"; then
|
||||
echo ">>> 数据库已删除"
|
||||
else
|
||||
echo ">>> 删除失败"
|
||||
fi
|
||||
else
|
||||
echo "取消删除"
|
||||
fi
|
||||
26
scripts/dump-schema.sh
Executable file
26
scripts/dump-schema.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo ">>> 导出 schema 到 ${SCHEMA_FILE}"
|
||||
PG_DUMP_BIN="${PG_DUMP_BIN:-pg_dump}"
|
||||
SERVER_VERSION="$(psql "${DB_URL}" -Atc "SHOW server_version" 2>/dev/null || true)"
|
||||
SERVER_MAJOR="${SERVER_VERSION%%.*}"
|
||||
LOCAL_VERSION="$(${PG_DUMP_BIN} --version 2>/dev/null || true)"
|
||||
LOCAL_MAJOR="$(echo "${LOCAL_VERSION}" | awk '{print $3}' | cut -d. -f1)"
|
||||
|
||||
if [ -n "${SERVER_MAJOR}" ] && [ -n "${LOCAL_MAJOR}" ] && [ "${SERVER_MAJOR}" != "${LOCAL_MAJOR}" ]; then
|
||||
echo "⚠️ pg_dump 版本不匹配(server=${SERVER_MAJOR}, local=${LOCAL_MAJOR}),跳过导出"
|
||||
echo " 可设置 PG_DUMP_BIN 指向匹配版本的 pg_dump"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TMP_SCHEMA="/tmp/schema.$$.sql"
|
||||
${PG_DUMP_BIN} -s -O -x "${DB_URL}" > "${TMP_SCHEMA}"
|
||||
if [ ! -w "${SCHEMA_FILE}" ]; then
|
||||
echo "⚠️ ${SCHEMA_FILE} 不可写,已将导出结果保留在 ${TMP_SCHEMA}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mv "${TMP_SCHEMA}" "${SCHEMA_FILE}"
|
||||
14
scripts/ensure-db.sh
Executable file
14
scripts/ensure-db.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-Atc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -qx '1'; then
|
||||
echo ">>> 数据库 ${DB_NAME} 已存在"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ">>> 创建数据库 ${DB_NAME}"
|
||||
PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_ADMIN_USER}" -d postgres \
|
||||
-c "CREATE DATABASE ${DB_NAME};"
|
||||
7
scripts/init-db-core.sh
Executable file
7
scripts/init-db-core.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo ">>> 初始化业务 schema (${SCHEMA_FILE})"
|
||||
psql "${DB_URL}" -v ON_ERROR_STOP=1 -f "${SCHEMA_FILE}"
|
||||
22
scripts/init-db-pglogical.sh
Executable file
22
scripts/init-db-pglogical.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ -f "${PGLOGICAL_INIT_FILE}" ]; then
|
||||
echo ">>> 初始化 pglogical schema (REPLICATION_MODE=pglogical)"
|
||||
if PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -U "${DB_ADMIN_USER}" -d "${DB_NAME}" \
|
||||
-Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" 2>/dev/null | grep -qx 't'; then
|
||||
PGPASSWORD="${DB_ADMIN_PASS}" psql -h "${DB_HOST}" -U "${DB_ADMIN_USER}" -d "${DB_NAME}" \
|
||||
-v ON_ERROR_STOP=1 -f "${PGLOGICAL_INIT_FILE}"
|
||||
elif psql "${DB_URL}" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then
|
||||
psql "${DB_URL}" -v ON_ERROR_STOP=1 -f "${PGLOGICAL_INIT_FILE}"
|
||||
else
|
||||
echo "⚠️ 当前用户非超级用户,跳过 pglogical 初始化"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "${PGLOGICAL_PATCH_FILE}" ]; then
|
||||
echo ">>> 应用 pglogical 默认值补丁"
|
||||
psql "${DB_URL}" -v ON_ERROR_STOP=1 -f "${PGLOGICAL_PATCH_FILE}"
|
||||
fi
|
||||
10
scripts/init-db-replication.sh
Executable file
10
scripts/init-db-replication.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ "${REPLICATION_MODE}" = "pglogical" ]; then
|
||||
bash scripts/init-db-pglogical.sh
|
||||
else
|
||||
echo ">>> 跳过 pglogical 初始化 (REPLICATION_MODE=${REPLICATION_MODE})"
|
||||
fi
|
||||
14
scripts/init-db.sh
Executable file
14
scripts/init-db.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo ">>> 初始化数据库 schema"
|
||||
if ! command -v psql >/dev/null; then
|
||||
echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash scripts/ensure-db.sh
|
||||
bash scripts/init-db-core.sh
|
||||
bash scripts/init-db-replication.sh
|
||||
28
scripts/init-go.sh
Executable file
28
scripts/init-go.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ ! -f go.mod ]; then
|
||||
echo ">>> go.mod not found, initializing module"
|
||||
go mod init account
|
||||
fi
|
||||
|
||||
go mod tidy
|
||||
|
||||
echo ">>> 检查 Go 环境"
|
||||
if ! command -v go >/dev/null; then
|
||||
echo "未安装 Go,自动安装中..."
|
||||
if [ "${OS:-}" = "Darwin" ]; then
|
||||
brew install go@1.24
|
||||
brew link --overwrite --force go@1.24
|
||||
else
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y golang
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ">>> 配置 Go Proxy"
|
||||
go env -w GOPROXY=https://proxy.golang.org,direct
|
||||
|
||||
go mod tidy
|
||||
11
scripts/init-pglogical-region-cn.sh
Executable file
11
scripts/init-pglogical-region-cn.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
REGION_DB_URL="${DB_URL}" \
|
||||
NODE_NAME="node_cn" \
|
||||
NODE_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
SUBSCRIPTION_NAME="sub_from_global" \
|
||||
PROVIDER_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
bash scripts/init-pglogical-region.sh
|
||||
11
scripts/init-pglogical-region-global.sh
Executable file
11
scripts/init-pglogical-region-global.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
REGION_DB_URL="${DB_URL}" \
|
||||
NODE_NAME="node_global" \
|
||||
NODE_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
SUBSCRIPTION_NAME="sub_from_cn" \
|
||||
PROVIDER_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
bash scripts/init-pglogical-region.sh
|
||||
17
scripts/init-pglogical-region.sh
Executable file
17
scripts/init-pglogical-region.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
: "${REGION_DB_URL:?❌ 缺少 REGION_DB_URL}"
|
||||
: "${NODE_NAME:?❌ 缺少 NODE_NAME}"
|
||||
: "${NODE_DSN:?❌ 缺少 NODE_DSN}"
|
||||
: "${SUBSCRIPTION_NAME:?❌ 缺少 SUBSCRIPTION_NAME}"
|
||||
: "${PROVIDER_DSN:?❌ 缺少 PROVIDER_DSN}"
|
||||
|
||||
psql "${REGION_DB_URL}" -v ON_ERROR_STOP=1 \
|
||||
-v NODE_NAME="${NODE_NAME}" \
|
||||
-v NODE_DSN="${NODE_DSN}" \
|
||||
-v SUBSCRIPTION_NAME="${SUBSCRIPTION_NAME}" \
|
||||
-v PROVIDER_DSN="${PROVIDER_DSN}" \
|
||||
-f "${PGLOGICAL_REGION_FILE}"
|
||||
7
scripts/init.sh
Executable file
7
scripts/init.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
bash scripts/init-go.sh
|
||||
bash scripts/init-db.sh
|
||||
12
scripts/migrate-db.sh
Executable file
12
scripts/migrate-db.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo ">>> 执行数据库迁移"
|
||||
if [ ! -d sql/migrations ]; then
|
||||
echo "⚠️ 未找到 sql/migrations,跳过迁移"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
go run ./cmd/migratectl/main.go migrate --dsn "${DB_URL}" --dir sql/migrations
|
||||
8
scripts/reinit-db.sh
Executable file
8
scripts/reinit-db.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
echo ">>> 重置业务 schema (sql/schema.sql)"
|
||||
bash scripts/reset-public-schema.sh
|
||||
bash scripts/init-db-core.sh
|
||||
11
scripts/reinit-pglogical.sh
Executable file
11
scripts/reinit-pglogical.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
if [ "${REPLICATION_MODE}" = "pglogical" ]; then
|
||||
echo ">>> 重新初始化 pglogical schema"
|
||||
bash scripts/init-db-pglogical.sh
|
||||
else
|
||||
echo ">>> 当前 REPLICATION_MODE=${REPLICATION_MODE},无需 pglogical 处理"
|
||||
fi
|
||||
6
scripts/reset-public-schema.sh
Executable file
6
scripts/reset-public-schema.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
psql "${DB_URL}" -v ON_ERROR_STOP=1 -v db_user="${DB_USER}" -f sql/reset_public_schema.sql
|
||||
7
scripts/restart.sh
Executable file
7
scripts/restart.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
bash scripts/stop.sh
|
||||
bash scripts/start.sh
|
||||
2
scripts/setup_postgres_local.sh
Normal file → Executable file
2
scripts/setup_postgres_local.sh
Normal file → Executable file
@ -13,7 +13,7 @@ set -euo pipefail
|
||||
# 配置参数(可通过环境变量覆盖)
|
||||
# -----------------------------------------------------------------------------
|
||||
DB_NAME="${DB_NAME:-account}"
|
||||
DB_USER="${DB_USER:-shenlan}"
|
||||
DB_USER="${DB_USER:-${POSTGRES_USER:-postgres}}"
|
||||
DB_PASS="${DB_PASS:-password}"
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
DB_HOST="${DB_HOST:-127.0.0.1}"
|
||||
|
||||
6
scripts/start.sh
Executable file
6
scripts/start.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
./"${APP_NAME}" --config config/account.yaml
|
||||
6
scripts/stop.sh
Executable file
6
scripts/stop.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
pkill -f "${APP_NAME}" || echo "⚠️ 未找到运行进程"
|
||||
49
scripts/stunnel-start.sh
Executable file
49
scripts/stunnel-start.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
CONF_FILE="${CLOUD_RUN_STUNNEL_CONF}"
|
||||
if [ ! -f "${CONF_FILE}" ]; then
|
||||
echo "❌ 未找到 stunnel 配置: ${CONF_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v stunnel >/dev/null; then
|
||||
echo "❌ 未检测到 stunnel,请先安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v ss >/dev/null; then
|
||||
if ss -ltn 2>/dev/null | grep -q ':15432'; then
|
||||
echo "✅ stunnel 已在 127.0.0.1:15432 监听"
|
||||
exit 0
|
||||
fi
|
||||
elif command -v lsof >/dev/null; then
|
||||
if lsof -nP -iTCP:15432 -sTCP:LISTEN | grep -q LISTEN; then
|
||||
echo "✅ stunnel 已在 127.0.0.1:15432 监听"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ">>> 启动 stunnel (client)"
|
||||
# stunnel 需要写入 /var/run,优先使用 sudo 启动
|
||||
if sudo -n true 2>/dev/null; then
|
||||
sudo stunnel "${CONF_FILE}" &
|
||||
echo "✅ stunnel 启动完成 (sudo)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "⚠️ sudo 不可用,使用用户态临时配置启动"
|
||||
TMP_CONF="/tmp/stunnel-account-db-client.conf"
|
||||
sed \
|
||||
-e 's#^pid = .*#pid = /tmp/stunnel-account-db-client.pid#' \
|
||||
-e 's#^output = .*#output = /tmp/stunnel-account-db-client.log#' \
|
||||
"${CONF_FILE}" > "${TMP_CONF}"
|
||||
|
||||
if [ ! -f /etc/ssl/certs/ca-certificates.crt ] && [ -f /etc/ssl/cert.pem ]; then
|
||||
sed -i '' 's#^CAfile = .*#CAfile = /etc/ssl/cert.pem#' "${TMP_CONF}"
|
||||
fi
|
||||
|
||||
stunnel "${TMP_CONF}" &
|
||||
echo "✅ stunnel 启动完成 (user mode)"
|
||||
6
scripts/test.sh
Executable file
6
scripts/test.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
go test ./...
|
||||
8
scripts/upgrade.sh
Executable file
8
scripts/upgrade.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh"
|
||||
|
||||
systemctl stop xcontrol-account
|
||||
cp xcontrol-account /usr/bin/xcontrol-account
|
||||
systemctl start xcontrol-account
|
||||
@ -67,9 +67,9 @@ go run ./cmd/migratectl/main.go check --cn "$CN_DSN" --global "$GLOBAL_DSN"
|
||||
make -C account init-pglogical-region \
|
||||
REGION_DB_URL="$REGION_DB_URL" \
|
||||
NODE_NAME=node_cn \
|
||||
NODE_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
|
||||
NODE_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}" \
|
||||
SUBSCRIPTION_NAME=sub_from_global \
|
||||
PROVIDER_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx"
|
||||
PROVIDER_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}"
|
||||
```
|
||||
|
||||
4. 在另一侧节点重复执行并互为订阅,实现双主写入。
|
||||
@ -85,21 +85,21 @@ pglogical schema 与业务 schema 分离,以防逻辑复制函数污染业务
|
||||
bash
|
||||
复制代码
|
||||
sudo -u postgres psql -d account -c "GRANT USAGE ON SCHEMA pglogical TO PUBLIC;"
|
||||
2️⃣ 授权业务用户(shenlan)
|
||||
2️⃣ 授权业务用户(app_user)
|
||||
sql
|
||||
复制代码
|
||||
-- 登录 postgres
|
||||
sudo -u postgres psql -d account
|
||||
|
||||
-- 授权 shenlan 对 public schema 全权限
|
||||
ALTER SCHEMA public OWNER TO shenlan;
|
||||
GRANT ALL ON SCHEMA public TO shenlan;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO shenlan;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO shenlan;
|
||||
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO shenlan;
|
||||
-- 授权 app_user 对 public schema 全权限
|
||||
ALTER SCHEMA public OWNER TO app_user;
|
||||
GRANT ALL ON SCHEMA public TO app_user;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_user;
|
||||
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO app_user;
|
||||
|
||||
-- 授权 pglogical schema 使用权限(仅使用,不可修改)
|
||||
GRANT USAGE ON SCHEMA pglogical TO shenlan;
|
||||
GRANT USAGE ON SCHEMA pglogical TO app_user;
|
||||
|
||||
\q
|
||||
⚙️ 执行顺序建议
|
||||
@ -123,17 +123,17 @@ GRANT USAGE ON SCHEMA pglogical TO shenlan;
|
||||
# Global 节点示例
|
||||
psql "$REGION_GLOBAL_DB_URL" -v ON_ERROR_STOP=1 \
|
||||
-v NODE_NAME=node_global \
|
||||
-v NODE_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx' \
|
||||
-v NODE_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}' \
|
||||
-v SUBSCRIPTION_NAME=sub_from_cn \
|
||||
-v PROVIDER_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx' \
|
||||
-v PROVIDER_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}' \
|
||||
-f account/sql/schema_pglogical_region.sql
|
||||
|
||||
# CN 节点示例
|
||||
psql "$REGION_CN_DB_URL" -v ON_ERROR_STOP=1 \
|
||||
-v NODE_NAME=node_cn \
|
||||
-v NODE_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx' \
|
||||
-v NODE_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}' \
|
||||
-v SUBSCRIPTION_NAME=sub_from_global \
|
||||
-v PROVIDER_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxx' \
|
||||
-v PROVIDER_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}' \
|
||||
-f account/sql/schema_pglogical_region.sql
|
||||
```
|
||||
|
||||
@ -143,12 +143,12 @@ psql "$REGION_CN_DB_URL" -v ON_ERROR_STOP=1 \
|
||||
make init-pglogical-region \
|
||||
REGION_DB_URL="$REGION_DB_URL" \
|
||||
NODE_NAME=node_example \
|
||||
NODE_DSN="host=example port=5432 dbname=account user=pglogical password=secret" \
|
||||
NODE_DSN="host=example port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}" \
|
||||
SUBSCRIPTION_NAME=sub_from_peer \
|
||||
PROVIDER_DSN="host=peer port=5432 dbname=account user=pglogical password=secret"
|
||||
PROVIDER_DSN="host=peer port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}"
|
||||
```
|
||||
|
||||
- 若使用业务账号(如 `shenlan`)执行初始化,PostgreSQL 会提示缺少超级用户权限并跳过 `pglogical` 初始化。
|
||||
- 若使用业务账号(如 `app_user`)执行初始化,PostgreSQL 会提示缺少超级用户权限并跳过 `pglogical` 初始化。
|
||||
- 建议改用 `postgres` 等超级用户连接执行,或由管理员预先安装 `pglogical` 扩展并授予业务用户访问权限。
|
||||
- 如果扩展已由管理员创建,可直接重新运行 `make init-pglogical-region-cn` 完成复制集与订阅配置。
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user