ci: add manual nas compose deploy

This commit is contained in:
2026-05-01 12:05:32 +08:00
parent 9727077dfa
commit 168697fae7
+228
View File
@@ -0,0 +1,228 @@
name: Deploy NAS Compose
on:
workflow_dispatch:
inputs:
host:
description: "NAS SSH host"
required: true
default: "192.168.100.19"
port:
description: "NAS SSH port"
required: true
default: "22"
user:
description: "NAS SSH user"
required: true
default: "admin"
deploy_base:
description: "Base deployment directory on the NAS"
required: true
default: "/home/admin/geo-rankly"
image_tag:
description: "Docker image tag. Defaults to the short commit SHA."
required: false
default: ""
keep_releases:
description: "Number of old release directories to keep"
required: false
default: "5"
permissions:
contents: read
jobs:
deploy:
name: Deploy Docker Compose to NAS
runs-on: ubuntu-latest
timeout-minutes: 180
env:
NAS_HOST: ${{ inputs.host }}
NAS_PORT: ${{ inputs.port }}
NAS_USER: ${{ inputs.user }}
DEPLOY_BASE: ${{ inputs.deploy_base }}
REQUESTED_IMAGE_TAG: ${{ inputs.image_tag }}
KEEP_RELEASES: ${{ inputs.keep_releases }}
NAS_SSH_PASSWORD: ${{ secrets.NAS_SSH_PASSWORD }}
NAS_DEPLOY_ENV: ${{ secrets.NAS_DEPLOY_ENV }}
DOCKER_BUILDKIT: "1"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install SSH tools
run: |
apt-get update
apt-get install -y openssh-client sshpass tar
- name: Resolve deployment metadata
run: |
set -eu
if [ -z "${NAS_SSH_PASSWORD:-}" ]; then
echo "::error::Missing repository secret NAS_SSH_PASSWORD"
exit 1
fi
case "${NAS_PORT}" in
*[!0-9]*|"")
echo "::error::NAS SSH port must be numeric"
exit 1
;;
esac
case "${DEPLOY_BASE}" in
/*) ;;
*)
echo "::error::deploy_base must be an absolute path"
exit 1
;;
esac
case "${DEPLOY_BASE}" in
*[!A-Za-z0-9_./-]*)
echo "::error::deploy_base contains unsupported characters"
exit 1
;;
esac
case "${KEEP_RELEASES}" in
*[!0-9]*|"") KEEP_RELEASES=5 ;;
esac
image_tag="${REQUESTED_IMAGE_TAG}"
if [ -z "${image_tag}" ]; then
image_tag="$(git rev-parse --short=12 HEAD)"
fi
case "${image_tag}" in
*[!A-Za-z0-9_.-]*|"")
echo "::error::image_tag may only contain letters, numbers, underscore, dot, and dash"
exit 1
;;
esac
release_id="$(date -u +%Y%m%d%H%M%S)-$(git rev-parse --short=12 HEAD)"
{
echo "IMAGE_TAG=${image_tag}"
echo "RELEASE_ID=${release_id}"
echo "KEEP_RELEASES=${KEEP_RELEASES}"
} >> "${GITHUB_ENV}"
echo "Image tag: ${image_tag}"
echo "Release ID: ${release_id}"
echo "Deploy base: ${DEPLOY_BASE}"
- name: Prepare SSH known hosts
run: |
set -eu
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p "${NAS_PORT}" "${NAS_HOST}" >> ~/.ssh/known_hosts
- name: Upload release to NAS
run: |
set -eu
remote="${NAS_USER}@${NAS_HOST}"
ssh_opts="-p ${NAS_PORT} -o BatchMode=no -o StrictHostKeyChecking=yes"
release_dir="${DEPLOY_BASE}/releases/${RELEASE_ID}"
SSHPASS="${NAS_SSH_PASSWORD}" sshpass -e ssh ${ssh_opts} "${remote}" \
"mkdir -p '${release_dir}' '${DEPLOY_BASE}/shared'"
git archive --format=tar.gz HEAD \
| SSHPASS="${NAS_SSH_PASSWORD}" sshpass -e ssh ${ssh_opts} "${remote}" \
"tar -xzf - -C '${release_dir}'"
- name: Upload deploy env when provided
run: |
set -eu
if [ -z "${NAS_DEPLOY_ENV:-}" ]; then
echo "NAS_DEPLOY_ENV is empty; keeping existing NAS .env or creating from .env.example during deploy."
exit 0
fi
remote="${NAS_USER}@${NAS_HOST}"
ssh_opts="-p ${NAS_PORT} -o BatchMode=no -o StrictHostKeyChecking=yes"
printf '%s\n' "${NAS_DEPLOY_ENV}" \
| SSHPASS="${NAS_SSH_PASSWORD}" sshpass -e ssh ${ssh_opts} "${remote}" \
"cat > '${DEPLOY_BASE}/shared/.env' && chmod 600 '${DEPLOY_BASE}/shared/.env'"
- name: Deploy with Docker Compose
run: |
set -eu
remote="${NAS_USER}@${NAS_HOST}"
ssh_opts="-p ${NAS_PORT} -o BatchMode=no -o StrictHostKeyChecking=yes"
SSHPASS="${NAS_SSH_PASSWORD}" sshpass -e ssh ${ssh_opts} "${remote}" \
"DEPLOY_BASE='${DEPLOY_BASE}' RELEASE_ID='${RELEASE_ID}' IMAGE_TAG='${IMAGE_TAG}' KEEP_RELEASES='${KEEP_RELEASES}' sh -se" <<'REMOTE_SCRIPT'
set -eu
release_dir="${DEPLOY_BASE}/releases/${RELEASE_ID}"
shared_dir="${DEPLOY_BASE}/shared"
lock_dir="${DEPLOY_BASE}/.deploy.lock"
if ! mkdir "${lock_dir}" 2>/dev/null; then
echo "Another deployment is already running: ${lock_dir}"
exit 1
fi
trap 'rmdir "${lock_dir}"' EXIT INT TERM
if [ ! -f "${shared_dir}/.env" ]; then
cp "${release_dir}/deploy/.env.example" "${shared_dir}/.env"
chmod 600 "${shared_dir}/.env"
echo "Created ${shared_dir}/.env from .env.example. Edit it on the NAS before production traffic if secrets are still placeholders."
fi
docker compose version
IMAGE_TAG="${IMAGE_TAG}" DOCKER_BUILDKIT=1 docker compose \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
config >/tmp/geo-rankly-compose.yaml
IMAGE_TAG="${IMAGE_TAG}" DOCKER_BUILDKIT=1 docker compose \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
up -d --build --remove-orphans
ln -sfn "${release_dir}" "${DEPLOY_BASE}/current"
IMAGE_TAG="${IMAGE_TAG}" docker compose \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
ps
if [ "${KEEP_RELEASES}" -gt 0 ] 2>/dev/null; then
find "${DEPLOY_BASE}/releases" -mindepth 1 -maxdepth 1 -type d \
| sort -r \
| awk "NR>${KEEP_RELEASES}" \
| xargs -r rm -rf
fi
REMOTE_SCRIPT
- name: Deployment summary
run: |
summary_file="${GITHUB_STEP_SUMMARY:-/tmp/nas-compose-deploy-summary.md}"
{
echo "### NAS Docker Compose deployment"
echo ""
echo "- Host: ${NAS_HOST}:${NAS_PORT}"
echo "- Deploy base: ${DEPLOY_BASE}"
echo "- Release: ${RELEASE_ID}"
echo "- Image tag: ${IMAGE_TAG}"
echo ""
echo "Useful NAS commands:"
echo ""
echo '```sh'
echo "cd ${DEPLOY_BASE}/current"
echo "docker compose --env-file ${DEPLOY_BASE}/shared/.env -f deploy/docker-compose.yaml ps"
echo "docker compose --env-file ${DEPLOY_BASE}/shared/.env -f deploy/docker-compose.yaml logs -f tenant-api"
echo '```'
} >> "${summary_file}"