Skip to main content

sysknife_daemon/actions/
mod.rs

1use sysknife_types::RiskLevel;
2
3pub mod apparmor;
4pub mod apt;
5pub mod apt_preferences;
6pub mod auditd;
7pub mod certbot;
8pub mod cloudinit;
9pub mod containers;
10pub mod deployment;
11pub mod distrobox;
12pub mod fail2ban;
13pub mod filesystem;
14pub mod flatpak;
15pub mod grub;
16pub mod identity;
17pub mod journald;
18pub mod layering;
19pub mod livepatch;
20pub mod logging;
21pub mod lvm;
22pub mod mounts;
23pub mod multipass;
24pub mod netplan;
25pub mod network;
26pub mod package_repos;
27pub mod pam;
28pub mod ppa;
29pub mod processes;
30pub mod reboot;
31pub mod release_upgrade;
32pub mod resolvectl;
33pub mod services;
34pub mod snap;
35pub mod ssh;
36pub mod sudoers;
37pub mod sysctl;
38pub mod system_info;
39pub mod toolbox;
40pub mod ubuntu_pro;
41pub mod ufw;
42pub mod users;
43pub(crate) mod validate;
44
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum ActionMechanism {
47    Command {
48        program: &'static str,
49        args: Vec<String>,
50    },
51    FileScan {
52        path: String,
53    },
54    FileWrite {
55        path: String,
56        content: String,
57    },
58    FilePatch {
59        path: String,
60        search: String,
61        replace: String,
62    },
63    FileDelete {
64        path: String,
65    },
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct ActionSpec {
70    pub action_name: &'static str,
71    pub mechanism: ActionMechanism,
72    pub risk_level: RiskLevel,
73    pub reboot_required: bool,
74    pub rollback_available: bool,
75}
76
77/// The canonical catalogue of every action the daemon recognises, grouped by
78/// domain. This is the **single source of truth** for per-action static
79/// metadata (`risk_level`, `reboot_required`, `rollback_available`): the
80/// preview/approval gate (`preview.rs`), the RBAC role table (`policy.rs`), and
81/// `docs/action-reference.md` all derive from — or are consistency-tested
82/// against — this list rather than re-declaring risk. The section titles are
83/// the only hand-authored presentation input. See `tests/action_consistency.rs`
84/// for the invariants that pin the derived tables to this catalogue.
85pub fn catalogue() -> Vec<(&'static str, Vec<ActionSpec>)> {
86    vec![
87        ("Deployment (atomic host)", deployment::specs()),
88        ("Package layering (rpm-ostree)", layering::specs()),
89        ("Filesystem", filesystem::specs()),
90        ("Flatpak", flatpak::specs()),
91        ("Toolbox", toolbox::specs()),
92        ("Services", services::specs()),
93        ("Processes", processes::specs()),
94        ("Journald", journald::specs()),
95        ("Storage — LVM", lvm::specs()),
96        ("Kernel parameters — sysctl", sysctl::specs()),
97        ("Mounts & swap", mounts::specs()),
98        ("Log management", logging::specs()),
99        ("PAM password policy", pam::specs()),
100        ("auditd", auditd::specs()),
101        ("certbot / ACME", certbot::specs()),
102        ("Scoped sudoers.d", sudoers::specs()),
103        ("Network", network::specs()),
104        ("resolvectl", resolvectl::specs()),
105        ("Identity / time / locale", identity::specs()),
106        ("Users & groups", users::specs()),
107        ("SSH keys", ssh::specs()),
108        ("Package repositories", package_repos::specs()),
109        ("System info", system_info::specs()),
110        ("Containers", containers::specs()),
111        ("Reboot", reboot::specs()),
112        ("AppArmor", apparmor::specs()),
113        ("cloud-init", cloudinit::specs()),
114        ("fail2ban", fail2ban::specs()),
115        ("apt", apt::specs()),
116        ("apt preferences / pinning", apt_preferences::specs()),
117        ("PPA", ppa::specs()),
118        ("snap", snap::specs()),
119        ("ufw", ufw::specs()),
120        ("netplan", netplan::specs()),
121        ("distrobox", distrobox::specs()),
122        ("GRUB", grub::specs()),
123        ("Ubuntu release upgrade", release_upgrade::specs()),
124        ("Ubuntu Pro", ubuntu_pro::specs()),
125        ("Livepatch", livepatch::specs()),
126        ("Multipass", multipass::specs()),
127    ]
128}
129
130/// Every action spec, flattened from [`catalogue`].
131pub fn all_specs() -> Vec<ActionSpec> {
132    catalogue()
133        .into_iter()
134        .flat_map(|(_, specs)| specs)
135        .collect()
136}
137
138/// Static per-action metadata that lives on the [`ActionSpec`] and must not be
139/// re-declared anywhere else. Consumers (preview gate, RBAC) read it via
140/// [`spec_meta`].
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub struct SpecMeta {
143    pub risk_level: RiskLevel,
144    pub reboot_required: bool,
145    pub rollback_available: bool,
146}
147
148static SPEC_META: std::sync::LazyLock<std::collections::HashMap<&'static str, SpecMeta>> =
149    std::sync::LazyLock::new(|| {
150        all_specs()
151            .into_iter()
152            .map(|s| {
153                (
154                    s.action_name,
155                    SpecMeta {
156                        risk_level: s.risk_level,
157                        reboot_required: s.reboot_required,
158                        rollback_available: s.rollback_available,
159                    },
160                )
161            })
162            .collect()
163    });
164
165/// Canonical static metadata for `action_name`, or `None` for actions with no
166/// `ActionSpec` (e.g. the dispatcher-internal `ListJobHistory`).
167pub fn spec_meta(action_name: &str) -> Option<SpecMeta> {
168    SPEC_META.get(action_name).copied()
169}
170
171pub(crate) fn command_mechanism(
172    program: &'static str,
173    args: impl IntoIterator<Item = impl Into<String>>,
174) -> ActionMechanism {
175    ActionMechanism::Command {
176        program,
177        args: args.into_iter().map(Into::into).collect(),
178    }
179}
180
181pub(crate) fn file_write_mechanism(
182    path: impl Into<String>,
183    content: impl Into<String>,
184) -> ActionMechanism {
185    ActionMechanism::FileWrite {
186        path: path.into(),
187        content: content.into(),
188    }
189}
190
191pub(crate) fn file_patch_mechanism(
192    path: impl Into<String>,
193    search: impl Into<String>,
194    replace: impl Into<String>,
195) -> ActionMechanism {
196    ActionMechanism::FilePatch {
197        path: path.into(),
198        search: search.into(),
199        replace: replace.into(),
200    }
201}
202
203pub(crate) fn file_scan_mechanism(path: impl Into<String>) -> ActionMechanism {
204    ActionMechanism::FileScan { path: path.into() }
205}
206
207pub(crate) fn file_delete_mechanism(path: impl Into<String>) -> ActionMechanism {
208    ActionMechanism::FileDelete { path: path.into() }
209}