ci: add non-docker production deployment workflow
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
name: deploy-prod-non-docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: wx-service-prod
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23.x'
|
||||
|
||||
- name: Download modules
|
||||
run: go mod download
|
||||
|
||||
- name: Build linux binary
|
||||
run: |
|
||||
mkdir -p tmp
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "-s -w" -o tmp/wx_service ./cmd/api
|
||||
|
||||
- name: Prepare SSH
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
|
||||
HOST: ${{ secrets.PROD_HOST }}
|
||||
PORT: ${{ secrets.PROD_PORT }}
|
||||
run: |
|
||||
set -e
|
||||
if [ -z "$SSH_KEY" ] || [ -z "$HOST" ]; then
|
||||
echo "Missing required secrets: PROD_SSH_KEY / PROD_HOST"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -p "${PORT:-22}" "$HOST" >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Upload binary to server
|
||||
env:
|
||||
HOST: ${{ secrets.PROD_HOST }}
|
||||
PORT: ${{ secrets.PROD_PORT }}
|
||||
USER: ${{ secrets.PROD_USER }}
|
||||
run: |
|
||||
set -e
|
||||
REMOTE_BIN="/tmp/wx_service-${GITHUB_SHA}"
|
||||
scp -P "${PORT:-22}" tmp/wx_service "${USER:-root}@${HOST}:${REMOTE_BIN}"
|
||||
|
||||
- name: Deploy on server
|
||||
env:
|
||||
HOST: ${{ secrets.PROD_HOST }}
|
||||
PORT: ${{ secrets.PROD_PORT }}
|
||||
USER: ${{ secrets.PROD_USER }}
|
||||
run: |
|
||||
set -e
|
||||
REMOTE_BIN="/tmp/wx_service-${GITHUB_SHA}"
|
||||
ssh -p "${PORT:-22}" "${USER:-root}@${HOST}" \
|
||||
"APP_DIR='/www/wwwroot/wx_service' \
|
||||
DIST_DIR='/www/wwwroot/wx_service/dist' \
|
||||
SOURCE_BIN='${REMOTE_BIN}' \
|
||||
RELEASE_ID='${GITHUB_SHA}' \
|
||||
SERVICE_NAME='wx_service' \
|
||||
RUN_USER='www' \
|
||||
RUN_GROUP='www' \
|
||||
PORT='8080' \
|
||||
SYNC_CODE='true' \
|
||||
DEPLOY_REF='${GITHUB_SHA}' \
|
||||
INSTALL_SERVICE='true' \
|
||||
bash -s" < scripts/ops/deploy_binary.sh
|
||||
@@ -10,6 +10,7 @@
|
||||
- `docs/common/upload_qiniu.md`
|
||||
- `docs/common/wechat_official.md`
|
||||
- `docs/common/redis.md`
|
||||
- `docs/common/deploy_ci.md`(GitHub Actions 非 Docker 自动化发布)
|
||||
|
||||
## 去水印小程序
|
||||
|
||||
|
||||
@@ -34,3 +34,7 @@
|
||||
## Redis
|
||||
|
||||
- `docs/common/redis.md`
|
||||
|
||||
## 自动化部署(非 Docker)
|
||||
|
||||
- `docs/common/deploy_ci.md`
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# 生产部署(GitHub Actions,非 Docker)
|
||||
|
||||
本文档用于 `wx_service` 在宝塔服务器上的自动化发布:
|
||||
- 触发:`main` 分支 push 或手动触发
|
||||
- 流程:GitHub Actions 构建二进制 -> SSH 上传到服务器 -> 远程发布脚本重启并健康检查
|
||||
- 特点:不依赖 Docker
|
||||
|
||||
## 1. 服务器约定
|
||||
|
||||
- 代码目录:`/www/wwwroot/wx_service`
|
||||
- 运行目录:`/www/wwwroot/wx_service/dist`
|
||||
- 二进制:`/www/wwwroot/wx_service/dist/wx_service`
|
||||
- 进程端口:`8080`
|
||||
- 反向代理:宝塔 Nginx -> `127.0.0.1:8080`
|
||||
|
||||
> 远程脚本:`scripts/ops/deploy_binary.sh`
|
||||
|
||||
## 2. GitHub Secrets
|
||||
|
||||
在仓库 `Settings -> Secrets and variables -> Actions` 新增:
|
||||
|
||||
- `PROD_HOST`:生产机 IP 或域名
|
||||
- `PROD_PORT`:SSH 端口(默认 `22`)
|
||||
- `PROD_USER`:SSH 用户(建议 `root` 或具备发布权限的用户)
|
||||
- `PROD_SSH_KEY`:私钥内容(建议单独部署密钥)
|
||||
|
||||
## 3. 工作流文件
|
||||
|
||||
- `/.github/workflows/deploy-prod.yml`
|
||||
|
||||
已默认发布到:`/www/wwwroot/wx_service/dist/wx_service`。
|
||||
|
||||
## 4. 首次上线注意事项
|
||||
|
||||
1. 服务器必须已存在:`/www/wwwroot/wx_service/.git`(可正常 `git fetch`)
|
||||
2. 服务器上配置好生产 `.env`(建议放两份):
|
||||
- `/www/wwwroot/wx_service/.env`
|
||||
- `/www/wwwroot/wx_service/dist/.env`
|
||||
3. 开放 `8080` 本地监听,并由 Nginx 反代
|
||||
4. 第一次执行会自动尝试创建 `systemd` 服务 `wx_service`
|
||||
|
||||
## 5. 发布行为
|
||||
|
||||
每次发布会执行:
|
||||
|
||||
1. GitHub Actions 构建 Linux 二进制
|
||||
2. 上传到服务器 `/tmp/wx_service-<commit_sha>`
|
||||
3. 远程脚本执行:
|
||||
- `git fetch` + `git reset --hard <commit_sha>`(同步代码)
|
||||
- 备份旧二进制到 `/www/wwwroot/wx_service/backups/`
|
||||
- 原子替换 `dist/wx_service`
|
||||
- 重启服务
|
||||
- 健康检查 `http://127.0.0.1:8080/healthz`
|
||||
4. 若健康检查失败,自动回滚旧二进制并重启
|
||||
|
||||
## 6. 手动发布(应急)
|
||||
|
||||
在服务器执行:
|
||||
|
||||
```bash
|
||||
cd /www/wwwroot/wx_service
|
||||
APP_DIR=/www/wwwroot/wx_service \
|
||||
DIST_DIR=/www/wwwroot/wx_service/dist \
|
||||
SOURCE_BIN=/tmp/wx_service-manual \
|
||||
RELEASE_ID=manual-$(date +%Y%m%d%H%M%S) \
|
||||
SERVICE_NAME=wx_service \
|
||||
RUN_USER=www RUN_GROUP=www \
|
||||
PORT=8080 \
|
||||
SYNC_CODE=true DEPLOY_REF=main \
|
||||
INSTALL_SERVICE=true \
|
||||
bash scripts/ops/deploy_binary.sh
|
||||
```
|
||||
|
||||
## 7. 回滚
|
||||
|
||||
```bash
|
||||
ls -lt /www/wwwroot/wx_service/backups/
|
||||
cp -f /www/wwwroot/wx_service/backups/wx_service.<backup_id>.bak /www/wwwroot/wx_service/dist/wx_service
|
||||
chown www:www /www/wwwroot/wx_service/dist/wx_service
|
||||
systemctl restart wx_service
|
||||
```
|
||||
|
||||
若机器未使用 systemd,可改为:
|
||||
|
||||
```bash
|
||||
pkill -f /www/wwwroot/wx_service/dist/wx_service
|
||||
su -s /bin/bash - www -c "cd /www/wwwroot/wx_service/dist && nohup ./wx_service >> /www/wwwlogs/wx_service.stdout.log 2>&1 &"
|
||||
```
|
||||
Executable
+148
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
APP_DIR="${APP_DIR:-/www/wwwroot/wx_service}"
|
||||
DIST_DIR="${DIST_DIR:-${APP_DIR}/dist}"
|
||||
BIN_PATH="${BIN_PATH:-${DIST_DIR}/wx_service}"
|
||||
SOURCE_BIN="${SOURCE_BIN:-}"
|
||||
SERVICE_NAME="${SERVICE_NAME:-wx_service}"
|
||||
PORT="${PORT:-8080}"
|
||||
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:${PORT}/healthz}"
|
||||
RUN_USER="${RUN_USER:-www}"
|
||||
RUN_GROUP="${RUN_GROUP:-www}"
|
||||
RELEASE_ID="${RELEASE_ID:-manual-$(date +%Y%m%d%H%M%S)}"
|
||||
KEEP_BACKUPS="${KEEP_BACKUPS:-5}"
|
||||
SYNC_CODE="${SYNC_CODE:-false}"
|
||||
DEPLOY_REF="${DEPLOY_REF:-main}"
|
||||
INSTALL_SERVICE="${INSTALL_SERVICE:-true}"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||
}
|
||||
|
||||
health_check() {
|
||||
local tries=30
|
||||
while [ "$tries" -gt 0 ]; do
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if curl -fsS "$HEALTH_URL" >/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if wget -q -O - "$HEALTH_URL" >/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
tries=$((tries - 1))
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then
|
||||
log "restarting systemd service: ${SERVICE_NAME}"
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
else
|
||||
log "systemd service not found, fallback to pkill + nohup"
|
||||
pkill -f "$BIN_PATH" || true
|
||||
su -s /bin/bash - "$RUN_USER" -c "cd '$DIST_DIR' && nohup '$BIN_PATH' >> /www/wwwlogs/${SERVICE_NAME}.stdout.log 2>&1 &"
|
||||
fi
|
||||
}
|
||||
|
||||
create_service_if_needed() {
|
||||
if [ "$INSTALL_SERVICE" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v systemctl >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "creating systemd service: ${SERVICE_NAME}"
|
||||
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<SERVICE
|
||||
[Unit]
|
||||
Description=wx_service API
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${RUN_USER}
|
||||
Group=${RUN_GROUP}
|
||||
WorkingDirectory=${DIST_DIR}
|
||||
ExecStart=${BIN_PATH}
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
LimitNOFILE=65535
|
||||
Environment=GIN_MODE=release
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
}
|
||||
|
||||
if [ -z "$SOURCE_BIN" ]; then
|
||||
echo "SOURCE_BIN is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$SOURCE_BIN" ]; then
|
||||
echo "binary not found: $SOURCE_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "deploy start, release: ${RELEASE_ID}"
|
||||
mkdir -p "$DIST_DIR" "$APP_DIR/backups"
|
||||
|
||||
if [ "$SYNC_CODE" = "true" ] && [ -d "$APP_DIR/.git" ]; then
|
||||
log "syncing code from github ref: ${DEPLOY_REF}"
|
||||
git -C "$APP_DIR" fetch --all --prune
|
||||
git -C "$APP_DIR" reset --hard "$DEPLOY_REF"
|
||||
fi
|
||||
|
||||
backup_path=""
|
||||
if [ -f "$BIN_PATH" ]; then
|
||||
backup_path="$APP_DIR/backups/wx_service.${RELEASE_ID}.bak"
|
||||
cp -f "$BIN_PATH" "$backup_path"
|
||||
log "backup created: $backup_path"
|
||||
fi
|
||||
|
||||
install -m 755 "$SOURCE_BIN" "${BIN_PATH}.new"
|
||||
mv -f "${BIN_PATH}.new" "$BIN_PATH"
|
||||
chown "$RUN_USER:$RUN_GROUP" "$BIN_PATH" || true
|
||||
|
||||
if [ ! -f "$DIST_DIR/.env" ] && [ -f "$APP_DIR/.env" ]; then
|
||||
cp -f "$APP_DIR/.env" "$DIST_DIR/.env"
|
||||
chown "$RUN_USER:$RUN_GROUP" "$DIST_DIR/.env" || true
|
||||
fi
|
||||
|
||||
create_service_if_needed
|
||||
restart_service
|
||||
|
||||
if health_check; then
|
||||
log "health check success: $HEALTH_URL"
|
||||
else
|
||||
log "health check failed, rollback"
|
||||
if [ -n "$backup_path" ] && [ -f "$backup_path" ]; then
|
||||
cp -f "$backup_path" "$BIN_PATH"
|
||||
chown "$RUN_USER:$RUN_GROUP" "$BIN_PATH" || true
|
||||
restart_service || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$SOURCE_BIN" ] && [[ "$SOURCE_BIN" == /tmp/* ]]; then
|
||||
rm -f "$SOURCE_BIN"
|
||||
fi
|
||||
|
||||
if [ -d "$APP_DIR/backups" ]; then
|
||||
ls -1t "$APP_DIR"/backups/wx_service.*.bak 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | xargs -r rm -f
|
||||
fi
|
||||
|
||||
log "deploy done"
|
||||
Reference in New Issue
Block a user