Compare commits

..

2 Commits

4 changed files with 46 additions and 32 deletions

View File

@ -0,0 +1,22 @@
name: deploy
on:
workflow_dispatch:
jobs:
deploy:
runs-on: docker
steps:
- name: SSH deploy
shell: sh
env:
KEY_B64: ${{ secrets.DEPLOY_KEY_B64 }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
run: |
set -e
apk add --no-cache openssh-client >/dev/null 2>&1 || true
mkdir -p "$HOME/.ssh"
echo "$KEY_B64" | base64 -d > "$HOME/.ssh/id"
chmod 600 "$HOME/.ssh/id"
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "$HOME/.ssh/id" \
"$DEPLOY_USER@$DEPLOY_HOST" "cd /opt/fashion/backend_v2 && ./deploy.sh backend"

21
deploy.sh Normal file → Executable file
View File

@ -6,6 +6,7 @@
# ./deploy.sh web 只更新前端 # ./deploy.sh web 只更新前端
# ./deploy.sh --rollback 切回上一个槽(不动代码,秒级) # ./deploy.sh --rollback 切回上一个槽(不动代码,秒级)
# ./deploy.sh all --no-pull 用服务器上现有代码构建 # ./deploy.sh all --no-pull 用服务器上现有代码构建
# ./deploy.sh web --no-cache 强制不用构建缓存(前端 SSG 依赖后端数据,数据变了要强制重建)
# ./deploy.sh --prune 只清理旧镜像(保留最近 KEEP_IMAGES 个,默认 5) # ./deploy.sh --prune 只清理旧镜像(保留最近 KEEP_IMAGES 个,默认 5)
# GIT_BRANCH=dev ./deploy.sh all 临时指定分支(优先于 .env) # GIT_BRANCH=dev ./deploy.sh all 临时指定分支(优先于 .env)
# #
@ -16,14 +17,16 @@ cd "$(dirname "$0")"
TARGET="backend" TARGET="backend"
NO_PULL=0 NO_PULL=0
NO_CACHE=0
PRUNE_ONLY=0 PRUNE_ONLY=0
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
backend|web|all) TARGET="$arg" ;; backend|web|all) TARGET="$arg" ;;
--rollback) TARGET="rollback" ;; --rollback) TARGET="rollback" ;;
--no-pull) NO_PULL=1 ;; --no-pull) NO_PULL=1 ;;
--no-cache) NO_CACHE=1 ;;
--prune) PRUNE_ONLY=1 ;; --prune) PRUNE_ONLY=1 ;;
*) echo "用法: ./deploy.sh [backend|web|all|--rollback|--prune] [--no-pull]"; exit 1 ;; *) echo "用法: ./deploy.sh [backend|web|all|--rollback|--prune] [--no-pull] [--no-cache]"; exit 1 ;;
esac esac
done done
@ -148,7 +151,9 @@ roll() { # $1=component(backend|web)
old=$(cur_slot "$comp"); idle=$(other_slot "$old") old=$(cur_slot "$comp"); idle=$(other_slot "$old")
echo "==> $comp:活跃槽 $old,更新空闲槽 $idle(镜像 $RELEASE)" echo "==> $comp:活跃槽 $old,更新空闲槽 $idle(镜像 $RELEASE)"
docker compose build "${comp}_a" >/dev/null local build_flag=""
[ "$NO_CACHE" = "1" ] && build_flag="--no-cache"
docker compose build $build_flag "${comp}_a" >/dev/null
# latest 只是给手动执行 compose 命令用的便利别名,deploy 自己从不依赖它 # latest 只是给手动执行 compose 命令用的便利别名,deploy 自己从不依赖它
docker tag "fashion-${comp}:${RELEASE}" "fashion-${comp}:latest" 2>/dev/null || true docker tag "fashion-${comp}:${RELEASE}" "fashion-${comp}:latest" 2>/dev/null || true
@ -214,12 +219,14 @@ else
export RELEASE="${RELEASE:-latest}" export RELEASE="${RELEASE:-latest}"
fi fi
# 首次部署先让代理起来(前端构建期要用 18091 取 SSG 数据) # 首次部署:先写切换点,再只启动代理。
if ! healthy proxy; then # - render_active 必须先于 proxy 启动,否则 include 的 map 为空,nginx 会因未知变量启动失败。
echo "==> 启动代理(首次会一并拉起 db 与 a 槽)" # - --no-deps 避免首次起代理时顺带构建前端(此时 18091 尚未就绪,SSG 取数会失败)。
docker compose up -d proxy
fi
render_active render_active
if ! healthy proxy; then
echo "==> 启动代理(首次只起代理,槽位由 roll 流程构建)"
docker compose up -d --no-deps proxy
fi
if [ "$TARGET" = "rollback" ]; then if [ "$TARGET" = "rollback" ]; then
do_rollback do_rollback

View File

@ -26,7 +26,7 @@ x-backend-base: &backend-base
BACKSTAGE_PORT: 8092 BACKSTAGE_PORT: 8092
GIN_MODE: release GIN_MODE: release
HASHID_SECRET: ${HASHID_SECRET:?HASHID_SECRET 未设置:复制 .env.example 为 .env 并填写} HASHID_SECRET: ${HASHID_SECRET:-}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET 未设置} JWT_SECRET: ${JWT_SECRET:?JWT_SECRET 未设置}
SSG_TOKEN: ${SSG_TOKEN:-} SSG_TOKEN: ${SSG_TOKEN:-}
CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:-https://xisoa.com,https://www.xisoa.com} CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:-https://xisoa.com,https://www.xisoa.com}
@ -35,7 +35,8 @@ x-backend-base: &backend-base
RATE_LIMIT_RPS: ${RATE_LIMIT_RPS:-20} RATE_LIMIT_RPS: ${RATE_LIMIT_RPS:-20}
RATE_LIMIT_BURST: ${RATE_LIMIT_BURST:-40} RATE_LIMIT_BURST: ${RATE_LIMIT_BURST:-40}
DB_HOST: db # 复用 1Panel 现有 Postgres(宿主 5432)。host-gateway 让容器经宿主访问它。
DB_HOST: host.docker.internal
DB_PORT: 5432 DB_PORT: 5432
DB_USER: ${DB_USER:-fashion} DB_USER: ${DB_USER:-fashion}
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置} DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置}
@ -60,9 +61,8 @@ x-backend-base: &backend-base
UPLOAD_DIR: /app/uploads UPLOAD_DIR: /app/uploads
volumes: volumes:
- uploads:/app/uploads - uploads:/app/uploads
depends_on: extra_hosts:
db: - "host.docker.internal:host-gateway"
condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/api/health"] test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/api/health"]
interval: 10s interval: 10s
@ -84,29 +84,15 @@ x-web-base: &web-base
start_period: 15s start_period: 15s
services: services:
# ---- 数据库:PostgreSQL 16 + pgvector ---- # ---- 数据库:复用 1Panel 现有 Postgres(连接信息见 .env 的 DB_HOST/DB_PORT/DB_USER/DB_PASSWORD)----
db:
image: pgvector/pgvector:pg16
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER:-fashion}
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置}
POSTGRES_DB: ${DB_NAME:-fashion}
TZ: Asia/Shanghai
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-fashion} -d ${DB_NAME:-fashion}"]
interval: 10s
timeout: 5s
retries: 10
# ---- 常驻代理:整个发布过程中不重建,切换只靠 reload ---- # ---- 常驻代理:整个发布过程中不重建,切换只靠 reload ----
proxy: proxy:
image: nginx:1.27-alpine image: nginx:1.27-alpine
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${HTTP_PORT:-80}:80" # 只绑宿主回环:80/443 由 1Panel OpenResty 占用,外层反代到 127.0.0.1:${HTTP_PORT}
- "127.0.0.1:${HTTP_PORT:-8080}:80"
- "127.0.0.1:18091:18091" # 前端构建期取 SSG - "127.0.0.1:18091:18091" # 前端构建期取 SSG
- "127.0.0.1:18092:18092" # 管理后台(SSH 隧道) - "127.0.0.1:18092:18092" # 管理后台(SSH 隧道)
volumes: volumes:
@ -151,5 +137,4 @@ services:
<<: *web-base <<: *web-base
volumes: volumes:
db_data:
uploads: uploads:

View File

@ -71,7 +71,7 @@ server {
# 内部:前端构建期取 SSG 数据(只绑宿主回环,前端镜像 build 时用 host 网络访问) # 内部:前端构建期取 SSG 数据(只绑宿主回环,前端镜像 build 时用 host 网络访问)
server { server {
listen 127.0.0.1:18091; listen 18091; # 容器内必须监听 0.0.0.0,否则 docker 端口发布到容器网卡时连不上;宿主机只发布到 127.0.0.1
access_log off; access_log off;
location / { location / {
proxy_pass http://$ssg_up; proxy_pass http://$ssg_up;
@ -81,7 +81,7 @@ server {
# 内部:管理后台(只绑宿主回环;远程访问用 SSH 隧道到 18092) # 内部:管理后台(只绑宿主回环;远程访问用 SSH 隧道到 18092)
server { server {
listen 127.0.0.1:18092; listen 18092; # 同上:容器内回环监听会导致 docker 端口发布不可达(管理后台/爬虫入库走此口)
location / { location / {
proxy_pass http://$admin_up; proxy_pass http://$admin_up;
proxy_set_header Host $host; proxy_set_header Host $host;