使用Systemd托管挂机服务

随着我对Linux系统的理解逐渐加深,外加上这一年来管理服务器越来越习惯命令行,我逐渐感觉我之前在家里云部署的Mcsmanager应用管理面板是多余的,因为Tmux和Systemd都可以做到,并且因为不需要运行一个nodejs,还更胜资源。
所以,我下定决心把服务迁移到Systemd。

让Systemd以普通用户身份运行服务

之前我使用Mcsmanager而不是Systemd的原因,就是觉得Systemd配置比较麻烦,并且默认以root身份运行服务,我感觉很不安全。于是进行多次配置后,用以下的配置文件启动了Mcsmanager:
/usr/lib/systemd/system/mcsm-web.service

[Unit]
Description=MCSManager Web

[Service]
WorkingDirectory=/opt/mcsmanager/web
ExecStart=/usr/bin/node app.js
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
User=chocola

[Install]
WantedBy=multi-user.target

/usr/lib/systemd/system/mcsm-daemon.service

[Unit]
Description=MCSManager Daemon

[Service]
WorkingDirectory=/opt/mcsmanager/daemon
ExecStart=/usr/bin/node app.js
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
User=chocola

[Install]
WantedBy=multi-user.target

本来配置用的就是用户身份,我在想,为什么不直接就用Systemd以用户身份帮我挂着服务,这样既节省了性能,而且省去了Mcsmanager也可以减少潜在的攻击面。
既然目前面板能这样运行,说明未来应用应该也可以。

为应用设置Systemd配置文件

在经历了QQ机器人封号,MC服务器没人玩,DDNS的工作实际上已经由路由器承担后,之前使用Mcsmanager挂着的服务就只剩下了这几个:两个Filebrowser文件管理,Jellyfin媒体服务器,以及偶尔需要启动的qBittorrent。除此之外,其他服务都是长期关闭的状态,这也为我做出转变提供了契机。
并且使用Systemd管理服务还有一个优势:可以让应用隔离开敏感数据,即使应用本身存在漏洞沦陷了,也不会影响到核心的敏感数据,这点是Mcsmanager给不了的。
那,接下来我们来进行配置吧!

Filebrowser

因为这个服务算是我这个家里云最核心的服务了,也就是对应的自建NAS服务,所以我把程序本体迁移到了/opt目录下,并且做好隔离。
实际上有两个Filebrowser实例,一个是管理一般文件,一个是管理重要文件,是分开的。
/usr/lib/systemd/system/filebrowser.service这个配置文件对应的是管理一般文件的实例:

[Unit]
Description=Filebrowser01

[Service]
WorkingDirectory=/opt/filebrowser
ExecStart=/opt/filebrowser/filebrowser
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
User=chocola
Group=chocola

# 严格沙盒
ProtectSystem=strict
ProtectHome=tmpfs
NoNewPrivileges=true

ReadWritePaths=/opt/filebrowser/
ReadWritePaths=/home/chocola/data/files/

# 显式禁止访问核心敏感路径
InaccessiblePaths=/root /home/chocola/.ssh /home/chocola/.bash_history /home/typecho /opt/nextcloud /opt/filebrowser_personal /home/chocola/.acme.sh /mnt

# 仅放行启动必需的系统映射
BindReadOnlyPaths=/bin /lib /lib64 /usr /etc /proc /sys /dev/shm

[Install]
WantedBy=multi-user.target

/usr/lib/systemd/system/filebrowser02.service这个配置文件对应的是管理重要文件的实例:

[Unit]
Description=Filebrowser Personal

[Service]
WorkingDirectory=/opt/filebrowser_personal
ExecStart=/opt/filebrowser_personal/filebrowser
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
User=chocola
Group=chocola

# 严格沙盒
ProtectSystem=strict
ProtectHome=tmpfs
NoNewPrivileges=true

ReadWritePaths=/opt/filebrowser_personal
ReadWritePaths=/mnt/4T-RAID1/data/

# 显式禁止访问核心敏感路径
InaccessiblePaths=/root /home/chocola/.ssh /home/chocola/.bash_history /home/typecho /opt/nextcloud /home/chocola/.acme.sh

