个人服务器架构整改记录

上岸鸭运维

背景

这台腾讯云轻量服务器跑着个人博客(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角色权限范围
root0系统管理员仅装系统、改 Nginx、初始化项目
ubuntu1000人类管理员sudo 权限,做一切系统级操作
openclaw996AI 助手管理博客内容、重启服务
blog999博客服务只跑 Next.js(nologin,不能登录)
www-data33Web 服务器只做 Nginx 反代

组策略

新建 webadmins 共享组(GID 1003),包含 blogopenclaw 两个用户。

权限模型:

/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)

后续计划

  1. HTTPS — 用 Let's Encrypt / Certbot 配置 SSL 证书
  2. 在线编辑器 — 给博客加一个 Markdown 在线编辑界面
  3. 监控告警 — 服务异常时自动通知

新应用接入规范

以后这台服务器上要加新应用/新服务时,请遵循以下规范,保持架构整洁。

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 分支有推送后,不会自动构建上线,而是:

  1. Webhook 记录推送信息
  2. OpenClaw 每 2 分钟检查一次
  3. 有待部署时,发微信问局长
  4. 局长回复同意 → 自动拉取 + 构建 + 重启
  5. 局长回复拒绝 → 标记已拒绝,不部署

10. 禁止清单

以下操作被列为红线,任何时候都不应该做:

  • ❌ 把项目代码放在 /root/
  • ❌ 用 root 用户跑应用服务
  • ❌ 数据库/配置文件用默认密码
  • ❌ 端口绑定 0.0.0.0(必须用 127.0.0.1
  • ❌ 不给服务设置 NoNewPrivileges
  • ❌ 用 PM2 代替 systemd 管理服务