From 9bac5c8dcb586971e79a7ccc0b880bdc294bfe4f Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 6 Jul 2026 00:10:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(ci):=20=E5=AE=98=E7=BD=91=20site-v*=20?= =?UTF-8?q?=E6=9E=84=E5=BB=BA+=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC(compile?= =?UTF-8?q?-site/deploy-site/lib-ssh/workflow)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/ci/compile-site.sh: node:20 容器内构建 web/website(Astro), SITE_URL 注入 canonical 域名 https://pangolin.yanmeiai.com。 - scripts/ci/lib-ssh.sh: 新增共享 setup_ssh/teardown_ssh(写临时私钥+ known_hosts),供 site 与后续 server 部署复用;目标写死 IP 103.119.13.48 (runner 无法解析用户本机 ~/.ssh/config 的 pangolin1 别名)。 - scripts/ci/deploy-site.sh: rsync dist/ 到 pangolin1 /var/www/pangolin-site/,root 部署。 - .gitea/workflows/deploy-site.yml: site-v* tag + workflow_dispatch 触发, concurrency 组 deploy-site 防并发。 - .gitea/workflows/ci.yml: shellcheck 列表纳入三个新脚本。 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u --- .gitea/workflows/ci.yml | 5 ++- .gitea/workflows/deploy-site.yml | 29 +++++++++++++++++ scripts/ci/compile-site.sh | 20 ++++++++++++ scripts/ci/deploy-site.sh | 20 ++++++++++++ scripts/ci/lib-ssh.sh | 53 ++++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/deploy-site.yml create mode 100755 scripts/ci/compile-site.sh create mode 100755 scripts/ci/deploy-site.sh create mode 100755 scripts/ci/lib-ssh.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index c644c34..2b513cf 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -47,7 +47,10 @@ jobs: -S warning \ /mnt/scripts/ci/_env.sh \ /mnt/scripts/ci/lib-forgejo.sh \ - /mnt/scripts/ci/notify.sh + /mnt/scripts/ci/notify.sh \ + /mnt/scripts/ci/lib-ssh.sh \ + /mnt/scripts/ci/compile-site.sh \ + /mnt/scripts/ci/deploy-site.sh # ── Job 2: OpenAPI Sync Check ──────────────────────────────────────────── openapi-check: diff --git a/.gitea/workflows/deploy-site.yml b/.gitea/workflows/deploy-site.yml new file mode 100644 index 0000000..086957b --- /dev/null +++ b/.gitea/workflows/deploy-site.yml @@ -0,0 +1,29 @@ +name: Deploy Site + +on: + push: + tags: + - 'site-v[0-9]*.[0-9]*.[0-9]*' + workflow_dispatch: + +concurrency: + group: deploy-site + cancel-in-progress: false + +jobs: + deploy-site: + runs-on: nas + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Compile (Astro 官网, node:20 容器内构建) + run: | + docker run --rm -v "$PWD:/w" -w /w \ + -e SITE_URL=https://pangolin.yanmeiai.com \ + node:20 bash scripts/ci/compile-site.sh + + - name: Deploy → pangolin1 + env: + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + run: bash scripts/ci/deploy-site.sh diff --git a/scripts/ci/compile-site.sh b/scripts/ci/compile-site.sh new file mode 100755 index 0000000..8ab204e --- /dev/null +++ b/scripts/ci/compile-site.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# compile-site.sh — build the Astro 官网 (web/website) as a static site. +# Output: web/website/dist/. Canonical domain is injected via SITE_URL (see +# web/website/astro.config.mjs) — must match the deploy target host name so +# canonical URLs / sitemap resolve correctly. +# +# Run inside a node:20 container by .gitea/workflows/deploy-site.yml; this +# script itself just runs npm and assumes it is invoked from the repo root. +set -euo pipefail + +SITE_URL="${SITE_URL:-https://pangolin.yanmeiai.com}" +export SITE_URL +echo "==> compile-site: SITE_URL=${SITE_URL}" + +cd web/website +npm ci +npm run build + +echo "==> compile-site: done — dist/ contents:" +ls -lh dist/ diff --git a/scripts/ci/deploy-site.sh b/scripts/ci/deploy-site.sh new file mode 100755 index 0000000..b83ff25 --- /dev/null +++ b/scripts/ci/deploy-site.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# deploy-site.sh — rsync the built Astro 官网 (web/website/dist/) to the +# pangolin1 VPS web root (/var/www/pangolin-site). Deploy user is root — the +# CI deploy key (secrets.DEPLOY_SSH_KEY) is authorized for root on the host. +# Static files only; nginx/caddy serves them directly, no service restart +# needed. Assumes it is invoked from the repo root, after compile-site.sh has +# produced web/website/dist/. +set -euo pipefail + +# shellcheck source=scripts/ci/lib-ssh.sh +. scripts/ci/lib-ssh.sh + +setup_ssh +trap teardown_ssh EXIT + +DEPLOY_TARGET="root@${DEPLOY_HOST}:/var/www/pangolin-site/" +echo "==> deploy-site: rsync web/website/dist/ -> ${DEPLOY_TARGET}" +rsync -az --delete -e "${RSYNC_SSH}" web/website/dist/ "${DEPLOY_TARGET}" + +echo "==> deploy-site: done" diff --git a/scripts/ci/lib-ssh.sh b/scripts/ci/lib-ssh.sh new file mode 100755 index 0000000..e36ce72 --- /dev/null +++ b/scripts/ci/lib-ssh.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# lib-ssh.sh — shared SSH deploy-key helpers for the pangolin deploy pipelines +# (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. +# +# Requires env: DEPLOY_SSH_KEY (PEM content of the deploy private key, +# authorized for root on the target host). +# +# Target host is hardcoded to the pangolin1 VPS IP (103.119.13.48): the CI +# runner has no access to the user's local ~/.ssh/config, so the `pangolin1` +# alias cannot be resolved there — the bare IP is used instead. Override with +# DEPLOY_HOST / DEPLOY_PORT if a caller needs to retarget. +# +# No command substitution ($()) is used anywhere, per repo bash conventions. + +DEPLOY_HOST="${DEPLOY_HOST:-103.119.13.48}" +DEPLOY_PORT="${DEPLOY_PORT:-22}" +SSH_KEY_FILE="${SSH_KEY_FILE:-/tmp/pangolin_deploy_key.$$}" + +# setup_ssh — write the deploy key, register known_hosts, export SSH/RSYNC_SSH. +setup_ssh() { + if [ -z "${DEPLOY_SSH_KEY:-}" ]; then + echo "==> setup_ssh: DEPLOY_SSH_KEY is empty" >&2 + return 1 + fi + + mkdir -p ~/.ssh + chmod 700 ~/.ssh + + # `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 + + 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}" +} + +# teardown_ssh — remove the temp private key. +teardown_ssh() { + rm -f "${SSH_KEY_FILE}" + echo "==> teardown_ssh: removed ${SSH_KEY_FILE}" +}