个人服务器架构整改记录
背景
这台腾讯云轻量服务器跑着个人博客(Next.js + MDX),还跑着 OpenClaw 智能助手。一开始权限设计比较随意,用户各管各的,没有统一的协作机制。
主要问题:
- 博客源码在
/root/blog/(root 属主),但 systemd 服务指向空的/home/blog/blog/ - blog 用户有
ProtectSystem=full,不能写内容文件 - Next.js 绑定
0.0.0.0:3000,公网可直接访问后端 - root 下残存一个 PM2 守护进程在跑旧博客,跟 systemd 服务打架
- 用户间没有共享组,无法协作
目标架构
┌──────────────────────────────────────────┐
│ 公网 │
├──────────────────────────────────────────┤
│ UFW 防火墙 │
│ 22(SSH) / 80(HTTP) / 443(HTTPS) │
├──────────────────────────────────────────┤
│ Nginx (www-data) │
│ 反向代理 /blog → :3000 │
├──────────────────────────────────────────┤
│ Next.js (blog 用户) 127.0.0.1:3000 │
│ /var/www/blog/ │
├──────────────────────────────────────────┤
│ openclaw → webadmins 组 → 管理内容 │
│ ubuntu → sudo 组 → 系统管理 │
└──────────────────────────────────────────┘
用户分工
| 用户 | UID | 角色 | 权限范围 |
|---|---|---|---|
root | 0 | 系统管理员 | 仅装系统、改 Nginx、初始化项目 |
ubuntu | 1000 | 人类管理员 | sudo 权限,做一切系统级操作 |
openclaw | 996 | AI 助手 | 管理博客内容、重启服务 |
blog | 999 | 博客服务 | 只跑 Next.js(nologin,不能登录) |
www-data | 33 | Web 服务器 | 只做 Nginx 反代 |
组策略
新建 webadmins 共享组(GID 1003),包含 blog 和 openclaw 两个用户。
权限模型:
/var/www/blog/ 775 root:webadmins ← 目录组可读写
├── src/ 775 root:webadmins ← 代码只读
├── content/posts/ 2775 root:webadmins ← 文章可写(SGID)
├── public/ 2775 root:webadmins ← 资源可写(SGID)
└── .next/ 775 root:webadmins ← 构建产物可写
2775 的 SGID 位确保在该目录下新建的文件自动继承组身份,不用每次手动 chgrp。
关键技术变更
1. 博客迁移
把代码从 /root/blog/ 搬到标准的 /var/www/blog/:
sudo cp -a /root/blog /var/www/blogsudo chgrp -R webadmins /var/www/blogsudo find /var/www/blog -type d -exec chmod 775 {} +sudo find /var/www/blog -type f -exec chmod 664 {} +sudo chmod g+rwxs /var/www/blog/contentsudo chmod g+rwxs /var/www/blog/public
2. blog.service 重构
[Service]User=blogGroup=blogWorkingDirectory=/var/www/blogExecStart=/usr/local/bin/node /var/www/blog/node_modules/.bin/next start --hostname 127.0.0.1 --port 3000Restart=alwaysRestartSec=5NoNewPrivileges=truePrivateTmp=trueReadWritePaths=/var/www/blog/content /var/www/blog/public
关键改动:
- 工作目录从
/home/blog/blog(空目录)改为/var/www/blog - 去掉
ProtectSystem=full,改用ReadWritePaths白名单 - 加
--hostname 127.0.0.1防止公网直连
3. PM2 清理
旧博客是用 PM2 以 root 身份启动的,跟 systemd 服务冲突(争抢 3000 端口)。清理:
pm2 stop all && pm2 delete allsystemctl stop pm2-root.servicesystemctl disable pm2-root.service
4. openclaw sudo 白名单
AI 助手只需要两条 sudo 命令:
openclaw ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart blogopenclaw ALL=(ALL) NOPASSWD: /usr/sbin/nginx -s reload
同时关掉了 openclaw-gateway.service 中的 NoNewPrivileges=true,允许 sudo 提权。
最终验证
博客访问: ✅ http://kolass.site/blog/ → 200
端口安全: ✅ Next.js 只绑 127.0.0.1:3000
内容管理: ✅ openclaw 用户可读写 content/
服务状态: ✅ blog.service active (running)
后续计划
- HTTPS — 用 Let's Encrypt / Certbot 配置 SSL 证书
- 在线编辑器 — 给博客加一个 Markdown 在线编辑界面
- 监控告警 — 服务异常时自动通知
新应用接入规范
以后这台服务器上要加新应用/新服务时,请遵循以下规范,保持架构整洁。
1. 目录规范
所有应用代码统一放在 /var/www/ 下,不要到处放:
/var/www/
├── blog/ # 个人博客
├── blog-admin/ # (计划中)在线编辑器
└── your-next-app/ # 你的新应用放这里
禁止放的位置:
- ❌
/root/— 这是 root 的家目录,不是项目目录 - ❌
/home/xxx/— 这是用户家目录,不是项目目录 - ❌
/opt/— 除非有特殊原因,否则不推荐
2. 用户规范
每个应用一个专用服务账号:
应用 用户 组 说明
blog blog blog+webadmins 博客服务
你的新应用 app-xxx app-xxx 新建专用用户
创建命令:
sudo useradd -r -s /usr/sbin/nologin -M app-你的应用名
关键原则:
- 服务账号的 shell 必须是
/usr/sbin/nologin(不能登录) - 不需要家目录(
-M) - 如果多个应用需要共享数据,加到
webadmins组
3. systemd 规范
每个应用必须有 systemd 服务文件,不允许用 PM2 或其他进程管理器直接跑:
[Unit]Description=你的应用描述After=network.target [Service]Type=simpleUser=app-你的应用名Group=app-你的应用名WorkingDirectory=/var/www/你的应用ExecStart=/usr/local/bin/node /var/www/你的应用/dist/index.jsRestart=alwaysRestartSec=5NoNewPrivileges=truePrivateTmp=true [Install]WantedBy=multi-user.target
规范要求:
- ✅
NoNewPrivileges=true— 防止权限提升 - ✅
PrivateTmp=true— 隔离临时文件 - ✅
Restart=always— 崩溃自动重启 - ✅ 用户必须是专用服务账号
- ❌ 不要用 root 跑应用
- ❌ 不要用 PM2(除非有明确理由)
4. 端口规范
端口 应用 绑定地址
3000 Next.js 博客 127.0.0.1
其他端口 你的新应用 127.0.0.1
规则:
- 所有后端应用一律绑定
127.0.0.1,不得暴露到公网 - 统一通过 Nginx 反向代理对外暴露
- 端口号从 3000 开始递增(3000, 3001, 3002...)
- Nginx 配置统一放在
/etc/nginx/sites-available/
5. Nginx 反代规范
server { listen 80; server_name your-domain.com; location /your-app { proxy_pass http://127.0.0.1:3001; 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; }}
所有 site 配置放在 /etc/nginx/sites-available/,启用时软链接到 sites-enabled/。
6. 防火墙规范
UFW 默认 deny incoming,只开放明确需要的端口:
sudo ufw allow 22/tcp # SSHsudo ufw allow 80/tcp # HTTPsudo ufw allow 443/tcp # HTTPS
新应用如果有独立域名且需要 HTTPS,先在 UFW 开好 80/443 端口(通常已开),然后通过 Nginx 反代出去。不要为每个应用单独开端口。
7. 权限速查
| 场景 | 命令 |
|---|---|
| 新建应用目录 | sudo mkdir -p /var/www/你的应用 |
| 设置属组 | sudo chgrp -R webadmins /var/www/你的应用 |
| 目录权限 | sudo find /var/www/你的应用 -type d -exec chmod 755 {} + |
| 文件权限 | sudo find /var/www/你的应用 -type f -exec chmod 644 {} + |
| 可写目录 | sudo chmod g+rwxs /var/www/你的应用/data |
| 建服务账号 | sudo useradd -r -s /usr/sbin/nologin -M app-你的应用名 |
| 加到共享组 | sudo usermod -aG webadmins app-你的应用名 |
8. 完整示例:部署一个 Node.js API 服务
# 1. 建用户sudo useradd -r -s /usr/sbin/nologin -M app-apisudo usermod -aG webadmins app-api # 2. 放代码sudo mkdir -p /var/www/apisudo cp -r ./my-api/* /var/www/api/sudo chgrp -R webadmins /var/www/apisudo find /var/www/api -type d -exec chmod 755 {} +sudo find /var/www/api -type f -exec chmod 644 {} + # 3. 写 systemd 服务sudo tee /etc/systemd/system/api.service << 'EOF'[Unit]Description=My API ServiceAfter=network.target [Service]Type=simpleUser=app-apiGroup=app-apiWorkingDirectory=/var/www/apiExecStart=/usr/local/bin/node /var/www/api/dist/index.jsRestart=alwaysRestartSec=5NoNewPrivileges=truePrivateTmp=true [Install]WantedBy=multi-user.targetEOF # 4. 启动sudo systemctl daemon-reloadsudo systemctl enable --now api.service # 5. 配 Nginx 反代(如果需要对外暴露)# 在 /etc/nginx/sites-available/ 加配置,软链到 sites-enabled/# 然后 sudo nginx -t && sudo systemctl reload nginx
9. Git 同步与部署工作流
博客与 Gitee 仓库双向同步,部署需要管理员审批。
架构
Gitee 仓库 (koala_755/blog) 服务器 /var/www/blog/
│ │
│ Push master │
▼ │
Webhook → /webhook │
│ │
▼ │
写部署审批文件 │
/tmp/blog-deploy-pending.json │
│ │
▼ │
OpenClaw 定时检查 (每2分钟) │
│ │
▼ │
发微信通知局长 → 局长审批 │
│ │
├── 同意 → git pull + build + restart │
└── 拒绝 → 标记已拒绝 │
写文章规则
| 场景 | 操作 |
|---|---|
| 明确要求写文章 | 直接写 → commit → push master → 通知局长审批部署 |
| 我觉得该写但没说 | 先说明原因 → 局长同意后才写 → 同上流程 |
| 在 Gitee 上修改/合并 | Webhook 触发 → 局长审批 → 部署 |
部署审批
每次 master 分支有推送后,不会自动构建上线,而是:
- Webhook 记录推送信息
- OpenClaw 每 2 分钟检查一次
- 有待部署时,发微信问局长
- 局长回复同意 → 自动拉取 + 构建 + 重启
- 局长回复拒绝 → 标记已拒绝,不部署
10. 禁止清单
以下操作被列为红线,任何时候都不应该做:
- ❌ 把项目代码放在
/root/下 - ❌ 用 root 用户跑应用服务
- ❌ 数据库/配置文件用默认密码
- ❌ 端口绑定
0.0.0.0(必须用127.0.0.1) - ❌ 不给服务设置
NoNewPrivileges - ❌ 用 PM2 代替 systemd 管理服务