首页 > Sysadmin > 正文

Linux 自定义服务:从写 unit 文件到开机自启

huyuanwen 2026-10-28 Sysadmin

自定义服务在 systemd 眼里就是普通 unit:写对文件、reload 加载、enable 建软链接,三件事各管各的。

一、unit 文件先写全:少了 Install 段就谈不上 enable

cat > /etc/systemd/system/demo.service <<'EOF'
[Unit]
Description=Demo custom service
[Service]
Type=oneshot
ExecStart=/bin/echo hello-from-demo
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload              # 手工写的文件,reload 后守护进程才认
systemctl start demo; echo "rc=$?"   # 实测 0
journalctl -u demo -b --no-pager | tail -3
systemctl show -p ActiveState demo   # 实测 ActiveState=inactive

实测日志三行:echo 进程打出 hello-from-demo、Deactivated successfully、Finished demo.service – Demo custom service。oneshot 跑完退回 inactive 是正常态,不是失败。

为什么必须 daemon-reload:Debian 手册在 enable 一节写明,管理员手工往配置目录放软链接或改文件后,必须自己跑 daemon-reload 才会重新加载 [1]。同一节还提醒 cat 读的是磁盘文件,改完没 reload 时可能与守护进程的理解不一致 [1]。WantedBy 这类设置只在处理 enable 时被读一次,正常运行时 systemd 不再看这个段 [4];unit 与 drop-in 的查找顺序是 /etc 覆盖 /run、/run 覆盖 /lib,drop-in 优先于同名 unit 文件、同一目录里多个 drop-in 按文件名字典序叠加 [3]。

二、enable 只建软链接,start 才真的起进程

systemctl enable demo    # Created symlink .../multi-user.target.wants/demo.service
systemctl is-enabled demo; echo "rc=$?"   # enabled,退出码 0
systemctl is-active demo; echo "rc=$?"    # inactive,退出码 3
systemctl start demo                      # 到这一步才有进程
systemctl disable demo   # Removed ".../multi-user.target.wants/demo.service"
systemctl is-enabled demo; echo "rc=$?"   # disabled,退出码 1

手册口径很硬:enabling 与 starting 是正交的,enable 只是把 unit 挂到建议的位置,start 才真正 spawn 进程 [2];enable 本身不会顺手启动,想一步到位就写 systemctl enable –now,或者稍后再 start [1]。

oneshot 用 is-active 判断到底跑过没有,会一直拿到 inactive(实测退出码 3),要看 journalctl -u 或 status 的 Active 行。常驻服务才有运行态可查:实测一个 ExecStart 为 sleep 120 的 Type=simple 测试 unit,运行中 show 报 ActiveState=active、is-active 退出码 0,stop 之后回到 inactive、退出码 3 [1]。

三、没有 Install 段的 unit:能 start,不能 enable

systemctl enable demo2; echo "rc=$?"      # 只打警告,退出码仍是 0,且没建软链接
systemctl is-enabled demo2; echo "rc=$?"  # static,退出码 0
systemctl start demo2; echo "rc=$?"       # 退出码 0 —— static 照样能手动起
systemctl is-active demo2; echo "rc=$?"   # active,退出码 0

实测警告原文以 The unit files have no installation config 开头,落到 they are not meant to be enabled or disabled using systemctl 结束,既不报错也不建软链接,退出码 0 [1]。手册给这种 unit 的名字是 static,定义就是没被 enable、且 Install 段里没有可 enable 的配置,is-enabled 对它返回 0 [1]。

这里有个常见误传:static 就起不来。实测不成立,static unit 用 systemctl start 手动激活完全正常,start 退出码 0、随后 is-active 为 active。真实边界只有一条,它不能被 enable,所以开机不会自动带上它,要么补上 Install 段再 enable,要么由别的 unit 拉起来。

四、退出码别照抄旧表:本机 systemd 255 的实测值

systemctl is-enabled demo;  echo $?   # enabled -> 0
systemctl is-enabled demo2; echo $?   # static -> 0
systemctl is-enabled demo3; echo $?   # 有 Install 段但被 disable -> disabled, 1
systemctl is-active demo;   echo $?   # 没在跑 -> inactive, 3
systemctl status demo4;     echo $?   # Unit demo4.service could not be found. -> 4

手册 EXIT STATUS 一节直接说明 systemctl 沿用 LSB 3.0.0 定义的返回码,表里 3 对应 unit is not active、4 对应 no such unit(本机 systemd 255 手册原文)。手册对 is-active 只承诺至少一个是 active 就返回 0、否则非 0 [2],is-enabled 状态表把 disabled、masked 一律写作大于 0 [1]。

结论是:把 is-active 的失败当成 1、把文件不存在和没在跑混成一个码,都会让脚本误判。要分得清就同时读输出文本,或者直接用 systemctl show -p ActiveState demo 取值,实测 active 与 inactive 两种结果都对得上。

五、改配置:手写 drop-in 要 reload,revert 是撤销键

mkdir -p /etc/systemd/system/demo.service.d
printf '[Service]\nEnvironment=DEMO_MODE=dropin\n' > /etc/systemd/system/demo.service.d/override.conf
systemctl daemon-reload
systemctl cat demo | grep override.conf   # 输出里出现该文件名注释,说明 drop-in 已拼上
systemctl revert demo                     # Removed 该 override.conf,连目录一起删

脚本里非交互改配置就照上面三行写。别指望 systemctl edit –stdin:本机 systemd 255 的 systemctl edit 帮助里 grep 不到 stdin,这类写法直接用不了;edit 自己会在编辑器退出后做一次等效 daemon-reload [1]。

