From ee6e1a6363afafba51e81e6663300568411de4d9 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Fri, 16 Jan 2026 16:15:23 +0800 Subject: [PATCH] feat: move account service to repo root # Conflicts: # account/Makefile # account/go.mod # docs/account-admin-settings.md # docs/account-svc-plus.md --- account/Dockerfile => Dockerfile | 0 Dockerfile.accounts-api | 29 + Makefile | 539 ++++++++---------- Makefile.account | 333 +++++++++++ account/Makefile | 294 ---------- account/go.mod | 64 --- account/go.sum | 146 ----- {account/api => api}/admin_agents.go | 0 {account/api => api}/admin_settings_test.go | 0 {account/api => api}/admin_users_metrics.go | 0 {account/api => api}/api.go | 0 {account/api => api}/api_test.go | 0 {account/api => api}/config_sync.go | 0 {account/api => api}/email.go | 0 cmd/accountsapi/main.go | 482 ++++++++++++++++ {account/cmd => cmd}/accountsvc/main.go | 0 {account/cmd => cmd}/createadmin/main.go | 0 {account/cmd => cmd}/migratectl/main.go | 0 {account/cmd => cmd}/syncctl/main.go | 0 {account/config => config}/account-agent.yaml | 2 +- .../config => config}/account-server.yaml | 2 +- {account/config => config}/account.yaml | 2 +- {account/config => config}/config.go | 4 +- {account/config => config}/sync.example.yaml | 0 {account/config => config}/sync.yaml | 0 .../xray.config.template.json | 0 deploy/caddy/accounts.svc.plus.Caddyfile | 8 + deploy/docker-compose/config/account.yaml | 2 +- deploy/systemd/accounts-api.service | 25 + docs/account-admin-settings.md | 8 +- docs/account-service-configuration.md | 6 +- docs/account-service-deployment.md | 14 +- docs/account-service-design.md | 26 +- docs/account-svc-plus.md | 4 +- docs/account-xstream-desktop-integration.md | 14 +- docs/cloud-neutral-domain-migration-plan.md | 4 +- docs/troubleshooting/register-404.md | 2 +- docs/xray-single-port-multi-user.md | 6 +- account/entrypoint.sh => entrypoint.sh | 0 go.mod | 86 ++- go.sum | 266 +++------ .../internal => internal}/agentmode/client.go | 0 .../internal => internal}/agentmode/runner.go | 0 .../agentmode/source_http.go | 0 .../agentmode/tracker.go | 0 .../internal => internal}/agentproto/types.go | 0 .../agentserver/registry.go | 0 .../agentserver/registry_test.go | 0 .../internal => internal}/auth/middleware.go | 0 .../auth/token_service.go | 0 {account/internal => internal}/cache/cache.go | 0 .../internal => internal}/mailer/mailer.go | 0 .../mailer/mailer_test.go | 0 .../internal => internal}/migrate/checker.go | 0 .../internal => internal}/migrate/cleaner.go | 0 {account/internal => internal}/migrate/db.go | 0 .../internal => internal}/migrate/runner.go | 0 .../internal => internal}/migrate/snapshot.go | 0 .../internal => internal}/migrate/transfer.go | 0 .../internal => internal}/migrate/verifier.go | 0 .../model/admin_setting.go | 0 .../service/admin_settings.go | 0 .../service/user_metrics.go | 0 .../service/user_metrics_test.go | 0 .../internal => internal}/store/postgres.go | 0 .../store/postgres_test.go | 0 {account/internal => internal}/store/store.go | 0 .../internal => internal}/syncer/config.go | 0 .../internal => internal}/syncer/syncer.go | 0 {account/internal => internal}/utils/diff.go | 0 {account/internal => internal}/utils/exec.go | 0 .../xrayconfig/definition.go | 0 .../xrayconfig/generator.go | 0 .../xrayconfig/generator_test.go | 0 .../xrayconfig/source_gorm.go | 0 .../xrayconfig/source_gorm_test.go | 0 .../xrayconfig/syncer.go | 0 .../xrayconfig/syncer_test.go | 0 .../xrayconfig/template_server.json | 0 .../xrayconfig/templates.go | 0 scripts/clean_git_history.sh | 2 +- .../setup_postgres_local.sh | 0 scripts/update_token_auth.sh | 2 +- sql/accounts_api_schema.sql | 7 + tests/README.md | 2 +- tests/dry_run_test.sh | 4 +- tests/local_test.sh | 2 +- 87 files changed, 1294 insertions(+), 1093 deletions(-) rename account/Dockerfile => Dockerfile (100%) create mode 100644 Dockerfile.accounts-api create mode 100644 Makefile.account delete mode 100644 account/Makefile delete mode 100644 account/go.mod delete mode 100644 account/go.sum rename {account/api => api}/admin_agents.go (100%) rename {account/api => api}/admin_settings_test.go (100%) rename {account/api => api}/admin_users_metrics.go (100%) rename {account/api => api}/api.go (100%) rename {account/api => api}/api_test.go (100%) rename {account/api => api}/config_sync.go (100%) rename {account/api => api}/email.go (100%) create mode 100644 cmd/accountsapi/main.go rename {account/cmd => cmd}/accountsvc/main.go (100%) rename {account/cmd => cmd}/createadmin/main.go (100%) rename {account/cmd => cmd}/migratectl/main.go (100%) rename {account/cmd => cmd}/syncctl/main.go (100%) rename {account/config => config}/account-agent.yaml (87%) rename {account/config => config}/account-server.yaml (96%) rename {account/config => config}/account.yaml (96%) rename {account/config => config}/config.go (97%) rename {account/config => config}/sync.example.yaml (100%) rename {account/config => config}/sync.yaml (100%) rename {account/config => config}/xray.config.template.json (100%) create mode 100644 deploy/caddy/accounts.svc.plus.Caddyfile create mode 100644 deploy/systemd/accounts-api.service rename account/entrypoint.sh => entrypoint.sh (100%) rename {account/internal => internal}/agentmode/client.go (100%) rename {account/internal => internal}/agentmode/runner.go (100%) rename {account/internal => internal}/agentmode/source_http.go (100%) rename {account/internal => internal}/agentmode/tracker.go (100%) rename {account/internal => internal}/agentproto/types.go (100%) rename {account/internal => internal}/agentserver/registry.go (100%) rename {account/internal => internal}/agentserver/registry_test.go (100%) rename {account/internal => internal}/auth/middleware.go (100%) rename {account/internal => internal}/auth/token_service.go (100%) rename {account/internal => internal}/cache/cache.go (100%) rename {account/internal => internal}/mailer/mailer.go (100%) rename {account/internal => internal}/mailer/mailer_test.go (100%) rename {account/internal => internal}/migrate/checker.go (100%) rename {account/internal => internal}/migrate/cleaner.go (100%) rename {account/internal => internal}/migrate/db.go (100%) rename {account/internal => internal}/migrate/runner.go (100%) rename {account/internal => internal}/migrate/snapshot.go (100%) rename {account/internal => internal}/migrate/transfer.go (100%) rename {account/internal => internal}/migrate/verifier.go (100%) rename {account/internal => internal}/model/admin_setting.go (100%) rename {account/internal => internal}/service/admin_settings.go (100%) rename {account/internal => internal}/service/user_metrics.go (100%) rename {account/internal => internal}/service/user_metrics_test.go (100%) rename {account/internal => internal}/store/postgres.go (100%) rename {account/internal => internal}/store/postgres_test.go (100%) rename {account/internal => internal}/store/store.go (100%) rename {account/internal => internal}/syncer/config.go (100%) rename {account/internal => internal}/syncer/syncer.go (100%) rename {account/internal => internal}/utils/diff.go (100%) rename {account/internal => internal}/utils/exec.go (100%) rename {account/internal => internal}/xrayconfig/definition.go (100%) rename {account/internal => internal}/xrayconfig/generator.go (100%) rename {account/internal => internal}/xrayconfig/generator_test.go (100%) rename {account/internal => internal}/xrayconfig/source_gorm.go (100%) rename {account/internal => internal}/xrayconfig/source_gorm_test.go (100%) rename {account/internal => internal}/xrayconfig/syncer.go (100%) rename {account/internal => internal}/xrayconfig/syncer_test.go (100%) rename {account/internal => internal}/xrayconfig/template_server.json (100%) rename {account/internal => internal}/xrayconfig/templates.go (100%) rename {account/scripts => scripts}/setup_postgres_local.sh (100%) create mode 100644 sql/accounts_api_schema.sql diff --git a/account/Dockerfile b/Dockerfile similarity index 100% rename from account/Dockerfile rename to Dockerfile diff --git a/Dockerfile.accounts-api b/Dockerfile.accounts-api new file mode 100644 index 0000000..09ebaf7 --- /dev/null +++ b/Dockerfile.accounts-api @@ -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"] diff --git a/Makefile b/Makefile index 30c88f3..2541559 100644 --- a/Makefile +++ b/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' '' \ - '' \ - '' \ - ' Labelhomebrew.mxcl.openresty' \ - ' ProgramArguments' \ - ' ' \ - ' /opt/homebrew/openresty/nginx/sbin/nginx' \ - ' -g' \ - ' daemon off;' \ - ' ' \ - ' RunAtLoad' \ - '' \ - > ~/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 diff --git a/Makefile.account b/Makefile.account new file mode 100644 index 0000000..30c88f3 --- /dev/null +++ b/Makefile.account @@ -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' '' \ + '' \ + '' \ + ' Labelhomebrew.mxcl.openresty' \ + ' ProgramArguments' \ + ' ' \ + ' /opt/homebrew/openresty/nginx/sbin/nginx' \ + ' -g' \ + ' daemon off;' \ + ' ' \ + ' RunAtLoad' \ + '' \ + > ~/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 diff --git a/account/Makefile b/account/Makefile deleted file mode 100644 index a7de010..0000000 --- a/account/Makefile +++ /dev/null @@ -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 diff --git a/account/go.mod b/account/go.mod deleted file mode 100644 index 2232179..0000000 --- a/account/go.mod +++ /dev/null @@ -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 => .. diff --git a/account/go.sum b/account/go.sum deleted file mode 100644 index 716d944..0000000 --- a/account/go.sum +++ /dev/null @@ -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= diff --git a/account/api/admin_agents.go b/api/admin_agents.go similarity index 100% rename from account/api/admin_agents.go rename to api/admin_agents.go diff --git a/account/api/admin_settings_test.go b/api/admin_settings_test.go similarity index 100% rename from account/api/admin_settings_test.go rename to api/admin_settings_test.go diff --git a/account/api/admin_users_metrics.go b/api/admin_users_metrics.go similarity index 100% rename from account/api/admin_users_metrics.go rename to api/admin_users_metrics.go diff --git a/account/api/api.go b/api/api.go similarity index 100% rename from account/api/api.go rename to api/api.go diff --git a/account/api/api_test.go b/api/api_test.go similarity index 100% rename from account/api/api_test.go rename to api/api_test.go diff --git a/account/api/config_sync.go b/api/config_sync.go similarity index 100% rename from account/api/config_sync.go rename to api/config_sync.go diff --git a/account/api/email.go b/api/email.go similarity index 100% rename from account/api/email.go rename to api/email.go diff --git a/cmd/accountsapi/main.go b/cmd/accountsapi/main.go new file mode 100644 index 0000000..cc8d2bc --- /dev/null +++ b/cmd/accountsapi/main.go @@ -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 +} diff --git a/account/cmd/accountsvc/main.go b/cmd/accountsvc/main.go similarity index 100% rename from account/cmd/accountsvc/main.go rename to cmd/accountsvc/main.go diff --git a/account/cmd/createadmin/main.go b/cmd/createadmin/main.go similarity index 100% rename from account/cmd/createadmin/main.go rename to cmd/createadmin/main.go diff --git a/account/cmd/migratectl/main.go b/cmd/migratectl/main.go similarity index 100% rename from account/cmd/migratectl/main.go rename to cmd/migratectl/main.go diff --git a/account/cmd/syncctl/main.go b/cmd/syncctl/main.go similarity index 100% rename from account/cmd/syncctl/main.go rename to cmd/syncctl/main.go diff --git a/account/config/account-agent.yaml b/config/account-agent.yaml similarity index 87% rename from account/config/account-agent.yaml rename to config/account-agent.yaml index 33fe7c4..f0b3552 100644 --- a/account/config/account-agent.yaml +++ b/config/account-agent.yaml @@ -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" diff --git a/account/config/account-server.yaml b/config/account-server.yaml similarity index 96% rename from account/config/account-server.yaml rename to config/account-server.yaml index dbf1e80..3c88ad4 100644 --- a/account/config/account-server.yaml +++ b/config/account-server.yaml @@ -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" diff --git a/account/config/account.yaml b/config/account.yaml similarity index 96% rename from account/config/account.yaml rename to config/account.yaml index f2e63ac..31ec455 100644 --- a/account/config/account.yaml +++ b/config/account.yaml @@ -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" diff --git a/account/config/config.go b/config/config.go similarity index 97% rename from account/config/config.go rename to config/config.go index 1b30369..e8aae98 100644 --- a/account/config/config.go +++ b/config/config.go @@ -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) diff --git a/account/config/sync.example.yaml b/config/sync.example.yaml similarity index 100% rename from account/config/sync.example.yaml rename to config/sync.example.yaml diff --git a/account/config/sync.yaml b/config/sync.yaml similarity index 100% rename from account/config/sync.yaml rename to config/sync.yaml diff --git a/account/config/xray.config.template.json b/config/xray.config.template.json similarity index 100% rename from account/config/xray.config.template.json rename to config/xray.config.template.json diff --git a/deploy/caddy/accounts.svc.plus.Caddyfile b/deploy/caddy/accounts.svc.plus.Caddyfile new file mode 100644 index 0000000..1f81ef6 --- /dev/null +++ b/deploy/caddy/accounts.svc.plus.Caddyfile @@ -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 + } +} diff --git a/deploy/docker-compose/config/account.yaml b/deploy/docker-compose/config/account.yaml index e87ff5b..0463c50 100644 --- a/deploy/docker-compose/config/account.yaml +++ b/deploy/docker-compose/config/account.yaml @@ -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" diff --git a/deploy/systemd/accounts-api.service b/deploy/systemd/accounts-api.service new file mode 100644 index 0000000..a52de34 --- /dev/null +++ b/deploy/systemd/accounts-api.service @@ -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=scrubbed +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 diff --git a/docs/account-admin-settings.md b/docs/account-admin-settings.md index f160427..dccfe08 100644 --- a/docs/account-admin-settings.md +++ b/docs/account-admin-settings.md @@ -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 ``` diff --git a/docs/account-service-configuration.md b/docs/account-service-configuration.md index b9914c5..cd11e59 100644 --- a/docs/account-service-configuration.md +++ b/docs/account-service-configuration.md @@ -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: diff --git a/docs/account-service-deployment.md b/docs/account-service-deployment.md index 5384f8e..debf3b5 100644 --- a/docs/account-service-deployment.md +++ b/docs/account-service-deployment.md @@ -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. 数据库备份、迁移与回滚示例 diff --git a/docs/account-service-design.md b/docs/account-service-design.md index c220593..1811e44 100644 --- a/docs/account-service-design.md +++ b/docs/account-service-design.md @@ -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,返回关联用户信息。【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` 接口以支持外部身份源认证,再与内部用户绑定。 diff --git a/docs/account-svc-plus.md b/docs/account-svc-plus.md index 4643657..39590bd 100644 --- a/docs/account-svc-plus.md +++ b/docs/account-svc-plus.md @@ -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. 部署建议 diff --git a/docs/account-xstream-desktop-integration.md b/docs/account-xstream-desktop-integration.md index 1525e85..6ec5a3a 100644 --- a/docs/account-xstream-desktop-integration.md +++ b/docs/account-xstream-desktop-integration.md @@ -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。 diff --git a/docs/cloud-neutral-domain-migration-plan.md b/docs/cloud-neutral-domain-migration-plan.md index 5e5b991..ea53afb 100644 --- a/docs/cloud-neutral-domain-migration-plan.md +++ b/docs/cloud-neutral-domain-migration-plan.md @@ -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) diff --git a/docs/troubleshooting/register-404.md b/docs/troubleshooting/register-404.md index 362e25d..cc2e0e0 100644 --- a/docs/troubleshooting/register-404.md +++ b/docs/troubleshooting/register-404.md @@ -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. diff --git a/docs/xray-single-port-multi-user.md b/docs/xray-single-port-multi-user.md index 6eb831c..0f1e865 100644 --- a/docs/xray-single-port-multi-user.md +++ b/docs/xray-single-port-multi-user.md @@ -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: diff --git a/account/entrypoint.sh b/entrypoint.sh similarity index 100% rename from account/entrypoint.sh rename to entrypoint.sh diff --git a/go.mod b/go.mod index f555d79..7846cd1 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 9d0cf6e..716d944 100644 --- a/go.sum +++ b/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= diff --git a/account/internal/agentmode/client.go b/internal/agentmode/client.go similarity index 100% rename from account/internal/agentmode/client.go rename to internal/agentmode/client.go diff --git a/account/internal/agentmode/runner.go b/internal/agentmode/runner.go similarity index 100% rename from account/internal/agentmode/runner.go rename to internal/agentmode/runner.go diff --git a/account/internal/agentmode/source_http.go b/internal/agentmode/source_http.go similarity index 100% rename from account/internal/agentmode/source_http.go rename to internal/agentmode/source_http.go diff --git a/account/internal/agentmode/tracker.go b/internal/agentmode/tracker.go similarity index 100% rename from account/internal/agentmode/tracker.go rename to internal/agentmode/tracker.go diff --git a/account/internal/agentproto/types.go b/internal/agentproto/types.go similarity index 100% rename from account/internal/agentproto/types.go rename to internal/agentproto/types.go diff --git a/account/internal/agentserver/registry.go b/internal/agentserver/registry.go similarity index 100% rename from account/internal/agentserver/registry.go rename to internal/agentserver/registry.go diff --git a/account/internal/agentserver/registry_test.go b/internal/agentserver/registry_test.go similarity index 100% rename from account/internal/agentserver/registry_test.go rename to internal/agentserver/registry_test.go diff --git a/account/internal/auth/middleware.go b/internal/auth/middleware.go similarity index 100% rename from account/internal/auth/middleware.go rename to internal/auth/middleware.go diff --git a/account/internal/auth/token_service.go b/internal/auth/token_service.go similarity index 100% rename from account/internal/auth/token_service.go rename to internal/auth/token_service.go diff --git a/account/internal/cache/cache.go b/internal/cache/cache.go similarity index 100% rename from account/internal/cache/cache.go rename to internal/cache/cache.go diff --git a/account/internal/mailer/mailer.go b/internal/mailer/mailer.go similarity index 100% rename from account/internal/mailer/mailer.go rename to internal/mailer/mailer.go diff --git a/account/internal/mailer/mailer_test.go b/internal/mailer/mailer_test.go similarity index 100% rename from account/internal/mailer/mailer_test.go rename to internal/mailer/mailer_test.go diff --git a/account/internal/migrate/checker.go b/internal/migrate/checker.go similarity index 100% rename from account/internal/migrate/checker.go rename to internal/migrate/checker.go diff --git a/account/internal/migrate/cleaner.go b/internal/migrate/cleaner.go similarity index 100% rename from account/internal/migrate/cleaner.go rename to internal/migrate/cleaner.go diff --git a/account/internal/migrate/db.go b/internal/migrate/db.go similarity index 100% rename from account/internal/migrate/db.go rename to internal/migrate/db.go diff --git a/account/internal/migrate/runner.go b/internal/migrate/runner.go similarity index 100% rename from account/internal/migrate/runner.go rename to internal/migrate/runner.go diff --git a/account/internal/migrate/snapshot.go b/internal/migrate/snapshot.go similarity index 100% rename from account/internal/migrate/snapshot.go rename to internal/migrate/snapshot.go diff --git a/account/internal/migrate/transfer.go b/internal/migrate/transfer.go similarity index 100% rename from account/internal/migrate/transfer.go rename to internal/migrate/transfer.go diff --git a/account/internal/migrate/verifier.go b/internal/migrate/verifier.go similarity index 100% rename from account/internal/migrate/verifier.go rename to internal/migrate/verifier.go diff --git a/account/internal/model/admin_setting.go b/internal/model/admin_setting.go similarity index 100% rename from account/internal/model/admin_setting.go rename to internal/model/admin_setting.go diff --git a/account/internal/service/admin_settings.go b/internal/service/admin_settings.go similarity index 100% rename from account/internal/service/admin_settings.go rename to internal/service/admin_settings.go diff --git a/account/internal/service/user_metrics.go b/internal/service/user_metrics.go similarity index 100% rename from account/internal/service/user_metrics.go rename to internal/service/user_metrics.go diff --git a/account/internal/service/user_metrics_test.go b/internal/service/user_metrics_test.go similarity index 100% rename from account/internal/service/user_metrics_test.go rename to internal/service/user_metrics_test.go diff --git a/account/internal/store/postgres.go b/internal/store/postgres.go similarity index 100% rename from account/internal/store/postgres.go rename to internal/store/postgres.go diff --git a/account/internal/store/postgres_test.go b/internal/store/postgres_test.go similarity index 100% rename from account/internal/store/postgres_test.go rename to internal/store/postgres_test.go diff --git a/account/internal/store/store.go b/internal/store/store.go similarity index 100% rename from account/internal/store/store.go rename to internal/store/store.go diff --git a/account/internal/syncer/config.go b/internal/syncer/config.go similarity index 100% rename from account/internal/syncer/config.go rename to internal/syncer/config.go diff --git a/account/internal/syncer/syncer.go b/internal/syncer/syncer.go similarity index 100% rename from account/internal/syncer/syncer.go rename to internal/syncer/syncer.go diff --git a/account/internal/utils/diff.go b/internal/utils/diff.go similarity index 100% rename from account/internal/utils/diff.go rename to internal/utils/diff.go diff --git a/account/internal/utils/exec.go b/internal/utils/exec.go similarity index 100% rename from account/internal/utils/exec.go rename to internal/utils/exec.go diff --git a/account/internal/xrayconfig/definition.go b/internal/xrayconfig/definition.go similarity index 100% rename from account/internal/xrayconfig/definition.go rename to internal/xrayconfig/definition.go diff --git a/account/internal/xrayconfig/generator.go b/internal/xrayconfig/generator.go similarity index 100% rename from account/internal/xrayconfig/generator.go rename to internal/xrayconfig/generator.go diff --git a/account/internal/xrayconfig/generator_test.go b/internal/xrayconfig/generator_test.go similarity index 100% rename from account/internal/xrayconfig/generator_test.go rename to internal/xrayconfig/generator_test.go diff --git a/account/internal/xrayconfig/source_gorm.go b/internal/xrayconfig/source_gorm.go similarity index 100% rename from account/internal/xrayconfig/source_gorm.go rename to internal/xrayconfig/source_gorm.go diff --git a/account/internal/xrayconfig/source_gorm_test.go b/internal/xrayconfig/source_gorm_test.go similarity index 100% rename from account/internal/xrayconfig/source_gorm_test.go rename to internal/xrayconfig/source_gorm_test.go diff --git a/account/internal/xrayconfig/syncer.go b/internal/xrayconfig/syncer.go similarity index 100% rename from account/internal/xrayconfig/syncer.go rename to internal/xrayconfig/syncer.go diff --git a/account/internal/xrayconfig/syncer_test.go b/internal/xrayconfig/syncer_test.go similarity index 100% rename from account/internal/xrayconfig/syncer_test.go rename to internal/xrayconfig/syncer_test.go diff --git a/account/internal/xrayconfig/template_server.json b/internal/xrayconfig/template_server.json similarity index 100% rename from account/internal/xrayconfig/template_server.json rename to internal/xrayconfig/template_server.json diff --git a/account/internal/xrayconfig/templates.go b/internal/xrayconfig/templates.go similarity index 100% rename from account/internal/xrayconfig/templates.go rename to internal/xrayconfig/templates.go diff --git a/scripts/clean_git_history.sh b/scripts/clean_git_history.sh index 0df8b6d..6b0ba82 100644 --- a/scripts/clean_git_history.sh +++ b/scripts/clean_git_history.sh @@ -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 diff --git a/account/scripts/setup_postgres_local.sh b/scripts/setup_postgres_local.sh similarity index 100% rename from account/scripts/setup_postgres_local.sh rename to scripts/setup_postgres_local.sh diff --git a/scripts/update_token_auth.sh b/scripts/update_token_auth.sh index f81e055..39b28ca 100755 --- a/scripts/update_token_auth.sh +++ b/scripts/update_token_auth.sh @@ -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" diff --git a/sql/accounts_api_schema.sql b/sql/accounts_api_schema.sql new file mode 100644 index 0000000..0007fe8 --- /dev/null +++ b/sql/accounts_api_schema.sql @@ -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() +); diff --git a/tests/README.md b/tests/README.md index b4f8e91..0643582 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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 diff --git a/tests/dry_run_test.sh b/tests/dry_run_test.sh index b4874ee..6a130f5 100755 --- a/tests/dry_run_test.sh +++ b/tests/dry_run_test.sh @@ -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" diff --git a/tests/local_test.sh b/tests/local_test.sh index 62383a9..385c179 100755 --- a/tests/local_test.sh +++ b/tests/local_test.sh @@ -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"