mise bootstrap linux:在 mise 中以声明式方式管理 Linux systemd 用户单元
【免费下载链接】misedev tools, env vars, task runner项目地址: https://gitcode.com/GitHub_Trending/mi/mise
本文围绕 mise 的mise bootstrap linux命令组展开,讲解它如何把[bootstrap.linux]配置段(核心为[bootstrap.linux.systemd.units])声明式地落盘为 systemd 用户服务/定时器。读完本文,你将能够:编写并验证 Linux bootstrap 配置、使用systemd-units status/apply检查与部署用户单元、并理解单元文件渲染路径、状态机语义以及命令背后的实现细节。
命令概览:mise bootstrap linux
mise bootstrap linux是 mise 引导(bootstrap)工作流中专门处理Linux 平台配置的命令组,其作用是将 mise.toml 中的[bootstrap.linux]配置段应用到当前 Linux 主机。命令参考页(docs/cli/bootstrap/linux.md)标注:
- Usage:
mise bootstrap linux <SUBCOMMAND> - Effect:read-only —— 命令组本身不修改状态,所有变更都发生在其子命令中
- Flags:
-h --help - 源码位置:src/cli/bootstrap.rs
从源码结构看,[bootstrap.linux]目前只有一个功能子命令入口,即systemd-units(别名systemd),见 src/cli/bootstrap.rs:
#[derive(Debug, usage_rs::Subcommands)] enum BootstrapLinuxCommands { #[usage(name = "systemd-units", alias = "systemd")] SystemdUnits(BootstrapSystemd), }也就是说,mise bootstrap linux与mise bootstrap linux systemd指向同一个子命令组。该命令组对应mise bootstrap整体流程中的第 5 阶段(shell 激活、macOS 配置、Linux user units、用户设置),是“把机器配置成配置文件中声明的样子”这一目标在 Linux 平台上的落点。
此外,从 src/cli/bootstrap.rs 的Commands枚举结构看,mise bootstrap顶层还注册了一个名为linux-systemd-units(显示别名systemd)的快捷入口,可以直接跳过linux这一层;而--only/--skip过滤 bootstrap 阶段时使用的部分名正是linux-systemd-units(见 src/cli/bootstrap.rs 中bootstrap_part_name的映射BootstrapPart::Systemd => "linux-systemd-units")。因此下面的等价用法都成立:
mise bootstrap --only linux-systemd-units # 只运行 Linux systemd units 阶段 mise bootstrap --skip linux-systemd-units # 跳过该阶段systemd-units子命令:status 与 apply
mise bootstrap linux systemd-units负责管理来自[bootstrap.linux.systemd.units]的 systemd 用户服务:安装单元文件并在当前用户管理器(user manager)中调和(reconcile)服务状态。注意它与[bootstrap.services]声明的系统级服务是两套独立机制(参考 docs/cli/bootstrap/linux/systemd-units.md)。
mise bootstrap linux systemd-units status
展示已配置的 systemd 用户服务当前状态,效果为 read-only。标志(见 docs/cli/bootstrap/linux/systemd-units/status.md):
| 标志 | 说明 |
|---|---|
-J --json | 以 JSON 格式输出 |
--missing | 若任何已配置的用户服务未处于期望状态,则以退出码 1 结束 |
-h --help | 打印帮助 |
状态判定在 src/system/systemd.rs 中定义为一个四值状态机:
pub(crate) enum SystemdState { Active, // 单元正在运行 Inactive, // 单元已停止 Differs, // 已落盘单元内容与配置声明不一致 Missing, // 用户单元目录下没有对应的单元文件 }其中“是否处于期望状态”由is_desired()依据声明中的start键判定:Active且start = true、Inactive且start = false均视为期望态。status --missing只要发现任意单元偏离期望态即返回非零退出码,适合放进 CI 或巡检脚本做断言。
mise bootstrap linux systemd-units apply
安装并(按声明)启动 systemd 用户服务,效果为 modifies state。标志(见 docs/cli/bootstrap/linux/systemd-units/apply.md):
| 标志 | 说明 |
|---|---|
-n --dry-run | 只打印将要执行的命令,不实际执行 |
-y --yes | 跳过确认提示 |
-h --help | 打印帮助 |
从 src/system/systemd.rs 的实现看,apply的实际动作序列是:
- 将变更的单元文件重写到
~/.config/systemd/user/; - 执行
systemctl --user daemon-reload(所有 systemctl 调用都带 30 秒超时,见 src/system/systemd.rs 的SYSTEMCTL_TIMEOUT); - 对声明了
wanted_by的单元执行 enable,对wanted_by = []的单元执行 disable; start = true时 restart 单元,start = false时停止单元。
apply不会隐式触发——mise 绝不会在别的命令里偷偷写 systemd 单元,只有mise bootstrap linux systemd-units apply和完整mise bootstrap流程会这么做。因此推荐的工作流是先status预览、再apply --dry-run确认、最后apply --yes落盘:
mise bootstrap linux systemd-units status # 查看各单元状态 mise bootstrap linux systemd-units status --json # 机器可读 mise bootstrap linux systemd-units status --missing # 偏离期望态则退出码 1 mise bootstrap linux systemd-units apply # 写入并启动缺失/变更的单元 mise bootstrap linux systemd-units apply --dry-run # 只打印将执行的命令 mise bootstrap linux systemd-units apply --yes # 跳过确认提示[bootstrap.linux]配置段:units 声明与渲染规则
配置声明在[bootstrap.linux.systemd.units.<name>]下。从 src/system/systemd.rs 的SystemdTomlConfig结构看,每个条目被反序列化为一个单元声明,键名即 systemd 指令的小写蛇形写法,核心字段包括:
- 通用(
[Unit]):description、after、wants、requires - 服务(
[Service]):exec_start、type、remain_after_exit、exec_stop、timeout_start_sec、timeout_stop_sec、no_new_privileges、private_tmp、environment(表,渲染为Environment)、environment_file、nice、umask、working_directory、restart、restart_sec、standard_output、standard_error - 定时器(
[Timer]):on_boot_sec、on_unit_active_sec、on_unit_inactive_sec、on_calendar、randomized_delay_sec、accuracy_sec、persistent、unit - 行为控制:
start(默认true)、wanted_by(service 默认["default.target"],timer 默认["timers.target"])
渲染规则:
- 每个条目被写入
~/.config/systemd/user/dev.mise.<name>.service或dev.mise.<name>.timer(含 timer 键的条目渲染为.timer),由systemctl --user管理; - 单元名允许字母、数字、
.、_、-和@;mise 只负责自己用dev.mise.前缀创建的单元文件,不会动其他用户单元; exec_start、exec_stop、working_directory中裸写的~/~/会在写盘前展开为当前用户主目录;- 定时器条目的
unit若不带类型后缀(如unit = "healthcheck"),会解析为 mise 拥有的dev.mise.<unit>.service;要指向外部单元需写全限定名(如unit = "nginx.service"),按原文写入; - 定时器必须至少设置
on_boot_sec、on_unit_active_sec、on_unit_inactive_sec、on_calendar之一,且服务专用键(exec_start、environment、restart等)在 timer 条目上会被拒绝——被定时器触发的那个单元应写成独立的服务条目。
一个完整的真实示例可直接参考 src/config/config_file/mise_toml.rs 中test_bootstrap_linux_systemd_units测试用例使用的配置:
[bootstrap.linux.systemd.units.my-sync] description = "sync files" after = ["network-online.target"] wants = ["network-online.target"] requires = ["credentials.service"] exec_start = "~/.local/bin/my-sync --watch" type = "oneshot" remain_after_exit = true exec_stop = "~/.local/bin/my-sync --stop" timeout_start_sec = "120" timeout_stop_sec = "30" no_new_privileges = true private_tmp = true environment = { PATH = "/usr/bin:/bin" } environment_file = ["-%h/.config/my-sync.env"] nice = 10 umask = "0007" working_directory = "~" restart = "on-failure" restart_sec = "5s" standard_output = "append:%h/.local/state/my-sync.log" wanted_by = ["default.target"] [bootstrap.linux.systemd.units.my-sync-timer] on_boot_sec = "2min" on_unit_active_sec = "10min" on_unit_inactive_sec = "5min" on_calendar = "hourly" randomized_delay_sec = "30s" accuracy_sec = "1s" persistent = true unit = "dev.mise.my-sync.service"该测试断言了每个字段被正确解析进配置对象(如environment.get("PATH") == "/usr/bin:/bin"、wanted_by == ["default.target"]、timer 的unit == "dev.mise.my-sync.service"),可以作为字段取值方式的可靠依据。
声明式语义:合并、替换与清理
从SystemdState的调和逻辑与配置解析(MiseToml::bootstrap_config(),见 src/config/config_file/mise_toml.rs)的结构看,[bootstrap.linux.systemd.units]遵循几条重要语义:
- 声明式且可叠加:unit 名按配置层级(全局 → 项目)合并,同一单元名在更局部的配置中声明时,会整体替换先前的完整声明;
- service/timer 切换自动清理:当一个条目的性质从 service 变为 timer(或反向)时,mise 会停止、禁用并删除过期的“兄弟”单元,避免残留;
- 纯 Linux:在其他平台上该配置段不生效——
status会把这些条目列为 skipped,apply直接忽略; - 仅用户单元、仅目标用户:mise 只写
~/.config/systemd/user并用systemctl --user;要管理/etc/systemd/system下的系统服务需改用 bootstrap 的 managed files 与 services 机制。以sudo mise运行时该阶段会被跳过,因为systemctl --user会指向错误的用户管理器; - 单元环境隔离:单元命令不会继承你交互式 shell 里的 mise 激活,必须在
ExecStart里显式写可执行路径,并通过environment/environment_file提供所需环境。ExecStart走 systemd 命令语法,需要 shell 运算符时要显式调用 shell 或包一层脚本。
状态机在源码中的验证
实现侧的关键证据集中在 src/system/systemd.rs:
- 单元文件路径构造:
user_units_dir().join(format!("dev.mise.{name}.service"))(约 src/system/systemd.rs); - timer 渲染示例(单测断言的期望输出):
[Unit] Requires=network-online.target [Timer] OnBootSec=2min OnUnitInactiveSec=5min RandomizedDelaySec=30s AccuracySec=1s Persistent=yes Unit=dev.mise.healthcheck.service [Install] WantedBy=timers.targetSystemdStatus结构同时记录request(期望声明)、path(单元文件路径)、active/enabled(manager 实时状态)与state(上述四值状态),status --json输出的就是这些字段的 JSON 化结果。
小结与延伸阅读
mise bootstrap linux本身是 read-only 的命令组骨架,真正干活的是systemd-units apply/status这对子命令:前者把[bootstrap.linux.systemd.units]的声明落盘为dev.mise.*用户单元并调和到期望状态,后者以active/inactive/differs/missing四态汇报偏离(可配合--missing做退出码断言)。它与 macOS 侧的mise bootstrap macos子命令在 bootstrap 流程中构成平台对称的设计。
延伸阅读:
- systemd 用户单元专题文档:docs/bootstrap/systemd.md
mise bootstrap总览:docs/cli/bootstrap.md- 子命令参考:systemd-units、apply、status
- 实现源码:src/cli/bootstrap.rs、src/system/systemd.rs、配置解析 src/config/config_file/mise_toml.rs
【免费下载链接】misedev tools, env vars, task runner项目地址: https://gitcode.com/GitHub_Trending/mi/mise
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考