# 仅放行启动必需的系统映射
BindReadOnlyPaths=/bin /lib /lib64 /usr /etc /proc /sys /dev/shm

[Install]
WantedBy=multi-user.target

配置的重点在于InaccessiblePaths,这个直接让应用“看不见”这些目录,达到隔离的目的。
我们只需要把一些敏感目录添加到这个参数,就可以极大的增强安全性。

Jellyfin

这个是我的媒体库,因为相比于Filebrowser,这个服务要复杂的多,也可能有更多潜在的安全漏洞,这也是我更想隔离的对象。
于是我写了/usr/lib/systemd/system/jellyfin.service这个配置文件:

[Unit]
Description=Jellyfin Media Server

[Service]
User=chocola
Group=chocola
WorkingDirectory=/home/chocola/data/apps/Jellyfin
ExecStart=/home/chocola/data/apps/Jellyfin/run.sh

# 严格沙盒
ProtectSystem=strict
ProtectHome=tmpfs
NoNewPrivileges=true

# 显式禁止访问核心敏感路径
InaccessiblePaths=/root /home/chocola/.ssh /home/chocola/.bash_history /opt /mnt /home/typecho 

# 仅放行启动必需的系统映射
BindReadOnlyPaths=/bin /lib /lib64 /usr /etc /proc /sys /dev/shm

# 精确的业务路径(不要直接写父目录)
ReadWritePaths=/home/chocola/data/apps/Jellyfin
ReadWritePaths=/home/chocola/.local/share/jellyfin
ReadWritePaths=/home/chocola/.config/jellyfin
ReadWritePaths=/home/chocola/data/files/Music
ReadWritePaths=/home/chocola/data/files/Download

# 限制进程只能看到自己的临时文件
PrivateTmp=true

[Install]
WantedBy=multi-user.target

配置文件直接禁止了它访问/mnt目录,因为这是我数据盘挂载的地方,也是敏感数据所在地。

qBittorrent

这个我并不是一直挂着,而是按需启动。并且在系统官方仓库也有这个软件,配置文件相对简单:
/usr/lib/systemd/system/qbittorrent-nox.service

[Unit]
Description=qBittorrent-nox
After=network.target

[Service]
Type=simple
User=chocola
Group=chocola
WorkingDirectory=/home/chocola
ExecStart=/usr/bin/qbittorrent-nox --webui-port=10086
# 显式禁止自动重启
Restart=no
# 可选:定义干净的停止信号(qbittorrent-nox 支持 SIGTERM)
KillSignal=SIGTERM
TimeoutStopSec=30

# 严格沙盒
ProtectSystem=strict
ProtectHome=tmpfs
NoNewPrivileges=true

# 显式禁止访问核心敏感路径
InaccessiblePaths=/root /home/chocola/.ssh /home/chocola/.bash_history /opt /mnt /home/typecho 

# 仅放行启动必需的系统映射
BindReadOnlyPaths=/bin /lib /lib64 /usr /etc /proc /sys /dev/shm

# 精确的业务路径(不要直接写父目录)
ReadWritePaths=/home/chocola/.config/qBittorrent
ReadWritePaths=/home/chocola/data/files/Download

# 限制进程只能看到自己的临时文件
PrivateTmp=true
NoNewPrivileges=true

[Install]

WantedBy=multi-user.target

对于qBittorrent-nox,我的需求是按需运行,也就是只要在Cockpit管理面板上直接点点鼠标就可以启动服务,和之前用Mcsmanager差别不大,甚至Cockpit的话还能和其他系统设置一起聚合管理。

小结

通过这样一波操作,我取得了三赢的结果:

  • 减少了一个不必要的中间管理层服务,节省了服务器资源
  • 减少了一个潜在的攻击面,增强服务器的安全性
  • 严格控制应用权限,做到了基础的纵深防御

或许,过段时间,我感觉运行没什么问题后会彻底移除掉Mcsmanager吧。这是一个很好的工具,但是我目前找到了更优雅的方案,也是时候说再见了。
也有可能,后面还需要Mcsmanager的时候,我将会把它关在权限的笼子里,确保我的数据安全。