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}" +}