Skip to main content

sysknife_daemon/actions/
flatpak.rs

1use super::{command_mechanism, ActionMechanism, ActionSpec};
2use sysknife_types::RiskLevel;
3
4pub fn specs() -> Vec<ActionSpec> {
5    vec![
6        install_flatpak("testuser", "app-id", "flathub"),
7        remove_flatpak("testuser", "app-id"),
8        search_flatpak_apps("search-term"),
9        list_flatpak_remotes("testuser"),
10        list_installed_flatpaks("testuser"),
11        add_flatpak_remote("testuser", "remote", "https://example.invalid"),
12        remove_flatpak_remote("testuser", "remote"),
13        get_flatpak_app_info("testuser", "app-id"),
14        update_flatpak("testuser", Some("com.example.App")),
15        ubuntu_install_flatpak("testuser", "app-id", "flathub"),
16        ubuntu_remove_flatpak("testuser", "app-id"),
17        ubuntu_update_flatpak("testuser", Some("com.example.App")),
18        ubuntu_list_flatpaks("testuser"),
19    ]
20}
21
22/// Run a fixed Flatpak operation after the helper drops to the target user.
23///
24/// Flatpak user installations live under `~/.local/share/flatpak/` and are
25/// accessed through the user's D-Bus session. The daemon runs as `sysknife`
26/// (a system user) with no user installation; the helper switches to the
27/// correct user UID without spawning a login shell, so each argv element is
28/// passed to `flatpak` verbatim.
29///
30/// The helper independently allowlists the complete Flatpak argv grammar and
31/// rejects option-shaped values before dropping credentials and executing the
32/// fixed binary. Callers also validate values upstream; no shell parses them.
33fn flatpak_as(username: &str, args: &[&str]) -> ActionMechanism {
34    let mut argv: Vec<String> = vec![
35        "/usr/lib/sysknife/action-steps".to_string(),
36        "flatpak".to_string(),
37        username.to_string(),
38    ];
39    argv.extend(args.iter().map(|s| s.to_string()));
40    ActionMechanism::Command {
41        program: "sudo",
42        args: argv,
43    }
44}
45
46pub fn install_flatpak(username: &str, app_id: &str, remote: &str) -> ActionSpec {
47    ActionSpec {
48        action_name: "InstallFlatpak",
49        mechanism: flatpak_as(username, &["install", "--user", "-y", remote, app_id]),
50        risk_level: RiskLevel::Medium,
51        reboot_required: false,
52        rollback_available: false,
53    }
54}
55
56pub fn remove_flatpak(username: &str, app_id: &str) -> ActionSpec {
57    ActionSpec {
58        action_name: "RemoveFlatpak",
59        mechanism: flatpak_as(username, &["uninstall", "--user", "-y", app_id]),
60        risk_level: RiskLevel::Medium,
61        reboot_required: false,
62        rollback_available: false,
63    }
64}
65
66/// Search is system-wide (no user context needed) — the Flatpak repo index
67/// is shared and does not require a D-Bus session or user installation.
68pub fn search_flatpak_apps(term: &str) -> ActionSpec {
69    ActionSpec {
70        action_name: "SearchFlatpakApps",
71        mechanism: command_mechanism("flatpak", ["search", term]),
72        risk_level: RiskLevel::Low,
73        reboot_required: false,
74        rollback_available: false,
75    }
76}
77
78pub fn list_flatpak_remotes(username: &str) -> ActionSpec {
79    ActionSpec {
80        action_name: "ListFlatpakRemotes",
81        mechanism: flatpak_as(username, &["remotes", "--user", "--columns=name,url"]),
82        risk_level: RiskLevel::Low,
83        reboot_required: false,
84        rollback_available: false,
85    }
86}
87
88pub fn add_flatpak_remote(username: &str, remote: &str, url: &str) -> ActionSpec {
89    ActionSpec {
90        action_name: "AddFlatpakRemote",
91        mechanism: flatpak_as(
92            username,
93            &["remote-add", "--user", "--if-not-exists", remote, url],
94        ),
95        risk_level: RiskLevel::Medium,
96        reboot_required: false,
97        rollback_available: false,
98    }
99}
100
101pub fn remove_flatpak_remote(username: &str, remote: &str) -> ActionSpec {
102    ActionSpec {
103        action_name: "RemoveFlatpakRemote",
104        mechanism: flatpak_as(username, &["remote-delete", "--user", remote]),
105        risk_level: RiskLevel::Medium,
106        reboot_required: false,
107        rollback_available: false,
108    }
109}
110
111pub fn list_installed_flatpaks(username: &str) -> ActionSpec {
112    ActionSpec {
113        action_name: "ListInstalledFlatpaks",
114        mechanism: flatpak_as(
115            username,
116            &[
117                "list",
118                "--user",
119                "--app",
120                "--columns=application,name,version,origin",
121            ],
122        ),
123        risk_level: RiskLevel::Low,
124        reboot_required: false,
125        rollback_available: false,
126    }
127}
128
129pub fn update_flatpak(username: &str, app_id: Option<&str>) -> ActionSpec {
130    let mechanism = match app_id {
131        Some(id) => flatpak_as(username, &["update", "--user", "-y", id]),
132        None => flatpak_as(username, &["update", "--user", "-y"]),
133    };
134    ActionSpec {
135        action_name: "UpdateFlatpak",
136        mechanism,
137        risk_level: RiskLevel::Medium,
138        reboot_required: false,
139        rollback_available: false,
140    }
141}
142
143pub fn get_flatpak_app_info(username: &str, app_id: &str) -> ActionSpec {
144    ActionSpec {
145        action_name: "GetFlatpakAppInfo",
146        mechanism: flatpak_as(username, &["info", "--user", app_id]),
147        risk_level: RiskLevel::Low,
148        reboot_required: false,
149        rollback_available: false,
150    }
151}
152
153// ---------------------------------------------------------------------------
154// Ubuntu-specific Flatpak actions
155//
156// On Ubuntu, Flatpak is not installed by default (unlike Fedora Atomic where
157// it ships with the base image). These Ubuntu-routed actions use the same
158// argv construction as their Fedora counterparts — `flatpak_as` is reused
159// verbatim — but carry distinct action names so the daemon's policy layer
160// and the brain's routing can treat them separately. No code is duplicated:
161// every Ubuntu wrapper delegates directly to the shared `flatpak_as` helper.
162// ---------------------------------------------------------------------------
163
164/// Install a Flatpak app on Ubuntu through the bounded user-operation helper.
165///
166/// Identical argv to `InstallFlatpak` on Fedora. Distinct action name for
167/// Ubuntu-specific routing in the daemon and LLM prompt.
168///
169/// Risk: Medium. Installs a sandboxed application from a Flatpak remote.
170pub fn ubuntu_install_flatpak(username: &str, app_id: &str, remote: &str) -> ActionSpec {
171    ActionSpec {
172        action_name: "UbuntuInstallFlatpak",
173        mechanism: flatpak_as(username, &["install", "--user", "-y", remote, app_id]),
174        risk_level: RiskLevel::Medium,
175        reboot_required: false,
176        rollback_available: false,
177    }
178}
179
180/// Remove a Flatpak app on Ubuntu through the bounded user-operation helper.
181///
182/// Risk: Medium. Uninstalls a sandboxed Flatpak application.
183pub fn ubuntu_remove_flatpak(username: &str, app_id: &str) -> ActionSpec {
184    ActionSpec {
185        action_name: "UbuntuRemoveFlatpak",
186        mechanism: flatpak_as(username, &["uninstall", "--user", "-y", app_id]),
187        risk_level: RiskLevel::Medium,
188        reboot_required: false,
189        rollback_available: false,
190    }
191}
192
193/// Update Flatpak app(s) on Ubuntu.
194///
195/// When `app_id` is `Some`, updates only that application; when `None`, updates
196/// all installed Flatpak apps for the user.
197///
198/// Risk: Medium. May pull new versions of installed apps.
199pub fn ubuntu_update_flatpak(username: &str, app_id: Option<&str>) -> ActionSpec {
200    let mechanism = match app_id {
201        Some(id) => flatpak_as(username, &["update", "--user", "-y", id]),
202        None => flatpak_as(username, &["update", "--user", "-y"]),
203    };
204    ActionSpec {
205        action_name: "UbuntuUpdateFlatpak",
206        mechanism,
207        risk_level: RiskLevel::Medium,
208        reboot_required: false,
209        rollback_available: false,
210    }
211}
212
213/// List installed Flatpak apps on Ubuntu.
214///
215/// Risk: Low. Read-only enumeration of the user's Flatpak installation.
216pub fn ubuntu_list_flatpaks(username: &str) -> ActionSpec {
217    ActionSpec {
218        action_name: "UbuntuListFlatpaks",
219        mechanism: flatpak_as(
220            username,
221            &[
222                "list",
223                "--user",
224                "--app",
225                "--columns=application,name,version,origin",
226            ],
227        ),
228        risk_level: RiskLevel::Low,
229        reboot_required: false,
230        rollback_available: false,
231    }
232}