Files
BYWCloudreve/.gitea/workflows/build-deploy.yml
T
2026-09-13 13:13:32 +08:00

192 lines
7.5 KiB
YAML

name: Build and Deploy
on:
push:
branches:
- main
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: false
default: 'info'
type: string
env:
REGISTRY: gitea.fse-media.group
IMAGE_NAME: henry_du/bywcloudreve
SERVER_HOST: 192.140.163.199
SERVER_USER: ubuntu
CONTAINER_NAME: cloudreve
jobs:
build-and-deploy:
runs-on: self-hosted
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,format=long,prefix=
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.ci
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-to: type=inline
build-args: |
BUILD_DATE=${{ gitea.sha }}
- name: Deploy to server via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script_stop: true
script: |
set -euo pipefail
sudo -n true 2>/dev/null || SUDO=""
[ -z "${SUDO+x}" ] && SUDO="sudo"
CONTAINER="${{ env.CONTAINER_NAME }}"
NEW_IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
echo "[1/6] 登录 Gitea Container Registry"
echo "${{ secrets.GITEA_TOKEN }}" | $SUDO docker login "${{ env.REGISTRY }}" -u "${{ gitea.actor }}" --password-stdin
echo "[2/6] 拉取最新镜像 ${NEW_IMAGE}"
$SUDO docker pull "${NEW_IMAGE}"
if ! $SUDO docker inspect "$CONTAINER" > /tmp/old.json 2>/dev/null; then
echo "[WARN] 现有容器 $CONTAINER 不存在,按默认参数首次启动(请确认 postgres/redis 已在 172.17.0.1)。"
$SUDO docker run -d \
--name "$CONTAINER" \
--restart unless-stopped \
-p 5212:5212 -p 6888:6888 -p 6888:6888/udp \
-v cloudreve_backend_data:/cloudreve/data \
--add-host=postgresql:172.17.0.1 \
--add-host=redis:172.17.0.1 \
-e CR_CONF_Database.Type=postgres \
-e CR_CONF_Database.Host=postgresql \
-e CR_CONF_Database.User=cloudreve \
-e CR_CONF_Database.Name=cloudreve \
-e CR_CONF_Database.Port=5432 \
-e CR_CONF_Redis.Server=redis:6379 \
"$NEW_IMAGE" || true
echo "首次启动命令已执行。"
exit 0
fi
echo "[3/6] 从现有容器提取运行参数 (安装 jq)"
which jq >/dev/null 2>&1 || ($SUDO apt-get update -qq && $SUDO apt-get install -y -qq jq >/dev/null)
OLD_IMAGE="$(jq -r '.Config.Image' /tmp/old.json)"
RESTART_POLICY="$(jq -r '.HostConfig.RestartPolicy.Name' /tmp/old.json)"
NETWORK_MODE="$(jq -r '.HostConfig.NetworkMode' /tmp/old.json)"
echo "旧镜像: ${OLD_IMAGE} -> 新镜像: ${NEW_IMAGE}"
echo "重启策略: ${RESTART_POLICY}, 网络: ${NETWORK_MODE}"
# 以数组方式构建参数,避免 eval 转义问题
RUN_ARGS=( -d )
RUN_ARGS+=( --name "${CONTAINER}_new" )
[ "${RESTART_POLICY}" != "null" ] && [ -n "${RESTART_POLICY}" ] && RUN_ARGS+=( --restart "${RESTART_POLICY}" )
[ "${NETWORK_MODE}" != "null" ] && [ -n "${NETWORK_MODE}" ] && RUN_ARGS+=( --network "${NETWORK_MODE}" )
# 环境变量
while IFS= read -r env_line; do
[ -z "${env_line}" ] && continue
RUN_ARGS+=( -e "${env_line}" )
done < <(jq -r '.Config.Env[]?' /tmp/old.json)
# 端口映射
while IFS=$'\t' read -r cport hostip hostport; do
[ -z "${cport}" ] && continue
proto=""
if [[ "${cport}" == */udp ]]; then
proto="/udp"
cport="${cport%/udp}"
elif [[ "${cport}" == */tcp ]]; then
cport="${cport%/tcp}"
fi
if [ -n "${hostport}" ]; then
ip_part=""
[ -n "${hostip}" ] && ip_part="${hostip}:"
RUN_ARGS+=( -p "${ip_part}${hostport}:${cport}${proto}" )
else
RUN_ARGS+=( -p "${cport}${proto}" )
fi
done < <(jq -r '.HostConfig.PortBindings | to_entries[]? | .key as $k | .value[]? | [$k, .HostIp, .HostPort] | @tsv' /tmp/old.json)
# 卷挂载
while IFS= read -r mline; do
[ -z "${mline}" ] && continue
RUN_ARGS+=( ${mline} )
done < <(jq -r '.Mounts[]? | select(.Type=="bind" or .Type=="volume") as $m
| if $m.Type == "bind" then
"-v " + $m.Source + ":" + $m.Destination + (if $m.ReadOnly then ":ro" else "" end)
else
"-v " + $m.Name + ":" + $m.Destination + (if $m.ReadOnly then ":ro" else "" end)
end' /tmp/old.json 2>/dev/null || true)
# Extra hosts
while IFS= read -r h; do
[ -z "${h}" ] && continue
RUN_ARGS+=( --add-host "${h}" )
done < <(jq -r '.HostConfig.ExtraHosts[]?' /tmp/old.json 2>/dev/null || true)
echo "[4/6] 以相同参数启动新容器 (${#RUN_ARGS[@]} args)"
NEW_CID=$($SUDO docker run "${RUN_ARGS[@]}" "${NEW_IMAGE}")
echo "新容器ID: ${NEW_CID}"
sleep 5
NEW_STATUS=$($SUDO docker inspect -f '{{.State.Status}}' "${NEW_CID}" 2>/dev/null || echo "unknown")
echo "新容器状态: ${NEW_STATUS}"
if [ "${NEW_STATUS}" != "running" ] && [ "${NEW_STATUS}" != "created" ]; then
echo "[ERROR] 新容器未正常运行,输出最近日志后回滚。"
$SUDO docker logs --tail 60 "${NEW_CID}" || true
$SUDO docker rm -f "${NEW_CID}" 2>/dev/null || true
exit 1
fi
echo "[5/6] 停用旧容器并切换新容器为目标名称"
$SUDO docker stop -t 30 "$CONTAINER" 2>/dev/null || true
$SUDO docker rm -f "$CONTAINER" 2>/dev/null || true
$SUDO docker rename "${CONTAINER}_new" "$CONTAINER"
echo "[6/6] 清理旧镜像 / 悬空镜像"
$SUDO docker image prune -f >/dev/null 2>&1 || true
FINAL_IMG=$($SUDO docker inspect -f '{{.Config.Image}}' "$CONTAINER")
echo "✅ 部署完成 @ $(date) 。容器 $CONTAINER 当前镜像: ${FINAL_IMG}"