feat: move account service to repo root
# Conflicts: # account/Makefile # account/go.mod # docs/account-admin-settings.md # docs/account-svc-plus.md
This commit is contained in:
parent
232da26361
commit
89bd31458f
29
Dockerfile.accounts-api
Normal file
29
Dockerfile.accounts-api
Normal file
@ -0,0 +1,29 @@
|
||||
# ------------------------------
|
||||
# Stage 1 — Build
|
||||
# ------------------------------
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -o accounts-api ./cmd/accountsapi
|
||||
|
||||
# ------------------------------
|
||||
# Stage 2 — Runtime
|
||||
# ------------------------------
|
||||
FROM ubuntu:24.04
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /src/accounts-api /usr/local/bin/accounts-api
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/usr/local/bin/accounts-api"]
|
||||
539
Makefile
539
Makefile
@ -1,333 +1,294 @@
|
||||
OS := $(shell uname -s)
|
||||
SHELL := /bin/bash
|
||||
O_BIN ?= /usr/local/go/bin
|
||||
PG_MAJOR ?= 16
|
||||
NODE_MAJOR ?= 22
|
||||
BASE_IMAGE_DIR ?= deploy/base-images
|
||||
OPENRESTY_IMAGE ?= xcontrol/openresty-geoip:latest
|
||||
POSTGRES_EXT_IMAGE ?= xcontrol/postgres-extensions:16
|
||||
NODE_BUILDER_IMAGE ?= xcontrol/node-builder:22
|
||||
NODE_RUNTIME_IMAGE ?= xcontrol/node-runtime:22
|
||||
GO_BUILDER_IMAGE ?= xcontrol/go-builder:1.23
|
||||
GO_RUNTIME_IMAGE ?= xcontrol/go-runtime:1.23
|
||||
ARCH := $(shell dpkg --print-architecture)
|
||||
PG_DSN ?= postgres://shenlan:password@127.0.0.1:5432/xserver?sslmode=disable
|
||||
# =========================================
|
||||
# 📦 XControl Account Service Makefile
|
||||
# =========================================
|
||||
|
||||
ifeq ($(shell id -u),0)
|
||||
SUDO :=
|
||||
else
|
||||
SUDO ?= sudo
|
||||
endif
|
||||
APP_NAME := xcontrol-account
|
||||
MAIN_FILE := ./cmd/accountsvc/main.go
|
||||
PORT ?= 8080
|
||||
OS := $(shell uname -s)
|
||||
|
||||
HOSTS_FILE ?= /etc/hosts
|
||||
HOSTS_IP ?= 127.0.0.1
|
||||
HOSTS_DOMAINS ?= dev-accounts.svc.plus dev-api.svc.plus
|
||||
DB_NAME := account
|
||||
DB_USER := shenlan
|
||||
DB_PASS := password
|
||||
DB_HOST := 127.0.0.1
|
||||
DB_PORT := 5432
|
||||
DB_URL := postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable
|
||||
|
||||
ifeq ($(OS),Darwin)
|
||||
NGINX_PREFIX ?= /opt/homebrew/openresty/nginx
|
||||
NGINX_MAIN_TEMPLATE ?= example/macos/openresty/nginx.conf
|
||||
else
|
||||
NGINX_PREFIX ?= /usr/local/openresty/nginx
|
||||
endif
|
||||
REPLICATION_MODE ?= pgsync
|
||||
|
||||
NGINX_CONF_ROOT ?= $(NGINX_PREFIX)/conf
|
||||
NGINX_CONF_DIR ?= $(NGINX_CONF_ROOT)/conf.d
|
||||
NGINX_MAIN_CONF ?= $(NGINX_CONF_ROOT)/nginx.conf
|
||||
DB_ADMIN_USER ?= $(DB_USER)
|
||||
DB_ADMIN_PASS ?= $(DB_PASS)
|
||||
|
||||
NGINX_SIT_CONFIGS := example/sit/nginx/nginx.conf
|
||||
NGINX_SIT_CONFIGS += example/sit/nginx/dev.svc.plus.conf
|
||||
NGINX_SIT_CONFIGS += example/sit/nginx/dev-api.svc.plus.conf
|
||||
NGINX_SIT_CONFIGS := example/sit/nginx/dev-accounts.svc.plus.conf
|
||||
SCHEMA_FILE := ./sql/schema.sql
|
||||
PGLOGICAL_INIT_FILE := ./sql/schema_pglogical_init.sql
|
||||
PGLOGICAL_PATCH_FILE := ./sql/schema_pglogical_patch.sql
|
||||
PGLOGICAL_REGION_FILE := ./sql/schema_pglogical_region.sql
|
||||
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/nginx.conf
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/dev.svc.plus.conf
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/api.svc.plus.conf
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/accounts.svc.plus.conf
|
||||
ACCOUNT_EXPORT_FILE ?= account-export.yaml
|
||||
ACCOUNT_IMPORT_FILE ?= account-export.yaml
|
||||
ACCOUNT_EMAIL_KEYWORD ?=
|
||||
ACCOUNT_SYNC_CONFIG ?= config/sync.yaml
|
||||
SUPERADMIN_USERNAME ?= Admin
|
||||
SUPERADMIN_PASSWORD ?= ChangeMe
|
||||
SUPERADMIN_EMAIL ?= admin@svc.plus
|
||||
|
||||
NGINX_ALL_CONFIGS := $(NGINX_SIT_CONFIGS) $(NGINX_PROD_CONFIGS)
|
||||
export PATH := /usr/local/go/bin:$(PATH)
|
||||
|
||||
export PATH := $(GO_BIN):$(PATH)
|
||||
# =========================================
|
||||
# 🧩 基础命令
|
||||
# =========================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Environment bootstrap (hosts & services)
|
||||
# -----------------------------------------------------------------------------
|
||||
.PHONY: all init build clean start stop restart dev test help \
|
||||
init-db-core init-db-replication init-db-pglogical \
|
||||
reinit-pglogical account-sync-push account-sync-pull account-sync-mirror create-db-user db-reset
|
||||
|
||||
init: configure-hosts init-nginx init-account init-rag-server
|
||||
all: build
|
||||
|
||||
install-services: configure-hosts install-nginx install-account install-rag-server
|
||||
help:
|
||||
@echo "🧭 XControl Account Service Makefile"
|
||||
@echo "make init 初始化 Go 环境与数据库"
|
||||
@echo "make init-db 执行数据库 schema(支持 REPLICATION_MODE=pgsync|pglogical)"
|
||||
@echo "make create-db-user 创建数据库用户并授权"
|
||||
@echo "make db-reset 重置整个 PostgreSQL 集群 (危险操作!)"
|
||||
@echo "make migrate-db 执行数据库迁移"
|
||||
@echo "make dump-schema 导出数据库 schema"
|
||||
@echo "make account-export 导出账号数据为 YAML"
|
||||
@echo "make account-import 从 YAML 导入账号数据"
|
||||
@echo "make create-super-admin 创建超级管理员"
|
||||
@echo "make reinit-db 重置业务 schema (不涉及 pglogical)"
|
||||
@echo "make reinit-pglogical 重新初始化 pglogical schema"
|
||||
@echo "make dev 热重载开发模式"
|
||||
@echo "make clean 清理构建产物"
|
||||
|
||||
upgrade-services: configure-hosts upgrade-nginx upgrade-account upgrade-rag-server
|
||||
# =========================================
|
||||
# 🧰 初始化
|
||||
# =========================================
|
||||
|
||||
configure-hosts:
|
||||
@set -e; \
|
||||
if [ ! -f "$(HOSTS_FILE)" ]; then \
|
||||
echo "⚠️ Hosts file $(HOSTS_FILE) not found; skipping host configuration."; \
|
||||
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
|
||||
|
||||
init-db:
|
||||
@echo ">>> 初始化数据库 schema"
|
||||
@command -v psql >/dev/null || (echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端" && exit 1)
|
||||
@$(MAKE) init-db-core
|
||||
@$(MAKE) init-db-replication
|
||||
|
||||
init-db-core:
|
||||
@echo ">>> 初始化业务 schema ($(SCHEMA_FILE))"
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(SCHEMA_FILE)
|
||||
|
||||
init-db-replication:
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
$(MAKE) init-db-pglogical; \
|
||||
else \
|
||||
for domain in $(HOSTS_DOMAINS); do \
|
||||
if grep -qE "^[[:space:]]*$(HOSTS_IP)[[:space:]]+.*\b$$domain\b" "$(HOSTS_FILE)"; then \
|
||||
echo "✅ Hosts entry exists for $$domain"; \
|
||||
else \
|
||||
echo "➕ Adding $(HOSTS_IP) $$domain to $(HOSTS_FILE)"; \
|
||||
echo "$(HOSTS_IP) $$domain" | $(SUDO) tee -a "$(HOSTS_FILE)" >/dev/null; \
|
||||
fi; \
|
||||
done; \
|
||||
echo ">>> 跳过 pglogical 初始化 (REPLICATION_MODE=$(REPLICATION_MODE))"; \
|
||||
fi
|
||||
|
||||
init-nginx:
|
||||
@$(SUDO) mkdir -p "$(NGINX_CONF_DIR)"
|
||||
@if [ -n "$(NGINX_MAIN_TEMPLATE)" ]; then \
|
||||
if [ -f "$(NGINX_MAIN_CONF)" ]; then \
|
||||
if cmp -s "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; then \
|
||||
echo "✅ $(NGINX_MAIN_CONF) already up to date"; \
|
||||
else \
|
||||
echo "⬆️ Updating $(NGINX_MAIN_CONF) from template"; \
|
||||
$(SUDO) install -m 0644 "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "➕ Installing $(NGINX_MAIN_CONF)"; \
|
||||
$(SUDO) install -m 0644 "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; \
|
||||
fi; \
|
||||
fi
|
||||
@for file in $(NGINX_ALL_CONFIGS); do \
|
||||
dest="$(NGINX_CONF_DIR)/$$(basename $$file)"; \
|
||||
if [ -f "$$dest" ]; then \
|
||||
echo "✅ $$dest already exists; skipping"; \
|
||||
else \
|
||||
echo "➕ Installing $$dest"; \
|
||||
$(SUDO) install -m 0644 "$$file" "$$dest"; \
|
||||
fi; \
|
||||
done
|
||||
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
|
||||
|
||||
install-nginx: init-nginx reload-openresty
|
||||
# =========================================
|
||||
# 🧠 PGLogical 双节点初始化
|
||||
# =========================================
|
||||
|
||||
upgrade-nginx:
|
||||
@$(SUDO) mkdir -p "$(NGINX_CONF_DIR)"
|
||||
@if [ -n "$(NGINX_MAIN_TEMPLATE)" ]; then \
|
||||
echo "⬆️ Updating $(NGINX_MAIN_CONF)"; \
|
||||
$(SUDO) install -m 0644 "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; \
|
||||
fi
|
||||
@for file in $(NGINX_ALL_CONFIGS); do \
|
||||
dest="$(NGINX_CONF_DIR)/$$(basename $$file)"; \
|
||||
echo "⬆️ Updating $$dest"; \
|
||||
$(SUDO) install -m 0644 "$$file" "$$dest"; \
|
||||
done
|
||||
@$(MAKE) reload-openresty
|
||||
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)
|
||||
|
||||
reload-openresty:
|
||||
@echo "🔄 Reloading OpenResty/Nginx if available..."
|
||||
@command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files | grep -q '^openresty.service' && { \
|
||||
$(SUDO) systemctl reload openresty 2>/dev/null || $(SUDO) systemctl restart openresty 2>/dev/null || true; \
|
||||
echo "✅ openresty.service reloaded"; \
|
||||
} || echo "ℹ️ openresty.service not managed by systemd or systemctl missing; please reload manually."
|
||||
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"
|
||||
|
||||
init-account:
|
||||
@$(MAKE) -C account init
|
||||
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"
|
||||
|
||||
install-account:
|
||||
@$(MAKE) -C account build
|
||||
# =========================================
|
||||
# 📦 数据库迁移与管理
|
||||
# =========================================
|
||||
|
||||
upgrade-account:
|
||||
@$(MAKE) -C account upgrade
|
||||
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 "✓ 数据库用户创建完成"
|
||||
|
||||
init-rag-server:
|
||||
@$(MAKE) -C rag-server init
|
||||
migrate-db:
|
||||
@echo ">>> 执行数据库迁移"
|
||||
@go run ./cmd/migratectl/main.go migrate --dsn "$(DB_URL)" --dir sql/migrations
|
||||
|
||||
install-rag-server:
|
||||
@$(MAKE) -C rag-server build
|
||||
dump-schema:
|
||||
@echo ">>> 导出 schema 到 $(SCHEMA_FILE)"
|
||||
@pg_dump -s -O -x "$(DB_URL)" > $(SCHEMA_FILE)
|
||||
|
||||
upgrade-rag-server:
|
||||
@$(MAKE) -C rag-server build
|
||||
@$(MAKE) -C rag-server restart
|
||||
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
|
||||
|
||||
.PHONY: install install-openresty install-redis install-postgresql init-db \
|
||||
build update-dashboard-manifests build-server build-dashboard \
|
||||
start start-openresty start-server start-dashboard \
|
||||
stop stop-server stop-dashboard stop-openresty restart lint-cms \
|
||||
init init-nginx install-nginx upgrade-nginx reload-openresty \
|
||||
init-account install-account upgrade-account \
|
||||
init-rag-server install-rag-server upgrade-rag-server \
|
||||
configure-hosts install-services upgrade-services \
|
||||
build-base-images docker-openresty-geoip docker-postgres-extensions \
|
||||
docker-node-builder docker-node-runtime docker-go-builder docker-go-runtime
|
||||
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
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Dependency installation
|
||||
# -----------------------------------------------------------------------------
|
||||
reset-public-schema:
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -v db_user="$(DB_USER)" -f sql/reset_public_schema.sql
|
||||
|
||||
install: install-nodejs install-go install-openresty install-redis install-postgresql
|
||||
reinit-db:
|
||||
@echo ">>> 重置业务 schema (sql/schema.sql)"
|
||||
@$(MAKE) reset-public-schema
|
||||
@$(MAKE) init-db-core
|
||||
|
||||
# --- Node.js ---------------------------------------------------------------
|
||||
install-nodejs:
|
||||
ifeq ($(OS),Darwin)
|
||||
( brew install node@22 && brew link --overwrite --force node@22 ) || brew install node
|
||||
corepack enable || true
|
||||
corepack prepare yarn@stable --activate || true
|
||||
@echo "✅ Node: $$(node -v)"; echo "✅ Yarn: $$(yarn -v 2>/dev/null || echo n/a)"
|
||||
else
|
||||
@echo "🟦 Installing Node.js $(NODE_MAJOR) via setup_ubuntu_2204.sh..."
|
||||
NODE_MAJOR=$(NODE_MAJOR) bash scripts/setup_ubuntu_2204.sh install-nodejs
|
||||
endif
|
||||
reinit-pglogical:
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
echo ">>> 重新初始化 pglogical schema"; \
|
||||
$(MAKE) init-db-pglogical; \
|
||||
else \
|
||||
echo ">>> 当前 REPLICATION_MODE=$(REPLICATION_MODE),无需 pglogical 处理"; \
|
||||
fi
|
||||
|
||||
# --- Go --------------------------------------------------------------------
|
||||
install-go:
|
||||
ifeq ($(OS),Darwin)
|
||||
brew install go
|
||||
else
|
||||
GO_VERSION=$(GO_VERSION) bash scripts/setup_ubuntu_2204.sh install-go
|
||||
endif
|
||||
# =========================================
|
||||
# 💾 账号导入导出
|
||||
# =========================================
|
||||
|
||||
# --- OpenResty -------------------------------------------------------------
|
||||
install-openresty:
|
||||
@echo "🚀 Installing OpenResty using external script..."
|
||||
@bash scripts/install-openresty.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)")
|
||||
|
||||
# --- Redis -----------------------------------------------------------------
|
||||
install-redis:
|
||||
ifeq ($(OS),Darwin)
|
||||
brew install redis && brew services start redis
|
||||
else
|
||||
@echo "🟥 Installing Redis via setup_ubuntu_2204.sh..."
|
||||
bash scripts/setup_ubuntu_2204.sh install-redis
|
||||
endif
|
||||
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)
|
||||
|
||||
# --- PostgreSQL ------------------------------------------------------------
|
||||
install-postgresql:
|
||||
ifeq ($(OS),Darwin)
|
||||
@set -e; \
|
||||
echo "🍎 Installing PostgreSQL 16 via Homebrew..."; \
|
||||
brew install postgresql@16 || true; \
|
||||
brew services start postgresql@16; \
|
||||
echo "📦 Installing pgvector extension..."; \
|
||||
brew install pgvector || true; \
|
||||
echo "📦 Installing pg_jieba (替代 zhparser + scws)..."; \
|
||||
tmp_dir=$$(mktemp -d) && cd $$tmp_dir && \
|
||||
git clone --recursive https://github.com/jaiminpan/pg_jieba.git && \
|
||||
cd pg_jieba && mkdir build && cd build && \
|
||||
cmake -DPostgreSQL_TYPE_INCLUDE_DIR=$$(brew --prefix postgresql@16)/include/postgresql/server .. && \
|
||||
make -j$$(sysctl -n hw.ncpu) && sudo make install && \
|
||||
cd / && rm -rf $$tmp_dir; \
|
||||
echo "✅ PostgreSQL extensions installed successfully!"
|
||||
else
|
||||
@set -e; \
|
||||
echo "🟨 Installing PostgreSQL 16..."; \
|
||||
bash scripts/setup_ubuntu_2204.sh install-postgresql; \
|
||||
echo "🟨 Installing pgvector extension..."; \
|
||||
bash scripts/setup_ubuntu_2204.sh install-pgvector; \
|
||||
echo "🟨 Installing pg_jieba extension (替代 zhparser + scws)..."; \
|
||||
tmp_dir=$$(mktemp -d) && cd $$tmp_dir && \
|
||||
sudo apt-get install -y cmake g++ git postgresql-server-dev-${PG_MAJOR}; \
|
||||
git clone --recursive https://github.com/jaiminpan/pg_jieba.git && \
|
||||
cd pg_jieba && mkdir build && cd build && \
|
||||
cmake -DPostgreSQL_TYPE_INCLUDE_DIR=/usr/include/postgresql/${PG_MAJOR}/server .. && \
|
||||
make -j$$(nproc) && sudo make install && \
|
||||
cd / && rm -rf $$tmp_dir; \
|
||||
echo "✅ PostgreSQL extensions installed successfully!"
|
||||
endif
|
||||
account-sync-push:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go push --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Base container images
|
||||
# -----------------------------------------------------------------------------
|
||||
account-sync-pull:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go pull --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
|
||||
build-base-images:
|
||||
@OPENRESTY_IMAGE=$(OPENRESTY_IMAGE) POSTGRES_EXT_IMAGE=$(POSTGRES_EXT_IMAGE) \
|
||||
NODE_BUILDER_IMAGE=$(NODE_BUILDER_IMAGE) NODE_RUNTIME_IMAGE=$(NODE_RUNTIME_IMAGE) \
|
||||
GO_BUILDER_IMAGE=$(GO_BUILDER_IMAGE) GO_RUNTIME_IMAGE=$(GO_RUNTIME_IMAGE) \
|
||||
bash scripts/build-base-images.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)"
|
||||
|
||||
docker-openresty-geoip:
|
||||
docker build -f $(BASE_IMAGE_DIR)/openresty-geoip.Dockerfile -t $(OPENRESTY_IMAGE) $(BASE_IMAGE_DIR)
|
||||
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)"
|
||||
|
||||
docker-postgres-extensions:
|
||||
docker build -f $(BASE_IMAGE_DIR)/postgres-extensions.Dockerfile -t $(POSTGRES_EXT_IMAGE) $(BASE_IMAGE_DIR)
|
||||
# =========================================
|
||||
# ⚙️ 编译与运行
|
||||
# =========================================
|
||||
|
||||
docker-node-builder:
|
||||
docker build -f $(BASE_IMAGE_DIR)/node-builder.Dockerfile -t $(NODE_BUILDER_IMAGE) $(BASE_IMAGE_DIR)
|
||||
build: init-go
|
||||
@go build -o $(APP_NAME) $(MAIN_FILE)
|
||||
|
||||
docker-node-runtime:
|
||||
docker build -f $(BASE_IMAGE_DIR)/node-runtime.Dockerfile -t $(NODE_RUNTIME_IMAGE) $(BASE_IMAGE_DIR)
|
||||
upgrade: build
|
||||
systemctl stop xcontrol-account
|
||||
cp xcontrol-account /usr/bin/xcontrol-account
|
||||
systemctl start xcontrol-account
|
||||
|
||||
docker-go-builder:
|
||||
docker build -f $(BASE_IMAGE_DIR)/go-builder.Dockerfile -t $(GO_BUILDER_IMAGE) $(BASE_IMAGE_DIR)
|
||||
start: build
|
||||
@./$(APP_NAME) --config config/account.yaml
|
||||
|
||||
docker-go-runtime:
|
||||
docker build -f $(BASE_IMAGE_DIR)/go-runtime.Dockerfile -t $(GO_RUNTIME_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Database initialization
|
||||
# -----------------------------------------------------------------------------
|
||||
init-db:
|
||||
@psql $(PG_DSN) -f rag-server/sql/schema.sql
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build targets
|
||||
# -----------------------------------------------------------------------------
|
||||
build: update-dashboard-manifests build-cli build-server build-dashboard
|
||||
|
||||
build-cli:
|
||||
$(MAKE) -C rag-server/cmd/rag-server-cli build
|
||||
|
||||
build-server:
|
||||
$(MAKE) -C rag-server build
|
||||
|
||||
build-dashboard:
|
||||
$(MAKE) -C dashboard build SKIP_SYNC=1
|
||||
|
||||
update-dashboard-manifests:
|
||||
$(MAKE) -C dashboard sync-dl-index
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Run targets
|
||||
# -----------------------------------------------------------------------------
|
||||
start: start-openresty start-server start-dashboard
|
||||
|
||||
start-server:
|
||||
$(MAKE) -C rag-server start
|
||||
|
||||
start-dashboard:
|
||||
$(MAKE) -C dashboard start
|
||||
|
||||
stop: stop-server stop-dashboard stop-openresty
|
||||
|
||||
stop-server:
|
||||
$(MAKE) -C rag-server stop
|
||||
|
||||
stop-dashboard:
|
||||
$(MAKE) -C dashboard stop
|
||||
|
||||
start-openresty:
|
||||
ifeq ($(OS),Darwin)
|
||||
@brew services start openresty >/dev/null 2>&1 || \
|
||||
( echo "Creating LaunchAgent for OpenResty..." && \
|
||||
mkdir -p ~/Library/LaunchAgents && \
|
||||
printf '%s\n' '<?xml version="1.0" encoding="UTF-8?>' \
|
||||
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
|
||||
'<plist version="1.0"><dict>' \
|
||||
' <key>Label</key><string>homebrew.mxcl.openresty</string>' \
|
||||
' <key>ProgramArguments</key>' \
|
||||
' <array>' \
|
||||
' <string>/opt/homebrew/openresty/nginx/sbin/nginx</string>' \
|
||||
' <string>-g</string>' \
|
||||
' <string>daemon off;</string>' \
|
||||
' </array>' \
|
||||
' <key>RunAtLoad</key><true/>' \
|
||||
'</dict></plist>' \
|
||||
> ~/Library/LaunchAgents/homebrew.mxcl.openresty.plist && \
|
||||
brew services start ~/Library/LaunchAgents/homebrew.mxcl.openresty.plist )
|
||||
else
|
||||
sudo systemctl enable --now openresty || echo "⚠️ openresty.service missing or inactive"
|
||||
endif
|
||||
|
||||
stop-openresty:
|
||||
ifeq ($(OS),Darwin)
|
||||
-brew services stop openresty >/dev/null 2>&1
|
||||
else
|
||||
-sudo systemctl stop openresty >/dev/null 2>&1
|
||||
endif
|
||||
stop:
|
||||
@pkill -f "$(APP_NAME)" || echo "⚠️ 未找到运行进程"
|
||||
|
||||
restart: stop start
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CMS configuration validation
|
||||
# -----------------------------------------------------------------------------
|
||||
lint-cms:
|
||||
python3 scripts/validate_cms_config.py
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
clean:
|
||||
rm -f $(APP_NAME) *.pid *.log
|
||||
|
||||
333
Makefile.account
Normal file
333
Makefile.account
Normal file
@ -0,0 +1,333 @@
|
||||
OS := $(shell uname -s)
|
||||
SHELL := /bin/bash
|
||||
O_BIN ?= /usr/local/go/bin
|
||||
PG_MAJOR ?= 16
|
||||
NODE_MAJOR ?= 22
|
||||
BASE_IMAGE_DIR ?= deploy/base-images
|
||||
OPENRESTY_IMAGE ?= xcontrol/openresty-geoip:latest
|
||||
POSTGRES_EXT_IMAGE ?= xcontrol/postgres-extensions:16
|
||||
NODE_BUILDER_IMAGE ?= xcontrol/node-builder:22
|
||||
NODE_RUNTIME_IMAGE ?= xcontrol/node-runtime:22
|
||||
GO_BUILDER_IMAGE ?= xcontrol/go-builder:1.23
|
||||
GO_RUNTIME_IMAGE ?= xcontrol/go-runtime:1.23
|
||||
ARCH := $(shell dpkg --print-architecture)
|
||||
PG_DSN ?= postgres://shenlan:password@127.0.0.1:5432/xserver?sslmode=disable
|
||||
|
||||
ifeq ($(shell id -u),0)
|
||||
SUDO :=
|
||||
else
|
||||
SUDO ?= sudo
|
||||
endif
|
||||
|
||||
HOSTS_FILE ?= /etc/hosts
|
||||
HOSTS_IP ?= 127.0.0.1
|
||||
HOSTS_DOMAINS ?= dev-accounts.svc.plus dev-api.svc.plus
|
||||
|
||||
ifeq ($(OS),Darwin)
|
||||
NGINX_PREFIX ?= /opt/homebrew/openresty/nginx
|
||||
NGINX_MAIN_TEMPLATE ?= example/macos/openresty/nginx.conf
|
||||
else
|
||||
NGINX_PREFIX ?= /usr/local/openresty/nginx
|
||||
endif
|
||||
|
||||
NGINX_CONF_ROOT ?= $(NGINX_PREFIX)/conf
|
||||
NGINX_CONF_DIR ?= $(NGINX_CONF_ROOT)/conf.d
|
||||
NGINX_MAIN_CONF ?= $(NGINX_CONF_ROOT)/nginx.conf
|
||||
|
||||
NGINX_SIT_CONFIGS := example/sit/nginx/nginx.conf
|
||||
NGINX_SIT_CONFIGS += example/sit/nginx/dev.svc.plus.conf
|
||||
NGINX_SIT_CONFIGS += example/sit/nginx/dev-api.svc.plus.conf
|
||||
NGINX_SIT_CONFIGS := example/sit/nginx/dev-accounts.svc.plus.conf
|
||||
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/nginx.conf
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/dev.svc.plus.conf
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/api.svc.plus.conf
|
||||
NGINX_PROD_CONFIGS := example/prod/nginx/accounts.svc.plus.conf
|
||||
|
||||
NGINX_ALL_CONFIGS := $(NGINX_SIT_CONFIGS) $(NGINX_PROD_CONFIGS)
|
||||
|
||||
export PATH := $(GO_BIN):$(PATH)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Environment bootstrap (hosts & services)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
init: configure-hosts init-nginx init-account init-rag-server
|
||||
|
||||
install-services: configure-hosts install-nginx install-account install-rag-server
|
||||
|
||||
upgrade-services: configure-hosts upgrade-nginx upgrade-account upgrade-rag-server
|
||||
|
||||
configure-hosts:
|
||||
@set -e; \
|
||||
if [ ! -f "$(HOSTS_FILE)" ]; then \
|
||||
echo "⚠️ Hosts file $(HOSTS_FILE) not found; skipping host configuration."; \
|
||||
else \
|
||||
for domain in $(HOSTS_DOMAINS); do \
|
||||
if grep -qE "^[[:space:]]*$(HOSTS_IP)[[:space:]]+.*\b$$domain\b" "$(HOSTS_FILE)"; then \
|
||||
echo "✅ Hosts entry exists for $$domain"; \
|
||||
else \
|
||||
echo "➕ Adding $(HOSTS_IP) $$domain to $(HOSTS_FILE)"; \
|
||||
echo "$(HOSTS_IP) $$domain" | $(SUDO) tee -a "$(HOSTS_FILE)" >/dev/null; \
|
||||
fi; \
|
||||
done; \
|
||||
fi
|
||||
|
||||
init-nginx:
|
||||
@$(SUDO) mkdir -p "$(NGINX_CONF_DIR)"
|
||||
@if [ -n "$(NGINX_MAIN_TEMPLATE)" ]; then \
|
||||
if [ -f "$(NGINX_MAIN_CONF)" ]; then \
|
||||
if cmp -s "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; then \
|
||||
echo "✅ $(NGINX_MAIN_CONF) already up to date"; \
|
||||
else \
|
||||
echo "⬆️ Updating $(NGINX_MAIN_CONF) from template"; \
|
||||
$(SUDO) install -m 0644 "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "➕ Installing $(NGINX_MAIN_CONF)"; \
|
||||
$(SUDO) install -m 0644 "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; \
|
||||
fi; \
|
||||
fi
|
||||
@for file in $(NGINX_ALL_CONFIGS); do \
|
||||
dest="$(NGINX_CONF_DIR)/$$(basename $$file)"; \
|
||||
if [ -f "$$dest" ]; then \
|
||||
echo "✅ $$dest already exists; skipping"; \
|
||||
else \
|
||||
echo "➕ Installing $$dest"; \
|
||||
$(SUDO) install -m 0644 "$$file" "$$dest"; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
install-nginx: init-nginx reload-openresty
|
||||
|
||||
upgrade-nginx:
|
||||
@$(SUDO) mkdir -p "$(NGINX_CONF_DIR)"
|
||||
@if [ -n "$(NGINX_MAIN_TEMPLATE)" ]; then \
|
||||
echo "⬆️ Updating $(NGINX_MAIN_CONF)"; \
|
||||
$(SUDO) install -m 0644 "$(NGINX_MAIN_TEMPLATE)" "$(NGINX_MAIN_CONF)"; \
|
||||
fi
|
||||
@for file in $(NGINX_ALL_CONFIGS); do \
|
||||
dest="$(NGINX_CONF_DIR)/$$(basename $$file)"; \
|
||||
echo "⬆️ Updating $$dest"; \
|
||||
$(SUDO) install -m 0644 "$$file" "$$dest"; \
|
||||
done
|
||||
@$(MAKE) reload-openresty
|
||||
|
||||
reload-openresty:
|
||||
@echo "🔄 Reloading OpenResty/Nginx if available..."
|
||||
@command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files | grep -q '^openresty.service' && { \
|
||||
$(SUDO) systemctl reload openresty 2>/dev/null || $(SUDO) systemctl restart openresty 2>/dev/null || true; \
|
||||
echo "✅ openresty.service reloaded"; \
|
||||
} || echo "ℹ️ openresty.service not managed by systemd or systemctl missing; please reload manually."
|
||||
|
||||
init-account:
|
||||
@$(MAKE) -C account init
|
||||
|
||||
install-account:
|
||||
@$(MAKE) -C account build
|
||||
|
||||
upgrade-account:
|
||||
@$(MAKE) -C account upgrade
|
||||
|
||||
init-rag-server:
|
||||
@$(MAKE) -C rag-server init
|
||||
|
||||
install-rag-server:
|
||||
@$(MAKE) -C rag-server build
|
||||
|
||||
upgrade-rag-server:
|
||||
@$(MAKE) -C rag-server build
|
||||
@$(MAKE) -C rag-server restart
|
||||
|
||||
.PHONY: install install-openresty install-redis install-postgresql init-db \
|
||||
build update-dashboard-manifests build-server build-dashboard \
|
||||
start start-openresty start-server start-dashboard \
|
||||
stop stop-server stop-dashboard stop-openresty restart lint-cms \
|
||||
init init-nginx install-nginx upgrade-nginx reload-openresty \
|
||||
init-account install-account upgrade-account \
|
||||
init-rag-server install-rag-server upgrade-rag-server \
|
||||
configure-hosts install-services upgrade-services \
|
||||
build-base-images docker-openresty-geoip docker-postgres-extensions \
|
||||
docker-node-builder docker-node-runtime docker-go-builder docker-go-runtime
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Dependency installation
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
install: install-nodejs install-go install-openresty install-redis install-postgresql
|
||||
|
||||
# --- Node.js ---------------------------------------------------------------
|
||||
install-nodejs:
|
||||
ifeq ($(OS),Darwin)
|
||||
( brew install node@22 && brew link --overwrite --force node@22 ) || brew install node
|
||||
corepack enable || true
|
||||
corepack prepare yarn@stable --activate || true
|
||||
@echo "✅ Node: $$(node -v)"; echo "✅ Yarn: $$(yarn -v 2>/dev/null || echo n/a)"
|
||||
else
|
||||
@echo "🟦 Installing Node.js $(NODE_MAJOR) via setup_ubuntu_2204.sh..."
|
||||
NODE_MAJOR=$(NODE_MAJOR) bash scripts/setup_ubuntu_2204.sh install-nodejs
|
||||
endif
|
||||
|
||||
# --- Go --------------------------------------------------------------------
|
||||
install-go:
|
||||
ifeq ($(OS),Darwin)
|
||||
brew install go
|
||||
else
|
||||
GO_VERSION=$(GO_VERSION) bash scripts/setup_ubuntu_2204.sh install-go
|
||||
endif
|
||||
|
||||
# --- OpenResty -------------------------------------------------------------
|
||||
install-openresty:
|
||||
@echo "🚀 Installing OpenResty using external script..."
|
||||
@bash scripts/install-openresty.sh; \
|
||||
|
||||
# --- Redis -----------------------------------------------------------------
|
||||
install-redis:
|
||||
ifeq ($(OS),Darwin)
|
||||
brew install redis && brew services start redis
|
||||
else
|
||||
@echo "🟥 Installing Redis via setup_ubuntu_2204.sh..."
|
||||
bash scripts/setup_ubuntu_2204.sh install-redis
|
||||
endif
|
||||
|
||||
# --- PostgreSQL ------------------------------------------------------------
|
||||
install-postgresql:
|
||||
ifeq ($(OS),Darwin)
|
||||
@set -e; \
|
||||
echo "🍎 Installing PostgreSQL 16 via Homebrew..."; \
|
||||
brew install postgresql@16 || true; \
|
||||
brew services start postgresql@16; \
|
||||
echo "📦 Installing pgvector extension..."; \
|
||||
brew install pgvector || true; \
|
||||
echo "📦 Installing pg_jieba (替代 zhparser + scws)..."; \
|
||||
tmp_dir=$$(mktemp -d) && cd $$tmp_dir && \
|
||||
git clone --recursive https://github.com/jaiminpan/pg_jieba.git && \
|
||||
cd pg_jieba && mkdir build && cd build && \
|
||||
cmake -DPostgreSQL_TYPE_INCLUDE_DIR=$$(brew --prefix postgresql@16)/include/postgresql/server .. && \
|
||||
make -j$$(sysctl -n hw.ncpu) && sudo make install && \
|
||||
cd / && rm -rf $$tmp_dir; \
|
||||
echo "✅ PostgreSQL extensions installed successfully!"
|
||||
else
|
||||
@set -e; \
|
||||
echo "🟨 Installing PostgreSQL 16..."; \
|
||||
bash scripts/setup_ubuntu_2204.sh install-postgresql; \
|
||||
echo "🟨 Installing pgvector extension..."; \
|
||||
bash scripts/setup_ubuntu_2204.sh install-pgvector; \
|
||||
echo "🟨 Installing pg_jieba extension (替代 zhparser + scws)..."; \
|
||||
tmp_dir=$$(mktemp -d) && cd $$tmp_dir && \
|
||||
sudo apt-get install -y cmake g++ git postgresql-server-dev-${PG_MAJOR}; \
|
||||
git clone --recursive https://github.com/jaiminpan/pg_jieba.git && \
|
||||
cd pg_jieba && mkdir build && cd build && \
|
||||
cmake -DPostgreSQL_TYPE_INCLUDE_DIR=/usr/include/postgresql/${PG_MAJOR}/server .. && \
|
||||
make -j$$(nproc) && sudo make install && \
|
||||
cd / && rm -rf $$tmp_dir; \
|
||||
echo "✅ PostgreSQL extensions installed successfully!"
|
||||
endif
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Base container images
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
build-base-images:
|
||||
@OPENRESTY_IMAGE=$(OPENRESTY_IMAGE) POSTGRES_EXT_IMAGE=$(POSTGRES_EXT_IMAGE) \
|
||||
NODE_BUILDER_IMAGE=$(NODE_BUILDER_IMAGE) NODE_RUNTIME_IMAGE=$(NODE_RUNTIME_IMAGE) \
|
||||
GO_BUILDER_IMAGE=$(GO_BUILDER_IMAGE) GO_RUNTIME_IMAGE=$(GO_RUNTIME_IMAGE) \
|
||||
bash scripts/build-base-images.sh
|
||||
|
||||
docker-openresty-geoip:
|
||||
docker build -f $(BASE_IMAGE_DIR)/openresty-geoip.Dockerfile -t $(OPENRESTY_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
docker-postgres-extensions:
|
||||
docker build -f $(BASE_IMAGE_DIR)/postgres-extensions.Dockerfile -t $(POSTGRES_EXT_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
docker-node-builder:
|
||||
docker build -f $(BASE_IMAGE_DIR)/node-builder.Dockerfile -t $(NODE_BUILDER_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
docker-node-runtime:
|
||||
docker build -f $(BASE_IMAGE_DIR)/node-runtime.Dockerfile -t $(NODE_RUNTIME_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
docker-go-builder:
|
||||
docker build -f $(BASE_IMAGE_DIR)/go-builder.Dockerfile -t $(GO_BUILDER_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
docker-go-runtime:
|
||||
docker build -f $(BASE_IMAGE_DIR)/go-runtime.Dockerfile -t $(GO_RUNTIME_IMAGE) $(BASE_IMAGE_DIR)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Database initialization
|
||||
# -----------------------------------------------------------------------------
|
||||
init-db:
|
||||
@psql $(PG_DSN) -f rag-server/sql/schema.sql
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build targets
|
||||
# -----------------------------------------------------------------------------
|
||||
build: update-dashboard-manifests build-cli build-server build-dashboard
|
||||
|
||||
build-cli:
|
||||
$(MAKE) -C rag-server/cmd/rag-server-cli build
|
||||
|
||||
build-server:
|
||||
$(MAKE) -C rag-server build
|
||||
|
||||
build-dashboard:
|
||||
$(MAKE) -C dashboard build SKIP_SYNC=1
|
||||
|
||||
update-dashboard-manifests:
|
||||
$(MAKE) -C dashboard sync-dl-index
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Run targets
|
||||
# -----------------------------------------------------------------------------
|
||||
start: start-openresty start-server start-dashboard
|
||||
|
||||
start-server:
|
||||
$(MAKE) -C rag-server start
|
||||
|
||||
start-dashboard:
|
||||
$(MAKE) -C dashboard start
|
||||
|
||||
stop: stop-server stop-dashboard stop-openresty
|
||||
|
||||
stop-server:
|
||||
$(MAKE) -C rag-server stop
|
||||
|
||||
stop-dashboard:
|
||||
$(MAKE) -C dashboard stop
|
||||
|
||||
start-openresty:
|
||||
ifeq ($(OS),Darwin)
|
||||
@brew services start openresty >/dev/null 2>&1 || \
|
||||
( echo "Creating LaunchAgent for OpenResty..." && \
|
||||
mkdir -p ~/Library/LaunchAgents && \
|
||||
printf '%s\n' '<?xml version="1.0" encoding="UTF-8?>' \
|
||||
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
|
||||
'<plist version="1.0"><dict>' \
|
||||
' <key>Label</key><string>homebrew.mxcl.openresty</string>' \
|
||||
' <key>ProgramArguments</key>' \
|
||||
' <array>' \
|
||||
' <string>/opt/homebrew/openresty/nginx/sbin/nginx</string>' \
|
||||
' <string>-g</string>' \
|
||||
' <string>daemon off;</string>' \
|
||||
' </array>' \
|
||||
' <key>RunAtLoad</key><true/>' \
|
||||
'</dict></plist>' \
|
||||
> ~/Library/LaunchAgents/homebrew.mxcl.openresty.plist && \
|
||||
brew services start ~/Library/LaunchAgents/homebrew.mxcl.openresty.plist )
|
||||
else
|
||||
sudo systemctl enable --now openresty || echo "⚠️ openresty.service missing or inactive"
|
||||
endif
|
||||
|
||||
stop-openresty:
|
||||
ifeq ($(OS),Darwin)
|
||||
-brew services stop openresty >/dev/null 2>&1
|
||||
else
|
||||
-sudo systemctl stop openresty >/dev/null 2>&1
|
||||
endif
|
||||
|
||||
restart: stop start
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CMS configuration validation
|
||||
# -----------------------------------------------------------------------------
|
||||
lint-cms:
|
||||
python3 scripts/validate_cms_config.py
|
||||
294
account/Makefile
294
account/Makefile
@ -1,294 +0,0 @@
|
||||
# =========================================
|
||||
# 📦 XControl Account Service Makefile
|
||||
# =========================================
|
||||
|
||||
APP_NAME := xcontrol-account
|
||||
MAIN_FILE := ./cmd/accountsvc/main.go
|
||||
PORT ?= 8080
|
||||
OS := $(shell uname -s)
|
||||
|
||||
DB_NAME := account
|
||||
DB_USER := shenlan
|
||||
DB_PASS := password
|
||||
DB_HOST := 127.0.0.1
|
||||
DB_PORT := 5432
|
||||
DB_URL := postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable
|
||||
|
||||
REPLICATION_MODE ?= pgsync
|
||||
|
||||
DB_ADMIN_USER ?= $(DB_USER)
|
||||
DB_ADMIN_PASS ?= $(DB_PASS)
|
||||
|
||||
SCHEMA_FILE := ../sql/schema.sql
|
||||
PGLOGICAL_INIT_FILE := ../sql/schema_pglogical_init.sql
|
||||
PGLOGICAL_PATCH_FILE := ../sql/schema_pglogical_patch.sql
|
||||
PGLOGICAL_REGION_FILE := ../sql/schema_pglogical_region.sql
|
||||
|
||||
ACCOUNT_EXPORT_FILE ?= account-export.yaml
|
||||
ACCOUNT_IMPORT_FILE ?= account-export.yaml
|
||||
ACCOUNT_EMAIL_KEYWORD ?=
|
||||
ACCOUNT_SYNC_CONFIG ?= config/sync.yaml
|
||||
SUPERADMIN_USERNAME ?= Admin
|
||||
SUPERADMIN_PASSWORD ?= ChangeMe
|
||||
SUPERADMIN_EMAIL ?= admin@svc.plus
|
||||
|
||||
export PATH := /usr/local/go/bin:$(PATH)
|
||||
|
||||
# =========================================
|
||||
# 🧩 基础命令
|
||||
# =========================================
|
||||
|
||||
.PHONY: all init build clean start stop restart dev test help \
|
||||
init-db-core init-db-replication init-db-pglogical \
|
||||
reinit-pglogical account-sync-push account-sync-pull account-sync-mirror create-db-user db-reset
|
||||
|
||||
all: build
|
||||
|
||||
help:
|
||||
@echo "🧭 XControl Account Service Makefile"
|
||||
@echo "make init 初始化 Go 环境与数据库"
|
||||
@echo "make init-db 执行数据库 schema(支持 REPLICATION_MODE=pgsync|pglogical)"
|
||||
@echo "make create-db-user 创建数据库用户并授权"
|
||||
@echo "make db-reset 重置整个 PostgreSQL 集群 (危险操作!)"
|
||||
@echo "make migrate-db 执行数据库迁移"
|
||||
@echo "make dump-schema 导出数据库 schema"
|
||||
@echo "make account-export 导出账号数据为 YAML"
|
||||
@echo "make account-import 从 YAML 导入账号数据"
|
||||
@echo "make create-super-admin 创建超级管理员"
|
||||
@echo "make reinit-db 重置业务 schema (不涉及 pglogical)"
|
||||
@echo "make reinit-pglogical 重新初始化 pglogical schema"
|
||||
@echo "make dev 热重载开发模式"
|
||||
@echo "make clean 清理构建产物"
|
||||
|
||||
# =========================================
|
||||
# 🧰 初始化
|
||||
# =========================================
|
||||
|
||||
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
|
||||
|
||||
init-db:
|
||||
@echo ">>> 初始化数据库 schema"
|
||||
@command -v psql >/dev/null || (echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端" && exit 1)
|
||||
@$(MAKE) init-db-core
|
||||
@$(MAKE) init-db-replication
|
||||
|
||||
init-db-core:
|
||||
@echo ">>> 初始化业务 schema ($(SCHEMA_FILE))"
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(SCHEMA_FILE)
|
||||
|
||||
init-db-replication:
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
$(MAKE) init-db-pglogical; \
|
||||
else \
|
||||
echo ">>> 跳过 pglogical 初始化 (REPLICATION_MODE=$(REPLICATION_MODE))"; \
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
# =========================================
|
||||
# 🧠 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)
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
# =========================================
|
||||
# 📦 数据库迁移与管理
|
||||
# =========================================
|
||||
|
||||
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 "✓ 数据库用户创建完成"
|
||||
|
||||
migrate-db:
|
||||
@echo ">>> 执行数据库迁移"
|
||||
@go run ./cmd/migratectl/main.go migrate --dsn "$(DB_URL)" --dir ../sql/migrations
|
||||
|
||||
dump-schema:
|
||||
@echo ">>> 导出 schema 到 $(SCHEMA_FILE)"
|
||||
@pg_dump -s -O -x "$(DB_URL)" > $(SCHEMA_FILE)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
reset-public-schema:
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -v db_user="$(DB_USER)" -f ../sql/reset_public_schema.sql
|
||||
|
||||
reinit-db:
|
||||
@echo ">>> 重置业务 schema ($(SCHEMA_FILE))"
|
||||
@$(MAKE) reset-public-schema
|
||||
@$(MAKE) init-db-core
|
||||
|
||||
reinit-pglogical:
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
echo ">>> 重新初始化 pglogical schema"; \
|
||||
$(MAKE) init-db-pglogical; \
|
||||
else \
|
||||
echo ">>> 当前 REPLICATION_MODE=$(REPLICATION_MODE),无需 pglogical 处理"; \
|
||||
fi
|
||||
|
||||
# =========================================
|
||||
# 💾 账号导入导出
|
||||
# =========================================
|
||||
|
||||
account-export:
|
||||
@go run ./cmd/migratectl/main.go export --dsn "$(DB_URL)" --output "$(ACCOUNT_EXPORT_FILE)" $(if $(ACCOUNT_EMAIL_KEYWORD),--email "$(ACCOUNT_EMAIL_KEYWORD)")
|
||||
|
||||
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)
|
||||
|
||||
account-sync-push:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go push --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
|
||||
account-sync-pull:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go pull --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
|
||||
account-sync-mirror:
|
||||
@[ -f "$(ACCOUNT_SYNC_CONFIG)" ] || (echo "❌ 未找到配置文件 $(ACCOUNT_SYNC_CONFIG)"; exit 1)
|
||||
@go run ./cmd/syncctl/main.go mirror --config "$(ACCOUNT_SYNC_CONFIG)"
|
||||
|
||||
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)"
|
||||
|
||||
# =========================================
|
||||
# ⚙️ 编译与运行
|
||||
# =========================================
|
||||
|
||||
build: init-go
|
||||
@go build -o $(APP_NAME) $(MAIN_FILE)
|
||||
|
||||
upgrade: build
|
||||
systemctl stop xcontrol-account
|
||||
cp xcontrol-account /usr/bin/xcontrol-account
|
||||
systemctl start xcontrol-account
|
||||
|
||||
start: build
|
||||
@./$(APP_NAME) --config config/account.yaml
|
||||
|
||||
stop:
|
||||
@pkill -f "$(APP_NAME)" || echo "⚠️ 未找到运行进程"
|
||||
|
||||
restart: stop start
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
clean:
|
||||
rm -f $(APP_NAME) *.pid *.log
|
||||
@ -1,64 +0,0 @@
|
||||
module account
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/pkg/sftp v1.13.10
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
golang.org/x/crypto v0.45.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
xcontrol v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
)
|
||||
|
||||
replace xcontrol => ..
|
||||
146
account/go.sum
146
account/go.sum
@ -1,146 +0,0 @@
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
||||
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
||||
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
482
cmd/accountsapi/main.go
Normal file
482
cmd/accountsapi/main.go
Normal file
@ -0,0 +1,482 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAddr = "127.0.0.1:8080"
|
||||
defaultBodyLimit = 1 << 20 // 1 MiB
|
||||
defaultSessionTTL = 24 * time.Hour
|
||||
defaultRateLimitPerMin = 60
|
||||
cookieName = "accounts_session"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
DBUser string
|
||||
DBPassword string
|
||||
DBName string
|
||||
DBSSLMode string
|
||||
BodyLimit int64
|
||||
SessionTTL time.Duration
|
||||
RateLimitRPM int
|
||||
}
|
||||
|
||||
type server struct {
|
||||
log *slog.Logger
|
||||
pool *pgxpool.Pool
|
||||
sessions *sessionStore
|
||||
bodyLimit int64
|
||||
limiter *rateLimiter
|
||||
sessionTTL time.Duration
|
||||
}
|
||||
|
||||
type session struct {
|
||||
userID int64
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type sessionStore struct {
|
||||
mu sync.RWMutex
|
||||
data map[string]session
|
||||
}
|
||||
|
||||
type rateLimiter struct {
|
||||
mu sync.Mutex
|
||||
limit int
|
||||
window time.Duration
|
||||
clients map[string]rateState
|
||||
disabled bool
|
||||
}
|
||||
|
||||
type rateState struct {
|
||||
count int
|
||||
resetAt time.Time
|
||||
}
|
||||
|
||||
type loginRequest struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type userResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, err := loadConfig()
|
||||
if err != nil {
|
||||
slog.Error("config error", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||
|
||||
pool, err := openPool(cfg)
|
||||
if err != nil {
|
||||
logger.Error("db connection failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := pool.Ping(ctx); err != nil {
|
||||
logger.Error("db health check failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
srv := &server{
|
||||
log: logger,
|
||||
pool: pool,
|
||||
sessions: newSessionStore(),
|
||||
bodyLimit: cfg.BodyLimit,
|
||||
limiter: newRateLimiter(cfg.RateLimitRPM, time.Minute),
|
||||
sessionTTL: cfg.SessionTTL,
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Addr: defaultAddr,
|
||||
Handler: srv.routes(),
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
logger.Info("accounts api listening", "addr", defaultAddr)
|
||||
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
logger.Error("server failed", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
waitForShutdown(logger, httpServer)
|
||||
}
|
||||
|
||||
func loadConfig() (config, error) {
|
||||
cfg := config{
|
||||
DBUser: strings.TrimSpace(os.Getenv("ACCOUNTS_DB_USER")),
|
||||
DBPassword: os.Getenv("ACCOUNTS_DB_PASSWORD"),
|
||||
DBName: strings.TrimSpace(os.Getenv("ACCOUNTS_DB_NAME")),
|
||||
DBSSLMode: strings.TrimSpace(os.Getenv("ACCOUNTS_DB_SSLMODE")),
|
||||
BodyLimit: defaultBodyLimit,
|
||||
SessionTTL: defaultSessionTTL,
|
||||
RateLimitRPM: defaultRateLimitPerMin,
|
||||
}
|
||||
if cfg.DBSSLMode == "" {
|
||||
cfg.DBSSLMode = "disable"
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv("ACCOUNTS_BODY_LIMIT")); v != "" {
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || n <= 0 {
|
||||
return config{}, fmt.Errorf("invalid ACCOUNTS_BODY_LIMIT: %q", v)
|
||||
}
|
||||
cfg.BodyLimit = n
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv("ACCOUNTS_SESSION_TTL")); v != "" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil || d <= 0 {
|
||||
return config{}, fmt.Errorf("invalid ACCOUNTS_SESSION_TTL: %q", v)
|
||||
}
|
||||
cfg.SessionTTL = d
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv("ACCOUNTS_RATE_LIMIT_RPM")); v != "" {
|
||||
if v == "0" {
|
||||
cfg.RateLimitRPM = 0
|
||||
} else {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 0 {
|
||||
return config{}, fmt.Errorf("invalid ACCOUNTS_RATE_LIMIT_RPM: %q", v)
|
||||
}
|
||||
cfg.RateLimitRPM = n
|
||||
}
|
||||
}
|
||||
if cfg.DBUser == "" || cfg.DBName == "" {
|
||||
return config{}, errors.New("ACCOUNTS_DB_USER and ACCOUNTS_DB_NAME are required")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func openPool(cfg config) (*pgxpool.Pool, error) {
|
||||
dsn := (&url.URL{
|
||||
Scheme: "postgres",
|
||||
User: url.UserPassword(cfg.DBUser, cfg.DBPassword),
|
||||
Host: "127.0.0.1:15432",
|
||||
Path: cfg.DBName,
|
||||
RawQuery: "sslmode=" + url.QueryEscape(cfg.DBSSLMode),
|
||||
}).String()
|
||||
pgxCfg, err := pgxpool.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pgxCfg.MaxConns = 10
|
||||
pgxCfg.MinConns = 2
|
||||
pgxCfg.MaxConnIdleTime = 5 * time.Minute
|
||||
pgxCfg.MaxConnLifetime = 30 * time.Minute
|
||||
return pgxpool.NewWithConfig(context.Background(), pgxCfg)
|
||||
}
|
||||
|
||||
func waitForShutdown(logger *slog.Logger, httpServer *http.Server) {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-signals
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
logger.Info("shutting down")
|
||||
if err := httpServer.Shutdown(ctx); err != nil {
|
||||
logger.Error("shutdown error", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) routes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/login", s.handleLogin)
|
||||
mux.HandleFunc("/api/logout", s.handleLogout)
|
||||
mux.HandleFunc("/api/me", s.handleMe)
|
||||
return s.middleware(mux)
|
||||
}
|
||||
|
||||
func (s *server) middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
wrapped := &statusWriter{ResponseWriter: w, status: http.StatusOK}
|
||||
|
||||
if s.limiter != nil {
|
||||
if ok := s.limiter.Allow(clientIP(r)); !ok {
|
||||
writeJSON(wrapped, http.StatusTooManyRequests, map[string]string{"error": "rate_limited"})
|
||||
s.logRequest(r, wrapped.status, start)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if r.Body != nil && s.bodyLimit > 0 {
|
||||
r.Body = http.MaxBytesReader(wrapped, r.Body, s.bodyLimit)
|
||||
}
|
||||
|
||||
next.ServeHTTP(wrapped, r)
|
||||
s.logRequest(r, wrapped.status, start)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method_not_allowed"})
|
||||
return
|
||||
}
|
||||
var req loginRequest
|
||||
if err := decodeJSON(r, &req); err != nil {
|
||||
if isBodyTooLarge(err) {
|
||||
writeJSON(w, http.StatusRequestEntityTooLarge, map[string]string{"error": "body_too_large"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid_json"})
|
||||
return
|
||||
}
|
||||
email := strings.ToLower(strings.TrimSpace(req.Email))
|
||||
if email == "" || req.Password == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "email_and_password_required"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
var (
|
||||
userID int64
|
||||
passwordHash string
|
||||
)
|
||||
err := s.pool.QueryRow(ctx, "SELECT id, password_hash FROM users WHERE email=$1", email).Scan(&userID, &passwordHash)
|
||||
if err != nil {
|
||||
if !errors.Is(err, pgx.ErrNoRows) {
|
||||
s.log.Error("login query failed", "err", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "server_error"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid_credentials"})
|
||||
return
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)) != nil {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid_credentials"})
|
||||
return
|
||||
}
|
||||
|
||||
sessionID, err := generateToken(32)
|
||||
if err != nil {
|
||||
s.log.Error("session token generation failed", "err", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "server_error"})
|
||||
return
|
||||
}
|
||||
expiresAt := time.Now().Add(s.sessionTTL)
|
||||
s.sessions.Set(sessionID, session{userID: userID, expiresAt: expiresAt})
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieName,
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: expiresAt,
|
||||
})
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *server) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method_not_allowed"})
|
||||
return
|
||||
}
|
||||
if cookie, err := r.Cookie(cookieName); err == nil && cookie.Value != "" {
|
||||
s.sessions.Delete(cookie.Value)
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieName,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: -1,
|
||||
})
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *server) handleMe(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method_not_allowed"})
|
||||
return
|
||||
}
|
||||
cookie, err := r.Cookie(cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
sess, ok := s.sessions.Get(cookie.Value)
|
||||
if !ok || time.Now().After(sess.expiresAt) {
|
||||
s.sessions.Delete(cookie.Value)
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
var user userResponse
|
||||
err = s.pool.QueryRow(ctx, "SELECT id, email, created_at FROM users WHERE id=$1", sess.userID).
|
||||
Scan(&user.ID, &user.Email, &user.CreatedAt)
|
||||
if err != nil {
|
||||
if !errors.Is(err, pgx.ErrNoRows) {
|
||||
s.log.Error("me query failed", "err", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "server_error"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"user": user})
|
||||
}
|
||||
|
||||
func decodeJSON(r *http.Request, dst any) error {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
if decoder.More() {
|
||||
return errors.New("extra json fields")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBodyTooLarge(err error) bool {
|
||||
var maxErr *http.MaxBytesError
|
||||
return errors.As(err, &maxErr)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, payload any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if payload != nil {
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}
|
||||
}
|
||||
|
||||
func generateToken(size int) (string, error) {
|
||||
buf := make([]byte, size)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
func newSessionStore() *sessionStore {
|
||||
return &sessionStore{data: make(map[string]session)}
|
||||
}
|
||||
|
||||
func (s *sessionStore) Get(token string) (session, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
val, ok := s.data[token]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (s *sessionStore) Set(token string, sess session) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.data[token] = sess
|
||||
}
|
||||
|
||||
func (s *sessionStore) Delete(token string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.data, token)
|
||||
}
|
||||
|
||||
func newRateLimiter(limit int, window time.Duration) *rateLimiter {
|
||||
if limit <= 0 {
|
||||
return &rateLimiter{disabled: true}
|
||||
}
|
||||
return &rateLimiter{
|
||||
limit: limit,
|
||||
window: window,
|
||||
clients: make(map[string]rateState),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rateLimiter) Allow(ip string) bool {
|
||||
if r == nil || r.disabled {
|
||||
return true
|
||||
}
|
||||
now := time.Now()
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
state := r.clients[ip]
|
||||
if state.resetAt.IsZero() || now.After(state.resetAt) {
|
||||
state.resetAt = now.Add(r.window)
|
||||
state.count = 0
|
||||
}
|
||||
if state.count >= r.limit {
|
||||
r.clients[ip] = state
|
||||
return false
|
||||
}
|
||||
state.count++
|
||||
r.clients[ip] = state
|
||||
return true
|
||||
}
|
||||
|
||||
type statusWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *statusWriter) WriteHeader(status int) {
|
||||
w.status = status
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (s *server) logRequest(r *http.Request, status int, start time.Time) {
|
||||
s.log.Info("request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", status,
|
||||
"latency", time.Since(start),
|
||||
"ip", clientIP(r),
|
||||
)
|
||||
}
|
||||
|
||||
func clientIP(r *http.Request) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.Split(xff, ",")
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
@ -18,7 +18,7 @@ xray:
|
||||
enabled: true
|
||||
interval: 5m
|
||||
outputPath: "/usr/local/etc/xray/config.json"
|
||||
templatePath: "account/config/xray.config.template.json"
|
||||
templatePath: "config/xray.config.template.json"
|
||||
validateCommand: []
|
||||
restartCommand:
|
||||
- "systemctl"
|
||||
@ -59,7 +59,7 @@ xray:
|
||||
enabled: false
|
||||
interval: 5m
|
||||
outputPath: "/usr/local/etc/xray/config.json"
|
||||
templatePath: "account/config/xray.config.template.json"
|
||||
templatePath: "config/xray.config.template.json"
|
||||
validateCommand: []
|
||||
restartCommand:
|
||||
- "systemctl"
|
||||
@ -69,7 +69,7 @@ xray:
|
||||
enabled: false
|
||||
interval: 5m
|
||||
outputPath: "/usr/local/etc/xray/config.json"
|
||||
templatePath: "account/config/xray.config.template.json"
|
||||
templatePath: "config/xray.config.template.json"
|
||||
validateCommand: []
|
||||
restartCommand:
|
||||
- "systemctl"
|
||||
@ -156,12 +156,12 @@ type AgentCredential struct {
|
||||
}
|
||||
|
||||
// Load reads the configuration file at the provided path. When path is empty,
|
||||
// it defaults to account/config/account.yaml. If the file does not exist an
|
||||
// it defaults to config/account.yaml. If the file does not exist an
|
||||
// empty configuration is returned.
|
||||
func Load(path string) (*Config, error) {
|
||||
p := path
|
||||
if p == "" {
|
||||
p = filepath.Join("account", "config", "account.yaml")
|
||||
p = filepath.Join("config", "account.yaml")
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(p)
|
||||
8
deploy/caddy/accounts.svc.plus.Caddyfile
Normal file
8
deploy/caddy/accounts.svc.plus.Caddyfile
Normal file
@ -0,0 +1,8 @@
|
||||
accounts.svc.plus {
|
||||
@api path /api/*
|
||||
reverse_proxy @api 127.0.0.1:8080
|
||||
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:3000
|
||||
}
|
||||
}
|
||||
@ -55,7 +55,7 @@ xray:
|
||||
enabled: false
|
||||
interval: 5m
|
||||
outputPath: "/usr/local/etc/xray/config.json"
|
||||
templatePath: "account/config/xray.config.template.json"
|
||||
templatePath: "config/xray.config.template.json"
|
||||
validateCommand: []
|
||||
restartCommand:
|
||||
- "systemctl"
|
||||
|
||||
25
deploy/systemd/accounts-api.service
Normal file
25
deploy/systemd/accounts-api.service
Normal file
@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=accounts.svc.plus API
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=accounts
|
||||
Group=accounts
|
||||
WorkingDirectory=/opt/xcontrol
|
||||
Environment=ACCOUNTS_DB_USER=accounts
|
||||
Environment=ACCOUNTS_DB_PASSWORD=change-me
|
||||
Environment=ACCOUNTS_DB_NAME=accounts
|
||||
Environment=ACCOUNTS_DB_SSLMODE=disable
|
||||
Environment=ACCOUNTS_RATE_LIMIT_RPM=60
|
||||
Environment=ACCOUNTS_SESSION_TTL=24h
|
||||
ExecStart=/opt/xcontrol/accounts-api
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -14,13 +14,13 @@ This document summarizes the new `/api/auth/admin/settings` endpoints for managi
|
||||
|
||||
## Storage Model
|
||||
|
||||
- The permission matrix is stored in the `admin_settings` table. GORM manages the model via `account/internal/model/admin_setting.go` and a dedicated migration script (`sql/20250305-admin-settings.sql`).
|
||||
- The permission matrix is stored in the `admin_settings` table. GORM manages the model via `internal/model/admin_setting.go` and a dedicated migration script (`sql/20250305-admin-settings.sql`).
|
||||
- Each cell records `module_key`, `role`, `enabled`, and a monotonically increasing `version` value. Updates occur inside a single transaction that replaces the existing matrix to guarantee consistency across modules and roles.
|
||||
- The service layer (`account/internal/service/admin_settings.go`) caches the most recent matrix in-memory and invalidates the cache whenever a write occurs or fails due to a version conflict.
|
||||
- The service layer (`internal/service/admin_settings.go`) caches the most recent matrix in-memory and invalidates the cache whenever a write occurs or fails due to a version conflict.
|
||||
|
||||
## Test Coverage
|
||||
|
||||
Integration tests are provided in `account/api/admin_settings_test.go`:
|
||||
Integration tests are provided in `api/admin_settings_test.go`:
|
||||
|
||||
- `TestAdminSettingsReadWrite` exercises a full write followed by a read using the operator role.
|
||||
- `TestAdminSettingsUnauthorized` verifies that callers without an admin/operator role receive `403 Forbidden` responses for both GET and POST.
|
||||
@ -29,5 +29,5 @@ Integration tests are provided in `account/api/admin_settings_test.go`:
|
||||
Run the suite with:
|
||||
|
||||
```bash
|
||||
go test ./account/api -run AdminSettings
|
||||
go test ./api -run AdminSettings
|
||||
```
|
||||
|
||||
@ -4,19 +4,19 @@
|
||||
|
||||
## 1. 配置加载策略
|
||||
|
||||
账号服务入口(`account/cmd/accountsvc/main.go`)会调用 `config.Load` 读取 YAML 配置,并允许通过命令行参数覆盖默认路径。当未提供配置文件时,服务会以零值启动,此时可结合环境变量填充关键字段。
|
||||
账号服务入口(`cmd/accountsvc/main.go`)会调用 `config.Load` 读取 YAML 配置,并允许通过命令行参数覆盖默认路径。当未提供配置文件时,服务会以零值启动,此时可结合环境变量填充关键字段。
|
||||
|
||||
当前推荐的覆盖顺序如下:
|
||||
|
||||
1. **命令行参数**:用于指定配置文件路径或运行模式。
|
||||
2. **配置文件**:默认从 `account/config/account.yaml` 读取,适合提交到仓库或挂载到容器内。
|
||||
2. **配置文件**:默认从 `config/account.yaml` 读取,适合提交到仓库或挂载到容器内。
|
||||
3. **代码默认值**:`config.Config` 结构体中的零值,保证最小可运行。
|
||||
|
||||
> 注:目前服务尚未内置环境变量映射逻辑,如需按环境注入配置,可在部署流程中提前生成 YAML 文件或扩展 `config.Load`。
|
||||
|
||||
## 2. 配置字段参考
|
||||
|
||||
`account/config/config.go` 定义了配置结构,主要包含以下几个部分:
|
||||
`config/config.go` 定义了配置结构,主要包含以下几个部分:
|
||||
|
||||
```yaml
|
||||
log:
|
||||
|
||||
@ -17,11 +17,11 @@
|
||||
```
|
||||
|
||||
2. **准备配置**
|
||||
使用仓库提供的 `account/config/account.yaml`,或根据需要拷贝一份修改端口、数据库连接等字段。
|
||||
使用仓库提供的 `config/account.yaml`,或根据需要拷贝一份修改端口、数据库连接等字段。
|
||||
|
||||
3. **启动服务(HTTP)**
|
||||
```bash
|
||||
go run ./account/cmd/accountsvc --config account/config/account.yaml
|
||||
go run ./cmd/accountsvc --config config/account.yaml
|
||||
```
|
||||
默认监听 `:8080`,可通过 `curl http://127.0.0.1:8080/healthz` 检查服务状态。
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
启动命令保持不变:
|
||||
|
||||
```bash
|
||||
go run ./account/cmd/accountsvc --config /path/to/secure-account.yaml
|
||||
go run ./cmd/accountsvc --config /path/to/secure-account.yaml
|
||||
```
|
||||
|
||||
常见验证步骤:
|
||||
@ -113,7 +113,7 @@ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-subj "/CN=localhost"
|
||||
|
||||
# 更新配置后启动服务
|
||||
ACCOUNT_CONFIG=/tmp/account-secure.yaml go run ./account/cmd/accountsvc --config $ACCOUNT_CONFIG
|
||||
ACCOUNT_CONFIG=/tmp/account-secure.yaml go run ./cmd/accountsvc --config $ACCOUNT_CONFIG
|
||||
|
||||
# 使用 curl 验证 HTTPS(开发环境可加 -k 跳过校验)
|
||||
curl -k https://127.0.0.1:8443/healthz
|
||||
@ -228,7 +228,7 @@ docker compose -f deploy/docker-compose/caddy-stunnel/docker-compose.db.yaml up
|
||||
|
||||
1. **构建镜像(示例)**
|
||||
```bash
|
||||
docker build -t xcontrol/account-service -f deploy/account/Dockerfile .
|
||||
docker build -t xcontrol/account-service -f Dockerfile .
|
||||
```
|
||||
|
||||
2. **运行容器(挂载配置与证书)**
|
||||
@ -237,7 +237,7 @@ docker compose -f deploy/docker-compose/caddy-stunnel/docker-compose.db.yaml up
|
||||
--name account-service \
|
||||
-p 8443:8443 \
|
||||
-p 8080:8080 \
|
||||
-v $(pwd)/account.yaml:/etc/xcontrol/account.yaml \
|
||||
-v $(pwd)/config/account.yaml:/etc/xcontrol/account.yaml \
|
||||
-v $(pwd)/certs:/etc/ssl/xcontrol \
|
||||
xcontrol/account-service \
|
||||
--config /etc/xcontrol/account.yaml
|
||||
@ -278,7 +278,7 @@ docker compose -f deploy/docker-compose/caddy-stunnel/docker-compose.db.yaml up
|
||||
- 在容器或集群层启用网络策略,仅开放必要端口。
|
||||
- 对外提供服务时务必启用 HTTPS,保护登录口令与 TOTP 码。
|
||||
- 对数据库、证书等敏感资源使用最小权限原则,并定期轮换。
|
||||
- 定期回顾 `account/api/api_test.go` 中的场景测试,确保关键登录链路持续可用。
|
||||
- 定期回顾 `api/api_test.go` 中的场景测试,确保关键登录链路持续可用。
|
||||
|
||||
## 9. 数据库备份、迁移与回滚示例
|
||||
|
||||
|
||||
@ -14,15 +14,15 @@
|
||||
|
||||
## 2. 系统架构
|
||||
|
||||
账号服务采用 Go 语言实现,入口位于 `account/cmd/accountsvc/main.go`,默认使用 Gin 框架启动 HTTP 服务并注册 REST API 路由。【F:account/cmd/accountsvc/main.go†L1-L12】
|
||||
账号服务采用 Go 语言实现,入口位于 `cmd/accountsvc/main.go`,默认使用 Gin 框架启动 HTTP 服务并注册 REST API 路由。【F:cmd/accountsvc/main.go†L1-L12】
|
||||
|
||||
核心模块划分如下:
|
||||
|
||||
- `account/api`: 定义 REST API,并实现用户注册、登录、会话维护等业务逻辑。【F:account/api/api.go†L1-L190】
|
||||
- `account/internal/store`: 提供用户数据的读写接口与内存实现,后续可扩展至数据库存储。【F:account/internal/store/store.go†L1-L109】
|
||||
- `account/internal/auth`: 声明可插拔的第三方认证提供方接口,为接入 LDAP/OIDC 等外部系统提供抽象。【F:account/internal/auth/auth.go†L1-L6】
|
||||
- `account/internal/cache`: 预留会话缓存接口,便于集成 Redis 等缓存组件。【F:account/internal/cache/cache.go†L1-L6】
|
||||
- `account/config`: 管理服务配置结构体(当前为空定义,未来将扩展字段)。【F:account/config/config.go†L1-L5】
|
||||
- `api`: 定义 REST API,并实现用户注册、登录、会话维护等业务逻辑。【F:api/api.go†L1-L190】
|
||||
- `internal/store`: 提供用户数据的读写接口与内存实现,后续可扩展至数据库存储。【F:internal/store/store.go†L1-L109】
|
||||
- `internal/auth`: 声明可插拔的第三方认证提供方接口,为接入 LDAP/OIDC 等外部系统提供抽象。【F:internal/auth/auth.go†L1-L6】
|
||||
- `internal/cache`: 预留会话缓存接口,便于集成 Redis 等缓存组件。【F:internal/cache/cache.go†L1-L6】
|
||||
- `config`: 管理服务配置结构体(当前为空定义,未来将扩展字段)。【F:config/config.go†L1-L5】
|
||||
|
||||
内部调用关系示意:
|
||||
|
||||
@ -56,27 +56,27 @@ Gin Router → API Handler → Store / Session Manager → 数据存储
|
||||
### 3.3 用户登录
|
||||
- `POST /v1/login`
|
||||
- 请求体:`{ "email": string, "password": string }`
|
||||
- 功能:校验凭据,通过内存存储读取用户并验证哈希密码,成功后生成 24 小时有效的会话 token。【F:account/api/api.go†L65-L136】
|
||||
- 功能:校验凭据,通过内存存储读取用户并验证哈希密码,成功后生成 24 小时有效的会话 token。【F:api/api.go†L65-L136】
|
||||
|
||||
### 3.4 查询会话
|
||||
- `GET /v1/session`
|
||||
- Header 中提供 `Authorization: Bearer <token>` 或查询参数 `token`。
|
||||
- 功能:校验 token,返回关联用户信息。【F:account/api/api.go†L138-L176】
|
||||
- 功能:校验 token,返回关联用户信息。【F:api/api.go†L138-L176】
|
||||
|
||||
### 3.5 注销会话
|
||||
- `DELETE /v1/session`
|
||||
- Header 或查询参数传入 token,删除内存中的会话记录。【F:account/api/api.go†L178-L190】
|
||||
- Header 或查询参数传入 token,删除内存中的会话记录。【F:api/api.go†L178-L190】
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
当前实现使用内存存储,结构体 `store.User` 定义了最小必要字段:`ID`、`Name`、`Email`、`PasswordHash` 与 `CreatedAt` 时间戳。【F:account/internal/store/store.go†L12-L18】
|
||||
当前实现使用内存存储,结构体 `store.User` 定义了最小必要字段:`ID`、`Name`、`Email`、`PasswordHash` 与 `CreatedAt` 时间戳。【F:internal/store/store.go†L12-L18】
|
||||
|
||||
`memoryStore` 负责提供线程安全的增删查能力,并在创建用户时自动生成 UUID 与 UTC 时间,保证多实例场景中的唯一性。未来替换为数据库时,可在 `Store` 接口的基础上新增实现即可。【F:account/internal/store/store.go†L31-L109】
|
||||
`memoryStore` 负责提供线程安全的增删查能力,并在创建用户时自动生成 UUID 与 UTC 时间,保证多实例场景中的唯一性。未来替换为数据库时,可在 `Store` 接口的基础上新增实现即可。【F:internal/store/store.go†L31-L109】
|
||||
|
||||
## 5. 安全与扩展
|
||||
|
||||
- **密码存储**:使用 `bcrypt` 哈希,防止明文泄露。【F:account/api/api.go†L90-L108】
|
||||
- **会话管理**:会话 token 为 32 字节随机数生成的十六进制字符串,并设置 24 小时过期,过期后自动清理。【F:account/api/api.go†L112-L171】
|
||||
- **密码存储**:使用 `bcrypt` 哈希,防止明文泄露。【F:api/api.go†L90-L108】
|
||||
- **会话管理**:会话 token 为 32 字节随机数生成的十六进制字符串,并设置 24 小时过期,过期后自动清理。【F:api/api.go†L112-L171】
|
||||
- **扩展点**:
|
||||
- 可在 `Store` 接口层新增 PostgreSQL、MySQL 等实现。
|
||||
- 可实现 `auth.Provider` 接口以支持外部身份源认证,再与内部用户绑定。
|
||||
|
||||
@ -101,10 +101,9 @@ CREATE TABLE IF NOT EXISTS sessions (
|
||||
|
||||
## 8. 代码目录规划
|
||||
|
||||
后端代码位于根目录的 `account/` 下:
|
||||
后端代码位于根目录下:
|
||||
|
||||
```
|
||||
account/
|
||||
cmd/accountsvc/main.go # 服务入口
|
||||
api/ # REST 接口
|
||||
config/ # 配置解析
|
||||
@ -117,6 +116,7 @@ account/
|
||||
|
||||
前端目录扩展:
|
||||
|
||||
- `ui/panel/app/`:控制台新增账号模块页面。
|
||||
- `dashboard/app/login/` 与 `dashboard/app/register/`:提供登录/注册页面,登录后根据身份跳转至用户或管理员界面。
|
||||
|
||||
## 7. 部署建议
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
|
||||
### 1.1 HTTP 接口扩展
|
||||
|
||||
仅新增 `POST /api/config/sync`,位于 `account/api` 路由注册:
|
||||
仅新增 `POST /api/config/sync`,位于 `api` 路由注册:
|
||||
|
||||
- **Handler 位置**:`account/api/config_sync.go`(新建文件),由 `api.RegisterRoutes` 中挂载到 `auth` 保护下的子路由组。
|
||||
- **Handler 位置**:`api/config_sync.go`(新建文件),由 `api.RegisterRoutes` 中挂载到 `auth` 保护下的子路由组。
|
||||
- **认证复用**:沿用 `xc_session` Cookie。若桌面端后续需要无 Cookie 调用,可在 `api/auth` 中增加“设备 Token”生成接口,但不影响本次实现。
|
||||
- **请求结构**:
|
||||
```text
|
||||
@ -51,17 +51,17 @@
|
||||
]
|
||||
}
|
||||
```
|
||||
- **重放保护**:服务端在 handler 中校验 `timestamp` ±5 分钟及 `nonce` 是否重复。重放窗口可复用现有 Redis/内存缓存(`account/internal/cache`)。
|
||||
- **重放保护**:服务端在 handler 中校验 `timestamp` ±5 分钟及 `nonce` 是否重复。重放窗口可复用现有 Redis/内存缓存(`internal/cache`)。
|
||||
|
||||
### 1.2 加密模块复用
|
||||
|
||||
- **Key 派发**:在 `account/internal/store/user.go` 中新增 `SyncSecret` 字段(可选),默认读取已有 `users.sync_secret` 列;若列不存在,可在迁移脚本中与 UUID 一致生成,确保最少改动。
|
||||
- **算法实现**:在 `account/internal/crypto/syncpayload`(新目录)封装 `Encrypt(payload []byte, secret []byte)` 与 `Decrypt`,使用 `XChaCha20-Poly1305`。该算法 Go 侧可复用 `golang.org/x/crypto/chacha20poly1305`。
|
||||
- **Key 派发**:在 `internal/store/user.go` 中新增 `SyncSecret` 字段(可选),默认读取已有 `users.sync_secret` 列;若列不存在,可在迁移脚本中与 UUID 一致生成,确保最少改动。
|
||||
- **算法实现**:在 `internal/crypto/syncpayload`(新目录)封装 `Encrypt(payload []byte, secret []byte)` 与 `Decrypt`,使用 `XChaCha20-Poly1305`。该算法 Go 侧可复用 `golang.org/x/crypto/chacha20poly1305`。
|
||||
- **密钥管理**:管理员通过 `GET/POST /api/auth/admin/settings`(已存在)调整“桌面同步”开关;密钥不在接口返回,仅在数据库存储,客户端登录成功后通过 `/api/config/sync` 解包获得配置。
|
||||
|
||||
### 1.3 配置生成复用
|
||||
|
||||
- **数据来源**:继续使用 `account/internal/xrayconfig`。根据 `user.UUID` 作为 tenant_id,从 `Generator.Generate()` 获得完整 JSON。
|
||||
- **数据来源**:继续使用 `internal/xrayconfig`。根据 `user.UUID` 作为 tenant_id,从 `Generator.Generate()` 获得完整 JSON。
|
||||
- **差异化控制**:在 `xrayconfig` 中新增 `HasDesktopPrivilege(uuid string) bool`(读取管理员设置或用户标记),若返回 false,则 handler 返回 `status=NO_PRIVILEGE`,客户端保持现状。
|
||||
- **审计 & 日志**:复用现有的 `logger.WithContext(ctx)`,记录 `uuid`、`deviceFingerprint`、`configVersion`。
|
||||
|
||||
@ -147,7 +147,7 @@ struct SyncResponse {
|
||||
## 5. 安全与运维要点
|
||||
|
||||
- **最小数据面**:所有敏感字段都封装在加密包内,URL 与 Header 仅携带基础信息(Cookie)。
|
||||
- **限流**:继续复用 `account/api/middleware/ratelimit`(若已有)或在 handler 中增加 per-device 限流。
|
||||
- **限流**:继续复用 `api/middleware/ratelimit`(若已有)或在 handler 中增加 per-device 限流。
|
||||
- **审计**:在服务端日志中记录 `uuid`、`deviceFingerprint` hash、`status`,便于定位问题而不过度存储。
|
||||
- **滚动升级**:版本字段可确保前后端同时升级;旧客户端仍可解析 version=1。
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Cloud-Neutral 子域统一命名迁移规划
|
||||
|
||||
## 1. 背景与目标
|
||||
现有 Cloud-Neutral 生态的各子系统分别部署在 `svc.plus` 旗下的多个子域中,例如账号中心使用 `accounts.svc.plus`,控制台与公共站点共用 `www.svc.plus`,API 入口映射到 `rag-server.svc.plus` 等配置文件仍保留旧命名。【F:account/config/account.yaml†L10-L23】【F:dashboard/config/runtime-service-config.yaml†L1-L27】【F:deploy/openresty/rag-server.svc.plus.conf†L1-L69】
|
||||
现有 Cloud-Neutral 生态的各子系统分别部署在 `svc.plus` 旗下的多个子域中,例如账号中心使用 `accounts.svc.plus`,控制台与公共站点共用 `www.svc.plus`,API 入口映射到 `rag-server.svc.plus` 等配置文件仍保留旧命名。【F:config/account.yaml†L10-L23】【F:dashboard/config/runtime-service-config.yaml†L1-L27】【F:deploy/openresty/rag-server.svc.plus.conf†L1-L69】
|
||||
|
||||
为降低证书、路由和跨域配置的复杂度,计划按照统一命名体系:
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
## 2. 现状盘点
|
||||
|
||||
### 2.1 用户中心(accounts.svc.plus)
|
||||
- Go 账号服务 `account/` 通过 `server.publicUrl` 指向 `https://accounts.svc.plus` 并在 `allowedOrigins` 中列出旧前端域名列表。【F:account/config/account.yaml†L6-L23】
|
||||
- Go 账号服务 `` 通过 `server.publicUrl` 指向 `https://accounts.svc.plus` 并在 `allowedOrigins` 中列出旧前端域名列表。【F:config/account.yaml†L6-L23】
|
||||
- 多份文档(例如 `docs/account-svc-plus.md`、`docs/account-xstream-desktop-integration.md`)阐述账号服务部署契约,需要同步调整对控制台和下载域的描述。【F:docs/account-svc-plus.md†L1-L37】【F:docs/account-xstream-desktop-integration.md†L1-L52】
|
||||
|
||||
### 2.2 控制台(console.svc.plus)
|
||||
|
||||
@ -19,5 +19,5 @@ Related source files:
|
||||
`NEXT_PUBLIC_REGISTER_URL` and submits the form to that URL.
|
||||
- `dashboard/app/api/auth/register/route.ts` – handles the
|
||||
`/api/auth/register` requests and forwards them to the account service.
|
||||
- `account/api/api.go` – exposes the `POST /api/auth/register` handler inside the
|
||||
- `api/api.go` – exposes the `POST /api/auth/register` handler inside the
|
||||
account service.
|
||||
|
||||
@ -119,11 +119,11 @@ func SyncXrayClients(ctx context.Context, db *sql.DB, fs afero.Fs, runner comman
|
||||
|
||||
The concrete implementation should wire in dependency-injected collaborators for database access, filesystem operations, validation, and command execution to simplify testing.
|
||||
|
||||
The initial `Config Generator` module lives at `account/internal/xrayconfig`. It embeds the default Xray template directly in
|
||||
The initial `Config Generator` module lives at `internal/xrayconfig`. It embeds the default Xray template directly in
|
||||
the binary, overwrites the VLESS user array with the current database view (setting `flow` to `xtls-rprx-vision` unless callers
|
||||
request a different value), and writes the merged document to `/usr/local/etc/xray/config.json` using an atomic rename so that Xray always
|
||||
observes a complete file. Agent deployments can still point `xray.sync.templatePath` at a local template (for example
|
||||
`account/config/xray.config.template.json`) when they need to override the embedded definition.
|
||||
`config/xray.config.template.json`) when they need to override the embedded definition.
|
||||
|
||||
### Periodic Synchronization
|
||||
|
||||
@ -134,7 +134,7 @@ now exposes a background syncer that periodically rebuilds the Xray config by:
|
||||
2. Regenerating the config with the existing generator.
|
||||
3. Optionally validating the JSON and restarting Xray using commands defined in configuration.
|
||||
|
||||
The cadence and command hooks are controlled through `account/config/account.yaml`:
|
||||
The cadence and command hooks are controlled through `config/account.yaml`:
|
||||
|
||||
```yaml
|
||||
xray:
|
||||
|
||||
86
go.mod
86
go.mod
@ -1,83 +1,61 @@
|
||||
module xcontrol
|
||||
module account
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.8
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.6.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang-migrate/migrate/v4 v4.19.0
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.5
|
||||
github.com/pgvector/pgvector-go v0.3.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/pkg/sftp v1.13.10
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/redis/go-redis/v9 v9.12.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/net v0.42.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
golang.org/x/crypto v0.45.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.4
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.11.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
)
|
||||
|
||||
266
go.sum
266
go.sum
@ -1,136 +1,72 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ=
|
||||
entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
|
||||
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg=
|
||||
github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-pg/pg/v10 v10.11.0 h1:CMKJqLgTrfpE/aOVeLdybezR2om071Vh38OLZjsyMI0=
|
||||
github.com/go-pg/pg/v10 v10.11.0/go.mod h1:4BpHRoxE61y4Onpof3x1a2SQvi9c+q1dJnrNdMjsroA=
|
||||
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
|
||||
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
@ -140,125 +76,71 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pgvector/pgvector-go v0.3.0 h1:Ij+Yt78R//uYqs3Zk35evZFvr+G0blW0OUN+Q2D1RWc=
|
||||
github.com/pgvector/pgvector-go v0.3.0/go.mod h1:duFy+PXWfW7QQd5ibqutBO4GxLsUZ9RVXhFZGIBsWSA=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
||||
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/redis/go-redis/v9 v9.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU=
|
||||
github.com/redis/go-redis/v9 v9.12.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
||||
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ=
|
||||
github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.12 h1:m/CM1UfOkoBTglGO5CUTKnIKKOApOYxkcP2qn0F9tJk=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.12/go.mod h1:Ij6WIxQILxLlL2frUBxUBOZJtLElD2QQNDcu/PWDHTc=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.12 h1:3rRWB1GK0psTJrHwxzNfEij2MLibggiLdTqjTtfHc1w=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.12/go.mod h1:ssYUP+qwSEgeDDS1xm2XBip9el1y9Mi5mTAvLoiADLM=
|
||||
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
|
||||
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
||||
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
# 用于清理指定文件的历史提交记录,但保留当前版本
|
||||
#
|
||||
# 使用示例:
|
||||
# ./clean_git_history.sh account/sql/schema_pglogical_region.sql
|
||||
# ./clean_git_history.sh sql/schema_pglogical_region.sql
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@ -37,7 +37,7 @@ log_error() {
|
||||
# 配置路径
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DASHBOARD_CONFIG="$ROOT_DIR/dashboard-fresh/config/runtime-service-config.base.yaml"
|
||||
ACCOUNT_CONFIG="$ROOT_DIR/account/config/account.yaml"
|
||||
ACCOUNT_CONFIG="$ROOT_DIR/config/account.yaml"
|
||||
RAG_CONFIG="$ROOT_DIR/rag-server/config/server.yaml"
|
||||
MANUAL_FILE="$ROOT_DIR/TOKEN_AUTH_MANUAL.md"
|
||||
|
||||
|
||||
7
sql/accounts_api_schema.sql
Normal file
7
sql/accounts_api_schema.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- Minimal schema for accounts.svc.plus API.
|
||||
CREATE TABLE IF NOT EXISTS public.users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
@ -133,7 +133,7 @@ All test scripts generate JSON results with the following structure:
|
||||
## Configuration
|
||||
|
||||
No configuration files are required. The scripts automatically detect:
|
||||
- Service directories (`account/`, `rag-server/`, `dashboard-fresh/`)
|
||||
- Service directories (`/`, `rag-server/`, `dashboard-fresh/`)
|
||||
- Configuration files in each service
|
||||
- Authentication implementations
|
||||
|
||||
|
||||
@ -159,8 +159,8 @@ echo "Starting configuration validation tests..."
|
||||
echo ""
|
||||
|
||||
# Account service
|
||||
validate_configs "account" "account/config"
|
||||
validate_auth_config "account" "account/internal/auth/token_service.go"
|
||||
validate_configs "account" "config"
|
||||
validate_auth_config "account" "internal/auth/token_service.go"
|
||||
|
||||
# RAG server
|
||||
validate_configs "rag-server" "rag-server/config"
|
||||
|
||||
@ -179,7 +179,7 @@ echo ""
|
||||
# test_endpoint "account" "8080" "/health"
|
||||
|
||||
# Instead, just validate that the binaries exist
|
||||
if [ -f "account/xcontrol-account" ]; then
|
||||
if [ -f "xcontrol-account" ]; then
|
||||
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||||
echo "✓ Account binary found"
|
||||
echo "," >> "$RESULTS_FILE"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user