最终结构
anpier.cn:主域名。www.anpier.cn、blog.anpier.cn:301 跳转到主域名。- Nginx:提供 Astro 静态文件,并把
/api/转发到 Node.js。 - Node.js:提供浏览量和点赞接口,只监听
127.0.0.1:8787。 - SQLite:保存统计数据,文件为
/var/lib/anpier/stats.db。 - systemd:守护统计服务。
- GitHub Actions:推送
main后自动构建、上传和切换版本。 - Cloudflare:只负责 DNS,不再使用 Pages、Functions 和 D1。
服务器购买于雨云,系统镜像为 Debian 12 + 宝塔 LNMP,运行 Node.js 22。所有 SERVER_IP 都要替换成服务器公网 IP。
一、准备服务器
以 root 登录服务器:
apt updateapt install -y ca-certificates curl git openssl rsync sqlite3 sudo
curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.shbash /tmp/nodesource_setup.shapt install -y nodejs
node -vsqlite3 --version创建部署用户、服务用户和目录:
adduser --disabled-password --gecos "" deployuseradd --system --user-group --no-create-home \ --home-dir /opt/anpier-api --shell /usr/sbin/nologin anpier
install -d -o deploy -g deploy -m 0755 /var/www/anpierinstall -d -o deploy -g deploy -m 0755 /var/www/anpier/releasesinstall -d -o deploy -g deploy -m 0755 /opt/anpier-apiinstall -d -o deploy -g deploy -m 0755 /opt/anpier-api/releasesinstall -d -o anpier -g anpier -m 0750 /var/lib/anpierinstall -d -o root -g anpier -m 0750 /etc/anpierinstall -d -o root -g root -m 0750 /var/backups/anpierinstall -d -o deploy -g deploy -m 0700 /home/deploy/.ssh二、配置自动部署 SSH
在 Windows PowerShell 生成 GitHub Actions 专用密钥:
$ServerIp = "SERVER_IP"ssh-keygen -t ed25519 -C "anpier-github-actions" ` -f "$env:USERPROFILE\.ssh\anpier_github_actions"公钥可以放到服务器,私钥不能上传到仓库或服务器:
Get-Content "$env:USERPROFILE\.ssh\anpier_github_actions.pub" | ` ssh root@$ServerIp "cat >> /home/deploy/.ssh/authorized_keys"回到服务器设置权限:
chown deploy:deploy /home/deploy/.ssh/authorized_keyschmod 600 /home/deploy/.ssh/authorized_keys测试部署账户:
ssh -i "$env:USERPROFILE\.ssh\anpier_github_actions" deploy@$ServerIp在服务器查看主机密钥指纹:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub在 Windows 获取同一指纹并核对:
ssh-keyscan -t ed25519 $ServerIp 2>$null | ssh-keygen -lf -三、安装统计服务
仓库内已经包含以下文件:
deploy/anpier-api.servicedeploy/api.env.exampleserver/scripts/activate-release.sh.github/workflows/deploy.yml在仓库目录上传 systemd 配置:
scp .\deploy\anpier-api.service root@${ServerIp}:/tmp/anpier-api.service在服务器安装服务并创建环境变量:
install -o root -g root -m 0644 \ /tmp/anpier-api.service /etc/systemd/system/anpier-api.service
install -o root -g anpier -m 0640 /dev/null /etc/anpier/api.envhash_secret="$(openssl rand -hex 32)"printf '%s\n' \ 'ANPIER_HOST=127.0.0.1' \ 'ANPIER_PORT=8787' \ 'ANPIER_DB_PATH=/var/lib/anpier/stats.db' \ "ANPIER_HASH_SECRET=$hash_secret" \ 'ANPIER_ALLOWED_ORIGINS=https://anpier.cn,https://www.anpier.cn,https://blog.anpier.cn' \ > /etc/anpier/api.envunset hash_secret
systemctl daemon-reloadsystemctl enable anpier-api.service只允许 deploy 重启统计服务:
printf '%s\n' \ 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart anpier-api.service' \ > /etc/sudoers.d/anpier-deploychmod 0440 /etc/sudoers.d/anpier-deployvisudo -cf /etc/sudoers.d/anpier-deploy四、配置 GitHub Actions
打开 GitHub 仓库:
Settings -> Environments -> New environment -> production在 production 的 Environment secrets 中添加:
| 名称 | 值 |
|---|---|
DEPLOY_HOST | 服务器公网 IP |
DEPLOY_PORT | 22 |
DEPLOY_USER | deploy |
DEPLOY_SSH_KEY | anpier_github_actions 私钥的完整内容 |
SSH_KNOWN_HOSTS | 服务器的 ED25519 主机密钥行 |
在 Windows 把私钥送入剪贴板,再粘贴到 DEPLOY_SSH_KEY:
Get-Content -Raw "$env:USERPROFILE\.ssh\anpier_github_actions" | Set-Clipboard把主机密钥送入剪贴板,再粘贴到 SSH_KNOWN_HOSTS:
ssh-keyscan -t ed25519 $ServerIp 2>$null | Set-Clipboard推送一次代码,首次部署会自动创建 current 软链接并启动 API:
git push origin main确认 GitHub Actions 中以下任务全部通过:
Deploy to VPSBuild and CheckCode quality服务器验证:
systemctl status anpier-api.service --no-pagercurl --fail http://127.0.0.1:8787/api/healthzls -l /var/www/anpier/current /opt/anpier-api/current五、配置 Nginx 和 HTTPS
在宝塔中新建站点,添加三个域名:
anpier.cnwww.anpier.cnblog.anpier.cn网站目录设为:
/var/www/anpier/current不需要创建 MySQL 数据库,也不需要 PHP。
在站点配置的 server 块中加入:
if ($host != 'anpier.cn') { return 301 https://anpier.cn$request_uri;}
location ^~ /api/ { proxy_pass http://127.0.0.1:8787; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 5s; proxy_read_timeout 15s;}在宝塔的 SSL 页面选择 Let’s Encrypt,同时申请以下三个域名的免费证书,然后开启强制 HTTPS:
anpier.cnwww.anpier.cnblog.anpier.cn不需要购买商业 SSL 证书。证书必须包含三个域名,否则别名在跳转前会先出现证书错误。
检查并重载 Nginx:
/www/server/nginx/sbin/nginx -t/etc/init.d/nginx reload六、切换 DNS
域名在阿里云购买,但 NS 已指向 Cloudflare,因此 DNS 记录在 Cloudflare 修改。
添加以下记录,代理状态先设为 DNS only:
| 类型 | 名称 | 内容 |
|---|---|---|
A | @ | 服务器公网 IP |
CNAME | www | anpier.cn |
CNAME | blog | anpier.cn |
验证:
Resolve-DnsName anpier.cncurl.exe -I http://anpier.cn/about/curl.exe -I https://www.anpier.cn/about/curl.exe -I https://blog.anpier.cn/about/curl.exe https://anpier.cn/api/healthz预期结果:HTTP 跳转 HTTPS,www 和 blog 跳转 anpier.cn,健康接口返回 {"status":"ok"}。
七、迁移浏览量和点赞
第一次部署后数据库会自动创建。迁移前先停止 API 并备份:
systemctl stop anpier-api.servicebackup_file="/var/backups/anpier/stats-before-import-$(date +%Y%m%d-%H%M%S).db"sqlite3 /var/lib/anpier/stats.db ".backup '$backup_file'"chmod 600 "$backup_file"本次迁移使用浏览量 1203、点赞 280:
sudo -u anpier sqlite3 /var/lib/anpier/stats.db "BEGIN IMMEDIATE;INSERT INTO counters (name, value) VALUES ('site_total', 1203) ON CONFLICT(name) DO UPDATE SET value = excluded.value;INSERT INTO counters (name, value) VALUES ('site_like', 280) ON CONFLICT(name) DO UPDATE SET value = excluded.value;DELETE FROM action_locks;COMMIT;"
systemctl start anpier-api.servicecurl --fail http://127.0.0.1:8787/api/healthzsqlite3 /var/lib/anpier/stats.db \ "SELECT name, value FROM counters ORDER BY name;"浏览量按访客 12 小时去重;点赞每次点击都会增加,后端限制每个 IP 每小时最多 120 次。
八、配置数据库备份
创建备份脚本:
tee /usr/local/sbin/backup-anpier-db >/dev/null <<'EOF'#!/usr/bin/env bashset -Eeuo pipefail
backup_dir=/var/backups/anpierdatabase=/var/lib/anpier/stats.dbtimestamp=$(date +%Y%m%d-%H%M%S)
umask 077sqlite3 "$database" ".backup '$backup_dir/stats-$timestamp.db'"find "$backup_dir" -type f -name 'stats-*.db' -mtime +30 -deleteEOF
chmod 0750 /usr/local/sbin/backup-anpier-db每天 03:30 备份:
printf '%s\n' \ '30 3 * * * root /usr/local/sbin/backup-anpier-db' \ > /etc/cron.d/anpier-backupchmod 0644 /etc/cron.d/anpier-backup
/usr/local/sbin/backup-anpier-dbls -lh /var/backups/anpier九、配置管理账户
生成管理员密钥:
ssh-keygen -t ed25519 -C "anpier-admin" ` -f "$env:USERPROFILE\.ssh\anpier_admin"服务器创建管理员账户:
adduser adrianusermod -aG sudo adrianinstall -d -o adrian -g adrian -m 0700 /home/adrian/.ssh从 Windows 安装管理员公钥:
Get-Content "$env:USERPROFILE\.ssh\anpier_admin.pub" | ` ssh root@$ServerIp "cat >> /home/adrian/.ssh/authorized_keys"服务器修正权限:
chown adrian:adrian /home/adrian/.ssh/authorized_keyschmod 600 /home/adrian/.ssh/authorized_keys先保持原 SSH 会话,另开窗口测试:
ssh -i "$env:USERPROFILE\.ssh\anpier_admin" adrian@$ServerIp确认密钥登录和 sudo 正常后,再关闭 root 和密码远程登录:
printf '%s\n' \ 'PermitRootLogin no' \ 'PasswordAuthentication no' \ 'PubkeyAuthentication yes' \ > /etc/ssh/sshd_config.d/00-anpier.conf
sshd -tsystemctl reload sshsshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication) '十、日常发布
修改文章或代码后,在 Windows 仓库目录执行:
pnpm.cmd checkpnpm.cmd api:testpnpm.cmd build
git statusgit add .git commit -m "更新博客"git push origin main推送完成后不需要手动上传文件。GitHub Actions 会构建静态站点、测试 API、上传新版本、切换 current 软链接并重启服务;健康检查失败时会自动恢复上一个版本。
十一、换电脑后的操作
先在旧电脑或加密备份中保存管理员私钥:
C:\Users\用户名\.ssh\anpier_adminC:\Users\用户名\.ssh\anpier_admin.pubGitHub Actions 的部署私钥已经保存在 GitHub,新电脑发布文章不需要该私钥。不要把任何私钥提交到仓库。
新电脑安装 Git、Node.js 22、pnpm 9.14.4 和 VS Code,然后执行:
git config --global user.name "你的 GitHub 用户名"git config --global user.email "你的 GitHub 邮箱"
corepack enablecorepack prepare pnpm@9.14.4 --activate
Set-Location C:\Bloggit clone https://github.com/Adrian-Pierre/anpier_blog.gitSet-Location C:\Blog\anpier_blogpnpm.cmd install --frozen-lockfilepnpm.cmd checkpnpm.cmd build把备份的 anpier_admin 和 anpier_admin.pub 放回新电脑的 .ssh 目录,即可管理服务器:
ssh -i "$env:USERPROFILE\.ssh\anpier_admin" adrian@SERVER_IP如果管理员私钥已经丢失,通过雨云控制台的远程连接进入服务器,生成一对新密钥,并把新公钥加入:
/home/adrian/.ssh/authorized_keys十二、故障检查
# API 状态和日志systemctl status anpier-api.service --no-pagerjournalctl -u anpier-api.service -n 100 --no-pager
# 本机 APIcurl --fail http://127.0.0.1:8787/api/healthzcurl http://127.0.0.1:8787/api/site-totalcurl http://127.0.0.1:8787/api/site-like
# Nginx/www/server/nginx/sbin/nginx -ttail -n 100 /www/wwwlogs/anpier.cn.error.log
# 当前版本readlink /var/www/anpier/currentreadlink /opt/anpier-api/current
# 数据库sqlite3 /var/lib/anpier/stats.db \ "SELECT name, value FROM counters ORDER BY name;"不要使用 POST /api/site-like 做普通检查,因为每次 POST 都会真实增加点赞数。