回滚用 revert 最省事,它把 drop-in 文件和它所在的目录一并移除。但对本机这种没有包自带版本的手写 unit,revert 退出码 0 却什么都没删,unit 文件原样留着(实测)——手写的东西最后还是得自己 rm 再加 daemon-reload。

六、彻底停掉一个 unit:mask 的边界和手写 unit 的下线顺序

  • 包自带 unit(文件在 /usr/lib/systemd/system):实测 systemctl mask 一个放在该目录的测试 unit,输出 Created symlink /etc/systemd/system/demolib.service 指向 /dev/null,is-enabled 变 masked 且退出码 1,start 被直接拒绝并提示 Unit demolib.service is masked.,unmask 后回到 disabled。手册口径一致:mask 是把 unit 文件链接到 /dev/null,比 disable 更硬,连手动激活都禁止 [1]。
  • 手写 unit(文件就在 /etc/systemd/system):mask 当场失败,实测报 Failed to mask unit: File already exists。手册同样写明这个限制,mask 在 /etc 或 /run 下已有同名文件时就不成立,所以它主要用于包自带的 unit [1]。
  • 手写 unit 的下线顺序:systemctl stop,有 Install 段的先 systemctl disable,再删掉 unit 文件,然后 systemctl daemon-reload,最后 systemctl is-enabled 应报 not-found、退出码 4(实测)。只想临时不跑就别删文件,stop 加 disable 足够。
  • 动手前先看一眼 wants 目录:ls -l /etc/systemd/system/multi-user.target.wants/。手册特别提示 disable 会移除指向该 unit 的全部软链接,包括手工建的那些,enable 和 disable 并不对称 [1]。
  • **常见问题**

  • 问:写完 unit 忘了 daemon-reload 会怎样? 答:手册说 cat 读磁盘、可能与守护进程加载态不一致,所以 reload 要当成习惯动作 [1];本机一次实测里改完 ExecStart 后 show 已经读到新值,没能复现出那种不一致,但别把不 reload 当安全。
  • 问:enable 之后需要重启才生效吗? 答:enable 的效果是开机时由 target 的 wants 目录把它带起来,当前这次运行要 start 或 enable –now。开机那一段依赖真实重启,本次未做重启验证,属文档口径 [2]。
  • 问:oneshot 服务 enable 了,is-active 怎么一直是 inactive? 答:正常,oneshot 执行完就退出,判断执行结果请用 journalctl -u 或 status 的 Active 行。
  • 问:怎么在脚本里稳妥判断状态? 答:读 systemctl show -p ActiveState 或 is-enabled 的输出文本,不要只认一个退出码;未运行是 3、unit 不存在是 4,两者含义不同。
  • 问:手写的 unit 想临时关掉又怕删错? 答:走 stop 加 disable 就停住了,mask 对手写 unit 无效,删除留到最后一步。
  • 局限性:本文环境为 WSL2 Ubuntu 24.04.4 LTS、systemd 255(255.4-1ubuntu8.16)、单用户 root;开机自启只验证到 wants 软链接与手册语义,未做真实重启验证;RPM 系发行版与 systemd user 实例未覆盖;systemctl edit 只做了帮助选项与文档核查,交互式编辑流程未逐步实操;退出码读数以本机 systemd 255 为准,更早或更新版本请自行复核。

    **实测记录**

  • 环境:WSL2 Ubuntu 24.04.4 LTS,内核 6.6.114.1-microsoft-standard-WSL2,systemd 255(255.4-1ubuntu8.16),root,测试日期 2026-10-28。测试 unit 均为一次性 demo 文件,测完已删除并 daemon-reload 复核无残留。
  • 带 Install 段的 oneshot unit:daemon-reload 后 start 退出码 0,journalctl 三行(pid 470),ActiveState=inactive。
  • enable 该 oneshot:Created symlink 到 multi-user.target.wants,is-enabled 输出 enabled 退出码 0,is-active 输出 inactive 退出码 3;disable 输出 Removed 该软链接,is-enabled 变 disabled 退出码 1。
  • 无 Install 段的 unit:enable 打印 installation config 警告、退出码 0、无软链接;is-enabled 输出 static 退出码 0;start 退出码 0,随后 is-active 输出 active 退出码 0。
  • Type=simple 常驻测试 unit(ExecStart 为 sleep 120):ActiveState=active、is-active 退出码 0、ExecMainPID=653;stop 后 inactive、退出码 3。
  • 不存在的 unit:is-enabled 输出 not-found 退出码 4,status 提示 could not be found 退出码 4。
  • drop-in:写入 demo.service.d/override.conf 并 daemon-reload 后 systemctl cat 出现该文件注释;revert 输出两条 Removed、目录随之消失;对无包自带版本的 unit 执行 revert,退出码 0 且 unit 文件仍在。
  • mask:对放在 /usr/lib/systemd/system 的测试 unit,mask 成功并链接到 /dev/null,is-enabled 输出 masked 退出码 1,start 提示 is masked;对 /etc/systemd/system 下的手写 unit,mask 报 already exists 而失败,unmask 后 is-enabled 回到 disabled 退出码 1。
  • systemctl preset demo:输出 Created symlink 到 multi-user.target.wants,随后 is-enabled 为 enabled;系统预设文件由 systemd 包提供,路径 /usr/lib/systemd/system-preset/90-systemd.preset。
  • systemctl edit –help 中 grep stdin 无匹配(systemd 255)。

  • 参考资料

    文中主要命令已在 WSL Ubuntu(24.04)实测,个别条目(未装工具/安装类命令)仅核对官方文档,未实际执行。