From 91d1355721514c3dd78e5d91e14223c30a4eac10 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 6 Jul 2026 00:17:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E7=A7=81=E9=92=A5=E6=B8=85=E7=90=86=20?= =?UTF-8?q?trap=20=E5=89=8D=E7=A7=BB=20+=20rsync=20=E7=A9=BA=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E5=AE=88=E5=8D=AB=20+=20=E4=B8=BB=E6=9C=BA=E5=AF=86?= =?UTF-8?q?=E9=92=A5=20accept-new(=E8=AF=84=E5=AE=A1=20Critical/Important)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- scripts/ci/deploy-site.sh | 14 +++++++++++++- scripts/ci/lib-ssh.sh | 40 +++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/scripts/ci/deploy-site.sh b/scripts/ci/deploy-site.sh index b83ff25..79bec6b 100755 --- a/scripts/ci/deploy-site.sh +++ b/scripts/ci/deploy-site.sh @@ -10,8 +10,20 @@ set -euo pipefail # shellcheck source=scripts/ci/lib-ssh.sh . scripts/ci/lib-ssh.sh +# setup_ssh registers the EXIT cleanup trap itself (before writing the key), +# so a mid-setup failure still cleans up — see lib-ssh.sh. setup_ssh -trap teardown_ssh EXIT + +# Guard against deploying a missing/empty build: `rsync --delete` against an +# empty source would wipe the live site. +if [ ! -d web/website/dist ]; then + echo "==> deploy-site: web/website/dist/ does not exist — refusing to deploy" >&2 + exit 1 +fi +if ! find web/website/dist -mindepth 1 -print -quit | grep -q .; then + echo "==> deploy-site: web/website/dist/ is empty — refusing to deploy" >&2 + exit 1 +fi DEPLOY_TARGET="root@${DEPLOY_HOST}:/var/www/pangolin-site/" echo "==> deploy-site: rsync web/website/dist/ -> ${DEPLOY_TARGET}" diff --git a/scripts/ci/lib-ssh.sh b/scripts/ci/lib-ssh.sh index e36ce72..fc7cb9f 100755 --- a/scripts/ci/lib-ssh.sh +++ b/scripts/ci/lib-ssh.sh @@ -3,10 +3,12 @@ # (site / server). `source` this from deploy-*.sh. # # Provides: -# setup_ssh -> writes $DEPLOY_SSH_KEY to a temp private key (mode 600), -# registers the deploy host in known_hosts, and exports -# SSH / RSYNC_SSH (ssh command strings) + SSH_KEY_FILE. -# teardown_ssh -> removes the temp private key. +# setup_ssh -> registers the EXIT cleanup trap first, then writes +# $DEPLOY_SSH_KEY to a temp private key (mode 600), +# registers the deploy host in a dedicated known_hosts +# file, and exports SSH / RSYNC_SSH (ssh command strings) +# + SSH_KEY_FILE. +# teardown_ssh -> removes the temp private key + known_hosts file. # # Requires env: DEPLOY_SSH_KEY (PEM content of the deploy private key, # authorized for root on the target host). @@ -21,9 +23,15 @@ DEPLOY_HOST="${DEPLOY_HOST:-103.119.13.48}" DEPLOY_PORT="${DEPLOY_PORT:-22}" SSH_KEY_FILE="${SSH_KEY_FILE:-/tmp/pangolin_deploy_key.$$}" +SSH_KNOWN_HOSTS_FILE="${SSH_KNOWN_HOSTS_FILE:-/tmp/pangolin_deploy_known_hosts.$$}" # setup_ssh — write the deploy key, register known_hosts, export SSH/RSYNC_SSH. setup_ssh() { + # Register cleanup FIRST: if anything below fails mid-setup (e.g. a + # transient ssh-keyscan error under `set -e`), the private key file must + # still be removed on exit rather than leaking. + trap teardown_ssh EXIT + if [ -z "${DEPLOY_SSH_KEY:-}" ]; then echo "==> setup_ssh: DEPLOY_SSH_KEY is empty" >&2 return 1 @@ -32,22 +40,30 @@ setup_ssh() { mkdir -p ~/.ssh chmod 700 ~/.ssh + # Pre-create the key file with restrictive perms *before* writing any key + # material into it, so there is no window at the default umask between + # file creation and chmod. + install -m 600 /dev/null "${SSH_KEY_FILE}" + # `printf '%s\n'` 末尾补一个换行:Forgejo/Gitea 存 secret 会去掉结尾换行, # 而缺结尾换行的 OpenSSH 格式私钥会被判为 "invalid format" 拒绝加载, # 退化成无密钥 → Permission denied。多补的换行对已含结尾换行的 PEM 无害。 printf '%s\n' "${DEPLOY_SSH_KEY}" > "${SSH_KEY_FILE}" - chmod 600 "${SSH_KEY_FILE}" - ssh-keyscan -p "${DEPLOY_PORT}" -H "${DEPLOY_HOST}" >> ~/.ssh/known_hosts 2>/dev/null + # Populate a dedicated known_hosts file via TOFU keyscan. This is + # belt-and-suspenders: `accept-new` below will pin the host key on first + # real connection regardless, so a transient keyscan failure must not + # abort the deploy. + ssh-keyscan -p "${DEPLOY_PORT}" -H "${DEPLOY_HOST}" >> "${SSH_KNOWN_HOSTS_FILE}" 2>/dev/null || true - SSH="ssh -i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o StrictHostKeyChecking=no" - RSYNC_SSH="ssh -i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o StrictHostKeyChecking=no" - export SSH RSYNC_SSH SSH_KEY_FILE DEPLOY_HOST DEPLOY_PORT - echo "==> setup_ssh: key written to ${SSH_KEY_FILE}, known_hosts updated for ${DEPLOY_HOST}:${DEPLOY_PORT}" + SSH="ssh -i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}" + RSYNC_SSH="ssh -i ${SSH_KEY_FILE} -p ${DEPLOY_PORT} -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${SSH_KNOWN_HOSTS_FILE}" + export SSH RSYNC_SSH SSH_KEY_FILE SSH_KNOWN_HOSTS_FILE DEPLOY_HOST DEPLOY_PORT + echo "==> setup_ssh: key written to ${SSH_KEY_FILE}, known_hosts pinned (accept-new) for ${DEPLOY_HOST}:${DEPLOY_PORT}" } -# teardown_ssh — remove the temp private key. +# teardown_ssh — remove the temp private key + known_hosts file. teardown_ssh() { - rm -f "${SSH_KEY_FILE}" + rm -f "${SSH_KEY_FILE}" "${SSH_KNOWN_HOSTS_FILE}" echo "==> teardown_ssh: removed ${SSH_KEY_FILE}" }