部署与 CI/CD
当前 Astro 站点的 CI/CD 配置说明
记录当前项目通过 Gitee Go 构建、下发压缩包、Linux 自动解压并由 Nginx 托管静态页面的完整流程。
- CI/CD
- Gitee Go
- Linux
- Nginx
- Astro
这篇文档记录当前站点的实际 CI/CD 配置。目标是本地提交代码后,Gitee 自动构建 Astro 静态产物并同步到 Linux 服务器,再由服务器自动解压到 Nginx 网站目录。
整体链路
当前部署链路是:
本地 git push origin main
-> Gitee Go Push 触发流水线
-> Node 24 环境通过 pnpm 安装依赖并执行 pnpm run build
-> 把 ./dist 打成 output.tar.gz
-> Gitee Go 主机部署把压缩包下发到服务器
-> 服务器执行 /opt/hal-astro/deploy.sh
-> 解压 dist 并 rsync 到 /usr/share/nginx/html
-> Nginx 直接托管静态页面
服务器上还有一层 systemd.path 监听兜底:只要 Gitee Go 更新了 output.tar.gz,系统也会自动触发部署脚本。
Gitee 流水线配置
流水线文件在仓库的 .workflow/ 目录下,当前为:
.workflow/流水线-202607132148.yml
文件名带时间戳,在 Gitee 上重新保存流水线后会生成新的 yml,以 .workflow/ 目录下实际文件为准。
当前流水线分为两个阶段:
- 构建:使用 Node 24.13.0,通过 corepack 激活 pnpm,安装依赖并构建 Astro。
- 部署:把构建产物下发到服务器,并执行部署脚本。
关键配置如下,已与仓库 .workflow/流水线-202607132148.yml 核对一致,省略了部分展示名和策略等元数据:
stages:
- name: 构建
displayName: 构建
steps:
- step: build@nodejs
displayName: Nodejs 构建
nodeVersion: 24.13.0
commands:
- npm config set registry https://registry.npmmirror.com
- corepack enable
- corepack prepare pnpm@10.33.0 --activate
- rm -rf dist
- pnpm install --frozen-lockfile
- pnpm run build
artifacts:
- name: BUILD_ARTIFACT
path:
- ./dist
type: .tar.gz
caches:
- ~/.local/share/pnpm
- name: 部署
displayName: 部署
steps:
- step: deploy@agent
displayName: 主机部署
deployArtifact:
- name: output
target: "~/gitee_go/deploy"
source: build
dependArtifact: BUILD_ARTIFACT
artifactRepository: default
artifactName: output
artifactVersion: latest
script: |-
bash /opt/hal-astro/deploy.sh
hostGroupID:
ID: hal-astro
构建使用 pnpm 而非 npm:通过 corepack 激活 package.json 里 packageManager 声明的 pnpm 版本,依赖用 pnpm install --frozen-lockfile 按 pnpm-lock.yaml 锁定安装,缓存目录 ~/.local/share/pnpm 用于加速后续构建。部署阶段把产物下发到主机组 hal-astro 对应的服务器。
触发条件是 main 分支 Push:
triggers:
push:
branches:
precise:
- main
只要本地执行:
git add .
git commit -m "更新内容"
git push origin main
Gitee Go 就会自动构建和部署。
构建产物位置
Gitee Go 的构建阶段把 Astro 的 dist 目录打成压缩包,部署阶段会把压缩包下发到服务器:
/root/gitee_go/deploy/output.tar.gz
可以在服务器上查看压缩包内容:
tar -tzf /root/gitee_go/deploy/output.tar.gz | sed -n '1,80p'
正常情况下能看到:
./dist/
./dist/index.html
./dist/topics/
./dist/docs/
./dist/about/
如果压缩包里出现了已删除的目录(例如本次清理掉的 docs/astro/markdown-guide/),说明 Gitee 的构建产物不是最新代码,需要检查流水线是否执行到了最新提交。
Linux 部署脚本
服务器部署脚本在:
/opt/hal-astro/deploy.sh
脚本按以下顺序执行:
- 等待
output.tar.gz写入稳定,避免文件还没传完就解压。 - 用
flock加锁,避免多个部署同时执行。 - 校验压缩包是否是合法
tar.gz。 - 解压到临时目录
/tmp/hal-astro-release。 - 找到
dist目录。 - 用
rsync --delete同步到 Nginx 根目录。 - 修正文件权限。
脚本内容:
#!/usr/bin/env bash
set -euo pipefail
ARTIFACT="/root/gitee_go/deploy/output.tar.gz"
TMP_DIR="/tmp/hal-astro-release"
WEB_DIR="/usr/share/nginx/html"
LOCK_FILE="/run/hal-astro-deploy.lock"
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
wait_for_stable_artifact() {
local previous_size=""
local stable_count=0
for _ in $(seq 1 30); do
if [ ! -f "$ARTIFACT" ]; then
sleep 1
continue
fi
local current_size
current_size="$(stat -c '%s' "$ARTIFACT")"
if [ "$current_size" = "$previous_size" ]; then
stable_count=$((stable_count + 1))
if [ "$stable_count" -ge 3 ]; then
return 0
fi
else
stable_count=0
previous_size="$current_size"
fi
sleep 1
done
log "Artifact did not become stable in time: $ARTIFACT"
return 1
}
main() {
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
log "Deploy is already running, skip this trigger."
exit 0
fi
log "Deploy started."
wait_for_stable_artifact
if ! tar -tzf "$ARTIFACT" >/dev/null; then
log "Artifact is not a valid tar.gz: $ARTIFACT"
exit 1
fi
rm -rf "$TMP_DIR"
mkdir -p "$TMP_DIR" "$WEB_DIR"
tar -xzf "$ARTIFACT" -C "$TMP_DIR"
local source_dir
if [ -d "$TMP_DIR/dist" ]; then
source_dir="$TMP_DIR/dist"
elif [ -f "$TMP_DIR/index.html" ]; then
source_dir="$TMP_DIR"
else
log "No dist directory or index.html found after extraction."
find "$TMP_DIR" -maxdepth 3 -print
exit 1
fi
rsync -a --delete "$source_dir/" "$WEB_DIR/"
find "$WEB_DIR" -type d -exec chmod 755 {} +
find "$WEB_DIR" -type f -exec chmod 644 {} +
restorecon -R "$WEB_DIR" 2>/dev/null || true
log "Deploy finished: $source_dir -> $WEB_DIR"
}
main "$@"
手动执行部署:
bash /opt/hal-astro/deploy.sh
查看发布后的文件:
find /usr/share/nginx/html -maxdepth 3 -type f | sort | sed -n '1,120p'
systemd 自动监听
服务器使用 systemd.path 监听压缩包变化。这样即使 Gitee Go 只是更新了 output.tar.gz,系统也会自动执行部署脚本。
Service 文件:
/etc/systemd/system/hal-astro-deploy.service
内容:
[Unit]
Description=Deploy HAL Astro static site from Gitee Go artifact
After=network.target nginx.service
[Service]
Type=oneshot
User=root
ExecStart=/opt/hal-astro/deploy.sh
Path 文件:
/etc/systemd/system/hal-astro-deploy.path
内容:
[Unit]
Description=Watch Gitee Go output.tar.gz for HAL Astro deploy
[Path]
PathChanged=/root/gitee_go/deploy
PathChanged=/root/gitee_go/deploy/output.tar.gz
PathModified=/root/gitee_go/deploy/output.tar.gz
Unit=hal-astro-deploy.service
[Install]
WantedBy=multi-user.target
启用监听:
systemctl daemon-reload
systemctl enable --now hal-astro-deploy.path
查看监听状态:
systemctl status hal-astro-deploy.path
查看部署日志:
journalctl -u hal-astro-deploy.service -n 80 --no-pager
持续查看日志:
journalctl -u hal-astro-deploy.service -f
Nginx 配置
当前 Nginx 站点配置文件:
/etc/nginx/conf.d/memolab.cn.conf
当前配置:
# memolab.cn site — points to default welcome page
server {
listen 80;
listen [::]:80;
server_name www.memolab.cn memolab.cn;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
关键点:
root /usr/share/nginx/html;必须和部署脚本里的WEB_DIR一致。try_files $uri $uri/ =404;适合当前 Astro 静态站点,因为 Astro 会生成真实目录和index.html。- 修改 Nginx 配置后,需要先测试再重载。
测试 Nginx 配置:
nginx -t
重载 Nginx:
systemctl reload nginx
查看 Nginx 状态:
systemctl status nginx
发布验证
一次完整发布后,在服务器上检查:
stat /root/gitee_go/deploy/output.tar.gz
tar -tzf /root/gitee_go/deploy/output.tar.gz | sed -n '1,80p'
journalctl -u hal-astro-deploy.service -n 40 --no-pager
检查 Nginx 本机访问:
curl -I http://127.0.0.1/
curl -I http://127.0.0.1/topics/
curl -I http://127.0.0.1/docs/nest/typescript/
正常情况下应该返回:
HTTP/1.1 200 OK
已经删除的页面会变成 404,例如本次清理掉的 docs/astro/markdown-guide/:
curl -I http://127.0.0.1/docs/astro/markdown-guide/
常见问题
流水线成功,但页面还是旧的
先检查服务器上的压缩包内容:
tar -tzf /root/gitee_go/deploy/output.tar.gz | sed -n '1,80p'
如果压缩包本身是旧内容,问题在 Gitee 构建阶段,需要检查流水线是否构建了最新提交。
如果压缩包是新内容,但 Nginx 目录还是旧内容,检查部署脚本日志:
journalctl -u hal-astro-deploy.service -n 80 --no-pager
output.tar.gz 更新了,但没有自动解压
检查 path 是否在运行:
systemctl status hal-astro-deploy.path
如果没有运行,重新启用:
systemctl daemon-reload
systemctl enable --now hal-astro-deploy.path
Nginx 返回 404
先确认文件是否存在:
find /usr/share/nginx/html -maxdepth 3 -type f | sort | sed -n '1,120p'
再确认 Nginx root 是否还是:
root /usr/share/nginx/html;
如果 root 指向了别的目录,即使部署成功,Nginx 也不会读到最新页面。
不要把密钥写进文档
文档和仓库里不应该保存服务器密码、AccessKey、Secret、私钥或 Token。
当前服务器连接使用本机 SSH key:
ssh alias: hal-astro-server
identity file: ~/.ssh/hal_astro_server_ed25519
这里记录的是本机连接方式,不记录私钥内容。