commit 34001a4296c88ab11135a14074a8601b535eabb6 Author: 张斌 <259278618@qq.com> Date: Thu Jun 25 09:41:16 2026 +0800 初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1b7204 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/* diff --git a/cluster/postgres/standby-entrypoint.sh b/cluster/postgres/standby-entrypoint.sh new file mode 100755 index 0000000..e80e11f --- /dev/null +++ b/cluster/postgres/standby-entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e + +# PostgreSQL standby bootstrap entrypoint +# If data dir is empty, pull base backup from primary, then hand off to postgres entrypoint + +PRIMARY_HOST="${PG_PRIMARY_HOST:-host1}" +PRIMARY_PORT="${PG_PRIMARY_PORT:-5432}" +REPL_PASSWORD="${REPL_PASSWORD:-Bjang09@686-Repl}" +PGDATA="/var/lib/postgresql/data" + +if [ ! -s "$PGDATA/PG_VERSION" ]; then + echo "[standby] Data directory empty, bootstrapping from primary $PRIMARY_HOST:$PRIMARY_PORT" + + # Wait for primary to accept connections + for i in $(seq 1 30); do + if PGPASSWORD="$REPL_PASSWORD" pg_isready -h "$PRIMARY_HOST" -p "$PRIMARY_PORT" -U replicator >/dev/null 2>&1; then + break + fi + echo "[standby] Waiting for primary to be ready (attempt $i/30)..." + sleep 2 + done + + # Pull base backup from primary + echo "[standby] Running pg_basebackup..." + PGPASSWORD="$REPL_PASSWORD" pg_basebackup -h "$PRIMARY_HOST" -p "$PRIMARY_PORT" \ + -U replicator -D "$PGDATA" -P -R \ + -S replication_slot_slave --checkpoint=fast --wal-method=stream + + # Ensure standby signal file exists + touch "$PGDATA/standby.signal" + + echo "[standby] Bootstrap complete, starting standby..." +fi + +# Hand off to the standard postgres entrypoint +exec /usr/local/bin/docker-entrypoint.sh "$@" diff --git a/docker-compose-app.yml b/docker-compose-app.yml new file mode 100644 index 0000000..a6442ea --- /dev/null +++ b/docker-compose-app.yml @@ -0,0 +1,313 @@ +# ============================================================ +# 红动未来 - Go 微服务 +# ============================================================ +# 配合中间件启动: +# docker compose -f deploy/docker-compose-infra-main.yml -f deploy/docker-compose-app.yml up -d +# 仅应用(需要 infra 网络已存在): +# docker compose -f deploy/docker-compose-app.yml up -d +# 构建后启动: +# docker compose -f deploy/docker-compose-app.yml build +# docker compose -f deploy/docker-compose-infra-main.yml -f deploy/docker-compose-app.yml up -d +# ============================================================ + +name: redfuture + +networks: + redfuture-net: + driver: bridge + +services: + + gateway: + build: + context: ../gateway + dockerfile: Dockerfile + container_name: gateway + restart: unless-stopped + ports: + - "8000:8000" + volumes: + - ../gateway/config.yml:/build/config.yml + depends_on: + redis: + condition: service_healthy + consul: + condition: service_started + networks: + - redfuture-net + + admin-go: + build: + context: ../admin-go + dockerfile: Dockerfile + container_name: admin-go + restart: unless-stopped + ports: + - "8808:8808" + volumes: + - ../admin-go/config.yaml:/build/manifest/config/config.yaml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + ai-agent: + build: + context: ../ai-agent + dockerfile: Dockerfile + container_name: ai-agent + restart: unless-stopped + volumes: + - ../ai-agent/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + assets: + build: + context: ../assets + dockerfile: Dockerfile + container_name: assets + restart: unless-stopped + ports: + - "3003:3003" + volumes: + - ../assets/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + nats: + condition: service_started + networks: + - redfuture-net + + cid: + build: + context: ../cid + dockerfile: Dockerfile + container_name: cid + restart: unless-stopped + ports: + - "3001:3001" + volumes: + - ../cid/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + customer-server: + build: + context: ../customer-server + dockerfile: Dockerfile + container_name: customer-server + restart: unless-stopped + ports: + - "3012:3012" + volumes: + - ../customer-server/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + mongodb: + condition: service_started + nats: + condition: service_started + rabbitmq: + condition: service_started + networks: + - redfuture-net + + data-engine: + build: + context: ../data-engine + dockerfile: Dockerfile + container_name: data-engine + restart: unless-stopped + ports: + - "3013:3013" + volumes: + - ../data-engine/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + erp: + build: + context: ../erp + dockerfile: Dockerfile + container_name: erp + restart: unless-stopped + ports: + - "3011:3011" + volumes: + - ../erp/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + media: + build: + context: ../media + dockerfile: Dockerfile + container_name: media + restart: unless-stopped + ports: + - "3010:3010" + volumes: + - ../media/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + model-gateway: + build: + context: ../model-gateway + dockerfile: Dockerfile + container_name: model-gateway + restart: unless-stopped + ports: + - "3004:3004" + volumes: + - ../model-gateway/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + order: + build: + context: ../order + dockerfile: Dockerfile + container_name: order + restart: unless-stopped + ports: + - "3002:3002" + volumes: + - ../order/config.yml:/build/config.yml + depends_on: + redis: + condition: service_healthy + mongodb: + condition: service_started + networks: + - redfuture-net + + oss: + build: + context: ../oss + dockerfile: Dockerfile + container_name: oss + restart: unless-stopped + ports: + - "3008:3008" + volumes: + - ../oss/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + minio: + condition: service_started + networks: + - redfuture-net + + prompts-core: + build: + context: ../prompts-core + dockerfile: Dockerfile + container_name: prompts-core + restart: unless-stopped + ports: + - "3009:3009" + volumes: + - ../prompts-core/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + rag: + build: + context: ../rag + dockerfile: Dockerfile + container_name: rag + restart: unless-stopped + ports: + - "3006:3006" + volumes: + - ../rag/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + meilisearch: + condition: service_started + networks: + - redfuture-net + + shop-user-trade: + build: + context: ../shop-user-trade + dockerfile: Dockerfile + container_name: shop-user-trade + restart: unless-stopped + ports: + - "3007:3007" + volumes: + - ../shop-user-trade/config.yml:/build/config.yml + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - redfuture-net + + video-factory: + build: + context: ../video-factory + dockerfile: Dockerfile + container_name: video-factory + restart: unless-stopped + ports: + - "3015:3006" + volumes: + - ../video-factory/config.yml:/build/config.yml + - ../data/video-factory:/build/data + networks: + - redfuture-net diff --git a/docker-compose-infra-main.yml b/docker-compose-infra-main.yml new file mode 100644 index 0000000..5616561 --- /dev/null +++ b/docker-compose-infra-main.yml @@ -0,0 +1,141 @@ +# ============================================================ +# 红动未来 - 第三方中间件 +# ============================================================ +# 启动:docker compose -f deploy/docker-compose-infra-main.yml up -d +# 停止:docker compose -f deploy/docker-compose-infra-main.yml down +# ============================================================ + +name: redfuture + +networks: + redfuture-net: + driver: bridge + +services: + + # ---- PostgreSQL (pgvector) ---- + postgres: + image: docker.m.daocloud.io/pgvector/pgvector:pg16 + container_name: postgres + restart: unless-stopped + ports: + - "15432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: Bjang09@686^*^ + POSTGRES_DB: postgres + volumes: + - ../data/postgres:/var/lib/postgresql/data + - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql + - ./sql/init-replication.sql:/docker-entrypoint-initdb.d/init-replication.sql + - ./sql/init-pg-hba.sh:/docker-entrypoint-initdb.d/init-pg-hba.sh + command: ["postgres", "-c", "wal_level=replica", "-c", "max_wal_senders=10", "-c", "max_replication_slots=10", "-c", "hot_standby=on"] + networks: + - redfuture-net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + # ---- PostgreSQL 自动备份 (每日凌晨 2:00) ---- + postgres-backup: + image: docker.m.daocloud.io/pgvector/pgvector:pg16 + container_name: postgres-backup + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + volumes: + - ../data/postgres-backup:/backup + - ./scripts/postgres-backup.sh:/usr/local/bin/backup.sh + environment: + PGUSER: postgres + PGPASSWORD: Bjang09@686^*^ + PGHOST: postgres + PGPORT: 5432 + BACKUP_DIR: /backup + RETENTION_DAYS: 7 + command: sh /usr/local/bin/backup.sh + networks: + - redfuture-net + + # ---- Redis ---- + redis: + image: docker.m.daocloud.io/redis:7.4.8 + container_name: redis + restart: unless-stopped + ports: + - "6379:6379" + command: redis-server --requirepass Bjang09@686^*^ + volumes: + - ../data/redis:/data + networks: + - redfuture-net + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # ---- Consul ---- + consul: + image: docker.m.daocloud.io/consul:1.15.4 + container_name: consul + restart: unless-stopped + ports: + - "8500:8500" + init: true + command: agent -server -bootstrap -ui -data-dir=/consul/data -bind=0.0.0.0 -client=0.0.0.0 + volumes: + - ../data/consul:/consul/data + networks: + - redfuture-net + + # ---- MinIO ---- + minio: + image: docker.m.daocloud.io/minio/minio + container_name: minio + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: admin + MINIO_ROOT_PASSWORD: Bjang09@686^*^ + command: server /data --console-address ":9001" + volumes: + - ../data/minio:/data + networks: + - redfuture-net + + # ---- MeiliSearch ---- + meilisearch: + image: docker.m.daocloud.io/getmeili/meilisearch:v1.3 + container_name: meilisearch + restart: unless-stopped + ports: + - "7700:7700" + environment: + MEILI_MASTER_KEY: wMW7BPM3CLwnEGpPbaoci-HvB8R37wV-5tGIizsDQSQ + volumes: + - ../data/meilisearch:/meili_data + networks: + - redfuture-net + + # ---- NATS (with JetStream) ---- + nats: + image: docker.m.daocloud.io/nats:latest + container_name: nats + restart: unless-stopped + ports: + - "4222:4222" + - "8222:8222" + command: -js -sd /data -m 8222 + volumes: + - ../data/nats:/data + depends_on: + redis: + condition: service_healthy + networks: + - redfuture-net diff --git a/docker-compose-infra-slave.yml b/docker-compose-infra-slave.yml new file mode 100644 index 0000000..1ae7e28 --- /dev/null +++ b/docker-compose-infra-slave.yml @@ -0,0 +1,91 @@ +# ============================================================ +# 红动未来 - 从机集群(Host2) +# 主从/集群配置:Redis 从库、PostgreSQL 从库、K3s Agent +# ============================================================ +# 前置条件:Host1 已启动 deploy/docker-compose-infra-main.yml +# ============================================================ +# 启动:docker compose -f deploy/docker-compose-infra-slave.yml up -d +# 停止:docker compose -f deploy/docker-compose-infra-slave.yml down +# ============================================================ + +name: redfuture-slave + +# ---- 共享变量(部署前修改 Host1 IP) ---- +x-vars: &vars + MASTER_IP: 192.168.0.169 + PG_USER: postgres + PG_PASSWORD: Bjang09@686^*^ + PG_PRIMARY_PORT: 15432 + +services: + + # ---- PostgreSQL 从库 (Streaming Replication) ---- + postgres-standby: + image: docker.m.daocloud.io/pgvector/pgvector:pg16 + container_name: postgres-standby + restart: unless-stopped + ports: + - "15432:5432" + environment: + <<: *vars + POSTGRES_USER: postgres + POSTGRES_PASSWORD: Bjang09@686^*^ + POSTGRES_DB: postgres + volumes: + - ../data/postgres-standby:/var/lib/postgresql/data + entrypoint: ["/bin/sh", "-c"] + command: + - | + set -e + PGDATA="/var/lib/postgresql/data" + if [ ! -s "$${PGDATA}/PG_VERSION" ]; then + echo "[standby] Data empty, running pg_basebackup from $${MASTER_IP}:$${PG_PRIMARY_PORT}..." + PGPASSWORD="$${PG_PASSWORD}" pg_basebackup \ + -h "$${MASTER_IP}" -p "$${PG_PRIMARY_PORT}" \ + -U $${PG_USER} -D "$${PGDATA}" -P -R \ + -S replication_slot_slave --checkpoint=fast --wal-method=stream + touch "$${PGDATA}/standby.signal" + echo "[standby] Bootstrap complete." + fi + exec docker-entrypoint.sh \ + postgres \ + -c wal_level=replica \ + -c hot_standby=on \ + -c hot_standby_feedback=on \ + -c "primary_conninfo=host=$${MASTER_IP} port=$${PG_PRIMARY_PORT} user=$${PG_USER} password=$${PG_PASSWORD} application_name=standby1" + networks: + - redfuture-slave-net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + # ---- Redis 从库 (Replication) ---- + redis-replica: + image: docker.m.daocloud.io/redis:7.4.8 + container_name: redis-replica + restart: unless-stopped + ports: + - "6379:6379" + command: + - redis-server + - --replicaof + - 192.168.0.169 + - "6379" + - --requirepass + - Bjang09@686^*^ + - --masterauth + - Bjang09@686^*^ + volumes: + - ../data/redis-replica:/data + networks: + - redfuture-slave-net + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 10s + timeout: 5s + retries: 5 +networks: + redfuture-slave-net: + driver: bridge diff --git a/scripts/postgres-backup.sh b/scripts/postgres-backup.sh new file mode 100755 index 0000000..f70a04d --- /dev/null +++ b/scripts/postgres-backup.sh @@ -0,0 +1,36 @@ +#!/bin/sh +set -e + +CREDENTIALS="-h ${PGHOST:-postgres} -U ${PGUSER:-postgres}" +BACKUP_DIR=${BACKUP_DIR:-/backup} +RETENTION_DAYS=${RETENTION_DAYS:-7} + +backup() { + local filename="backup_$(date +%Y%m%d_%H%M%S).sql.gz" + echo "[$(date +%Y-%m-%d\ %H:%M:%S)] Starting backup: $filename" + pg_dumpall $CREDENTIALS | gzip > "$BACKUP_DIR/$filename" + echo "[$(date +%Y-%m-%d\ %H:%M:%S)] Backup complete: $(du -h "$BACKUP_DIR/$filename" | cut -f1)" +} + +cleanup() { + local count=$(find "$BACKUP_DIR" -name "backup_*.sql.gz" -mtime +$RETENTION_DAYS | wc -l) + if [ "$count" -gt 0 ]; then + find "$BACKUP_DIR" -name "backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete + echo "[$(date +%Y-%m-%d\ %H:%M:%S)] Cleaned up $count backup(s) older than $RETENTION_DAYS days" + fi +} + +# Run immediately on startup +backup +cleanup + +# Schedule: calculate seconds until next 2:00 AM, then run every 24h +while true; do + now=$(date +%s) + target=$(date -d "tomorrow 02:00" +%s) + sleep_seconds=$((target - now)) + echo "[$(date +%Y-%m-%d\ %H:%M:%S)] Next backup scheduled at $(date -d @$target '+%Y-%m-%d %H:%M:%S')" + sleep $sleep_seconds + backup + cleanup +done diff --git a/scripts/setup-replication.sh b/scripts/setup-replication.sh new file mode 100644 index 0000000..035ea0c --- /dev/null +++ b/scripts/setup-replication.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -e + +# 红动未来 - PostgreSQL 主从复制初始化 +# 在 Host1(主库)上执行一次,为从库复制做准备 +# 用法:sh deploy/scripts/setup-replication.sh + +POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-postgres}" + +echo "=== 1. 添加 pg_hba 复制规则 ===" +docker exec "$POSTGRES_CONTAINER" sh -c 'echo "host replication all 0.0.0.0/0 md5" >> /var/lib/postgresql/data/pg_hba.conf' + +echo "=== 2. 重载 PostgreSQL 配置 ===" +docker exec "$POSTGRES_CONTAINER" psql -U postgres -c "SELECT pg_reload_conf();" + +echo "=== 3. 创建物理复制槽 ===" +docker exec "$POSTGRES_CONTAINER" psql -U postgres -c "SELECT pg_create_physical_replication_slot('replication_slot_slave') WHERE NOT EXISTS (SELECT 1 FROM pg_replication_slots WHERE slot_name = 'replication_slot_slave');" + +echo "=== 完成 ===" +echo "现在可以在从库(Host2)上启动 PostgreSQL 从库:" +echo " docker compose -f deploy/docker-compose-infra-slave.yml up -d postgres-standby" diff --git a/sql/init-pg-hba.sh b/sql/init-pg-hba.sh new file mode 100644 index 0000000..9d9031d --- /dev/null +++ b/sql/init-pg-hba.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# 添加 pg_hba 复制访问规则(在 docker-entrypoint 初始化后执行) +echo "host replication all 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf" diff --git a/sql/init-replication.sql b/sql/init-replication.sql new file mode 100644 index 0000000..47aa961 --- /dev/null +++ b/sql/init-replication.sql @@ -0,0 +1,12 @@ +-- 创建复制用户(在 Host1 主库上运行) +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'replicator') THEN + CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'Bjang09@686^*^'; + END IF; +END +$$; + +-- 创建复制槽(用于主从复制) +SELECT pg_create_physical_replication_slot('replication_slot_slave') +WHERE NOT EXISTS (SELECT 1 FROM pg_replication_slots WHERE slot_name = 'replication_slot_slave'); diff --git a/sql/init.sql b/sql/init.sql new file mode 100644 index 0000000..af0ac82 --- /dev/null +++ b/sql/init.sql @@ -0,0 +1,34 @@ +-- 红动未来 - 初始化数据库和扩展 +CREATE EXTENSION IF NOT EXISTS vector; + +-- 为各微服务创建数据库 +SELECT 'CREATE DATABASE admin_go' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'admin_go')\gexec +SELECT 'CREATE DATABASE ai_agent' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'ai_agent')\gexec +SELECT 'CREATE DATABASE assets' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'assets')\gexec +SELECT 'CREATE DATABASE cid' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'cid')\gexec +SELECT 'CREATE DATABASE customer_server' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'customer_server')\gexec +SELECT 'CREATE DATABASE data_engine' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'data_engine')\gexec +SELECT 'CREATE DATABASE erp' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'erp')\gexec +SELECT 'CREATE DATABASE media' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'media')\gexec +SELECT 'CREATE DATABASE model_gateway' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'model_gateway')\gexec +SELECT 'CREATE DATABASE orders' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'orders')\gexec +SELECT 'CREATE DATABASE oss' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'oss')\gexec +SELECT 'CREATE DATABASE prompts_core' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'prompts_core')\gexec +SELECT 'CREATE DATABASE rag' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'rag')\gexec +SELECT 'CREATE DATABASE shop_user_trade' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'shop_user_trade')\gexec +SELECT 'CREATE DATABASE video_factory' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'video_factory')\gexec