192 lines
7.5 KiB
YAML
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}"
|