目录布局、验证与日常运维
/srv 是服务运行区
每个服务使用独立目录。新建项目时,先查看 /srv,确认目标目录尚不存在:
ls -ld /srv
sudo find /srv -mindepth 1 -maxdepth 2 -printf '%M %u:%g %p\n'
# 仅在路径不存在、不是符号链接或挂载点时新建。
sudo mkdir -- /srv/example-api &&
sudo chown -- "$VPS_USER:$VPS_USER" /srv/example-api
这里故意不给 mkdir 加 -p:已有路径会使创建失败,&& 随之阻止修改所有权。确认 $VPS_USER 是预期的部署账户,且同名用户组确实存在。多人共用的主机还要防止检查与创建之间发生并发修改。
不要对整个 /srv 执行 chown -R。递归修改所有权会影响服务数据,而不只是配置文件;容器卷可能要求特定的数字 UID/GID,备份也可能需要仅限 root 访问。已有项目必须先查清服务要求,再修改明确指定的路径。空的 /srv/backups 不是备份系统,还需要备份计划、版本、异地副本和恢复演练。
systemd 的三个常用问题
systemctl is-active docker # 现在是否正在运行?
systemctl is-enabled docker # 下次开机是否自动启动?
systemctl status docker --no-pager
active与enabled互不替代。服务故障时:
sudo journalctl -u docker --since today --no-pager
sudo journalctl -u tailscaled --since today --no-pager
sudo journalctl -u ssh --since today --no-pager
systemctl --failed --no-pager
日志可能包含 IP、用户名、路径或应用数据,公开前先脱敏。
软件更新例行流程
sudo apt update
apt list --upgradable
sudo apt upgrade
先阅读升级摘要。如果需要自动化,可先用 apt-get -s模拟。Ubuntu phased updates 会让一部分普通更新暂时保持不升级;这是分批发布,不应与依赖冲突混为一谈。
检查重启需求:
if [ -f /var/run/reboot-required ]; then
cat /var/run/reboot-required
else
echo "No reboot required"
fi
需要重启时,先验证当前 OpenSSH-over-Tailscale,保留会话,执行 sudo reboot,等待机器返回后重新连接并复查内核与失败服务。
完整健康检查
hostnamectl
timedatectl
nproc
free -h
df -h /
ip -brief addr
ip route
tailscale status
tailscale ip -4
systemctl is-active tailscaled
sudo ufw status verbose
sudo ufw status numbered
sudo sshd -t
sudo sshd -T | grep -E \
'^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|authenticationmethods) '
docker --version
docker compose version
systemctl is-active docker
docker info --format \
'ServerVersion={{.ServerVersion}} Driver={{.Driver}} CgroupDriver={{.CgroupDriver}}'
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
docker compose -f /srv/caddy/compose.yaml ps
docker compose -f /srv/uptime-kuma/compose.yaml ps
docker compose -f /srv/rsshub/compose.yaml ps
docker network inspect infra-edge
systemctl --failed --no-pager
find /srv -maxdepth 2 -printf '%M %u:%g %p\n' | sort
将 SSH 有效值与仅公钥登录策略对照;若存在 Match 块,也要检查其适用条件。再从 WSL2 验证:
# 应成功
ssh -o ConnectTimeout=10 "$VPS_USER@$TAILSCALE_IPV4"
# 应失败
ssh -o ConnectionAttempts=1 -o ConnectTimeout=5 \
"$VPS_USER@$VPS_PUBLIC_IPV4"
# 两个公开 HTTPS 健康检查都应成功
curl --fail https://infra.example.com/healthz
curl --fail https://rss.example.com/healthz
# RSSHub 1200 不应从任何主机地址直接访问
curl --connect-timeout 5 --max-time 7 \
"http://$VPS_PUBLIC_IPV4:1200/"
curl --connect-timeout 5 --max-time 7 \
"http://$TAILSCALE_IPV4:1200/"
docker ps 中单独出现 1200/tcp 只表示镜像在容器内部监听;只有看到类似 0.0.0.0:1200->1200/tcp 才是主机发布。当前合格状态是:Caddy 发布 80/443,Kuma 精确发布 127.0.0.1:3001,RSSHub 没有任何主机绑定。
建议的检查节奏
故障处理优先级
- 保住访问:不要关闭最后一个工作会话。
- 停止扩大变更:不要连续尝试互相覆盖的“修复”。
- 确认层次:路由、端口、防火墙、daemon、认证逐层检查。
- 收集非敏感证据:状态、退出码、时间和最近日志。
- 恢复最近的安全状态:优先添加临时访问,而不是重置系统。
- 验证恢复:仍需一个全新连接或完整请求。
长时间操作可以放在 Tmux 中,避免本地网络抖动让升级或排障过程失去终端。Tmux 保护远程 shell 进程,但不能替代第二条 SSH 访问路径。
当前服务的操作边界
现在已经有三套互相独立的 Compose 项目:Caddy、Uptime Kuma 与 RSSHub。日常操作要从对应的 /srv/<service> 目录执行,先看 docker compose ps 与日志,再决定是否重启。不要为了排查一个应用而重启所有容器,也不要使用全局 Docker prune。
RSSHub 的镜像更新、ACCESS_KEY、真实路由验证和回滚步骤集中在 RSSHub 生产影子部署。既有 feed consumer 的切换、持久数据备份恢复与调度器迁移尚未发生,不能把“VPS RSSHub 已健康”误当成“下游已经迁移”。