Skip to main content

podbox/cli/
command.rs

1//! The `podbox` command tree: the `Command` subcommand enum and its
2//! nested `Export`/`Profile`/`Snapshot` command enums.
3//!
4//! Extracted verbatim from `cli.rs`; re-exported from `cli` so
5//! `podbox::cli::Command` paths keep resolving.
6
7use clap::Subcommand;
8
9use super::{OutputFormat, Shell};
10
11#[derive(Subcommand)]
12pub enum Command {
13    /// Internal stdin watchdog for interactive sessions. Not for direct use.
14    #[command(hide = true)]
15    InternalStdinWatchdog {
16        /// PID of the process to terminate when stdin hangs up.
17        parent_pid: u32,
18    },
19
20    /// Build the container image from the definition.
21    #[command(display_order = 31)]
22    Build {
23        /// Container name to build (overrides auto-detection).
24        name: Option<String>,
25        /// Force rebuild even if definition hasn't changed.
26        #[arg(long)]
27        rebuild: bool,
28        /// Skip post-build drift check.
29        #[arg(long)]
30        no_diff: bool,
31        /// Open config in editor before building.
32        #[arg(long)]
33        edit: bool,
34    },
35
36    /// Install Quadlet systemd files and enable the container.
37    #[command(display_order = 32)]
38    Enable {
39        /// Container name (overrides auto-detection / active context).
40        name: Option<String>,
41        /// Skip confirmation when reinstalling Quadlet on a running container.
42        #[arg(long)]
43        yes: bool,
44    },
45
46    /// Disable and remove Quadlet systemd files.
47    #[command(display_order = 33)]
48    Disable {
49        /// Container name (overrides auto-detection / active context).
50        name: Option<String>,
51        /// Skip config loading and remove Quadlet files by name only.
52        #[arg(long)]
53        force: bool,
54    },
55
56    /// Start the container.
57    #[command(display_order = 23)]
58    Start {
59        /// Container name (overrides auto-detection / active context).
60        name: Option<String>,
61        /// Maximum seconds to wait for the container to become ready.
62        #[arg(long, default_value = "30")]
63        timeout: u64,
64        /// Open config in editor before starting.
65        #[arg(long)]
66        edit: bool,
67    },
68
69    /// Stop the container.
70    #[command(display_order = 24)]
71    Stop {
72        /// Container name (overrides auto-detection / active context).
73        name: Option<String>,
74    },
75
76    /// Execute a command interactively in the container.
77    #[command(display_order = 21)]
78    Exec {
79        /// Run as root inside the container (omit -u flag).
80        #[arg(long)]
81        root: bool,
82        /// Command and arguments to execute.
83        #[arg(required = true, trailing_var_arg = true)]
84        args: Vec<String>,
85    },
86
87    /// Run a GUI application in the container (detached).
88    #[command(display_order = 22)]
89    Run {
90        /// Application to run.
91        app: String,
92        /// Additional arguments for the application.
93        #[arg(trailing_var_arg = true)]
94        app_args: Vec<String>,
95    },
96
97    /// Show container status.
98    #[command(display_order = 26)]
99    Status {
100        /// Container name (overrides auto-detection / active context).
101        name: Option<String>,
102        /// Output format (text or json).
103        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
104        output: OutputFormat,
105    },
106
107    /// Show container logs.
108    #[command(display_order = 40)]
109    Logs {
110        /// Container name (overrides auto-detection / active context).
111        name: Option<String>,
112        /// Follow log output.
113        #[arg(short, long)]
114        follow: bool,
115        /// Number of lines to show from the end (default: 50).
116        #[arg(short, long)]
117        tail: Option<u32>,
118        /// Show logs since this duration (e.g. "5m", "1h", "2024-01-01").
119        #[arg(long)]
120        since: Option<String>,
121    },
122
123    /// Export a .desktop app or binary shim to the host.
124    #[command(display_order = 53)]
125    Export {
126        #[command(subcommand)]
127        export_cmd: ExportCommand,
128    },
129
130    /// Show resource usage for the container (wraps podman stats).
131    #[command(display_order = 42)]
132    Stats {
133        /// Container name (overrides auto-detection / active context).
134        name: Option<String>,
135        /// Only show one snapshot, don't stream.
136        #[arg(long)]
137        no_stream: bool,
138        /// Output format (text or json).
139        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
140        output: OutputFormat,
141    },
142
143    /// Remove the container.
144    #[command(visible_alias = "rm", display_order = 60)]
145    Remove {
146        /// Container name (overrides auto-detection / active context).
147        name: Option<String>,
148        /// Also remove the home directory.
149        #[arg(long)]
150        all: bool,
151        /// Skip confirmation prompt.
152        #[arg(long)]
153        force: bool,
154        /// Remove stale/orphaned containers (no valid config, not running).
155        #[arg(long)]
156        stale: bool,
157        /// Also delete the TOML definition file.
158        #[arg(long)]
159        config: bool,
160    },
161
162    /// Inspect container configuration, generated Quadlet, or computed environment.
163    #[command(display_order = 41)]
164    Inspect {
165        /// Container name (overrides auto-detection / active context).
166        name: Option<String>,
167        /// Show the resolved TOML config.
168        #[arg(long)]
169        config: bool,
170        /// Show the generated Quadlet (.container file).
171        #[arg(long)]
172        quadlet: bool,
173        /// Show the computed environment variables.
174        #[arg(long)]
175        env: bool,
176        /// Output format (text or json).
177        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
178        output: OutputFormat,
179    },
180
181    /// Run the host socket server (socket-activated by systemd).
182    #[command(hide = true)]
183    Serve {
184        /// Container name to serve.
185        name: String,
186    },
187
188    /// Run the Wayland firewall proxy (systemd companion service).
189    #[command(hide = true)]
190    Compositor {
191        /// Container name to proxy.
192        name: String,
193    },
194
195    /// Open an interactive shell in the container.
196    #[command(visible_alias = "shell", display_order = 20)]
197    Enter {
198        /// Container name (overrides auto-detection / active context).
199        name: Option<String>,
200        /// Open config in editor before entering shell.
201        #[arg(long)]
202        edit: bool,
203    },
204
205    /// Create and start a container from a profile or image in one step.
206    #[command(display_order = 10)]
207    Create {
208        /// Profile name (fedora, cachy) or full image reference.
209        image: String,
210        /// Override the container name.
211        #[arg(long, short)]
212        name: Option<String>,
213        /// Comma-separated list of packages to install (e.g. "fastfetch,btop").
214        #[arg(long, short)]
215        packages: Option<String>,
216        /// Skip starting the container after setup.
217        #[arg(long)]
218        no_start: bool,
219        /// Open config in editor before creating.
220        #[arg(long)]
221        edit: bool,
222    },
223
224    /// Open the container config in your preferred editor.
225    #[command(display_order = 30)]
226    Edit {
227        /// Container name (overrides auto-detection / active context).
228        name: Option<String>,
229        /// After saving, rebuild the image if image config changed.
230        #[arg(long)]
231        rebuild: bool,
232    },
233
234    /// List all managed containers.
235    #[command(visible_alias = "ls", display_order = 25)]
236    List {
237        /// Output format (text or json).
238        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
239        output: OutputFormat,
240    },
241
242    /// Clone an existing container config to a new name.
243    #[command(display_order = 50)]
244    Clone {
245        /// Source container name.
246        src: String,
247        /// Destination container name.
248        dst: String,
249        /// Also copy the home directory contents.
250        #[arg(long)]
251        copy_home: bool,
252    },
253
254    /// Initialize a new container config.
255    #[command(display_order = 11)]
256    Init {
257        /// Base image reference (e.g. "fedora:44") for a non-prebuilt container.
258        /// If omitted, defaults to "fedora:44".
259        image: Option<String>,
260        /// Container name (defaults to the image name).
261        #[arg(long)]
262        name: Option<String>,
263        /// Run an interactive wizard to build the config.
264        #[arg(long, short = 'i', conflicts_with = "profile")]
265        interactive: bool,
266        /// Use a named profile (cachy, fedora, dev) as template.
267        #[arg(long)]
268        profile: Option<String>,
269    },
270
271    /// Pull the latest image and restart the container.
272    #[command(display_order = 34)]
273    Update {
274        /// Container name (overrides auto-detection / active context).
275        name: Option<String>,
276        /// Skip restart after update.
277        #[arg(long)]
278        no_restart: bool,
279    },
280
281    /// Pull a prebuilt image without building.
282    #[command(display_order = 35)]
283    Pull {
284        /// Distro shorthand or full image reference.
285        image: Option<String>,
286    },
287
288    /// Manage container profiles.
289    #[command(display_order = 12)]
290    Profile {
291        #[command(subcommand)]
292        profile_cmd: ProfileCommand,
293    },
294
295    /// Run diagnostic checks.
296    #[command(display_order = 43)]
297    Doctor {
298        /// Container name (overrides auto-detection / active context).
299        name: Option<String>,
300        /// Auto-fix common issues (e.g. corrupted Wayland socket ownership).
301        #[arg(long)]
302        fix: bool,
303        /// Output format (text or json).
304        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
305        output: OutputFormat,
306    },
307
308    /// Generate shell completions.
309    #[command(display_order = 80)]
310    Completions {
311        /// Shell to generate completions for.
312        shell: Shell,
313        /// Also print daily-driver `abbr` shorthand (fish only).
314        #[arg(long, action = clap::ArgAction::SetTrue)]
315        abbrs: bool,
316    },
317
318    /// Compare declared packages against the running container.
319    #[command(display_order = 36)]
320    Diff {
321        /// Container name (overrides auto-detection / active context).
322        name: Option<String>,
323        /// Update the config TOML's install list to match the container.
324        #[arg(long)]
325        apply: bool,
326        /// Output format (text or json).
327        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
328        output: OutputFormat,
329    },
330
331    /// Snapshot the current container state as a tagged image.
332    #[command(display_order = 51)]
333    Snapshot {
334        #[command(subcommand)]
335        snapshot_cmd: SnapshotCommand,
336    },
337
338    /// Restore a container from a snapshot.
339    #[command(display_order = 52)]
340    Restore {
341        /// Tag of the snapshot to restore.
342        tag: String,
343        /// Container name (overrides auto-detection / active context).
344        name: Option<String>,
345    },
346
347    /// Set or show active context.
348    #[command(display_order = 70)]
349    Use {
350        /// Container name to set as active (omit to show current context).
351        name: Option<String>,
352        /// Clear the active context.
353        #[arg(long)]
354        clear: bool,
355    },
356
357    /// Find the definition file that would be used.
358    #[command(display_order = 44)]
359    FindDefinition {
360        /// Container name (overrides auto-detection / active context).
361        name: Option<String>,
362    },
363
364    /// Guided repair for a container that won't start.
365    #[command(display_order = 45)]
366    Recover {
367        /// Container name (overrides auto-detection / active context).
368        name: Option<String>,
369        /// Run every step without prompting.
370        #[arg(long)]
371        yes: bool,
372    },
373
374    /// Show the recent lifecycle action history.
375    #[command(display_order = 46)]
376    History {
377        /// Container name filter (leave empty for all containers).
378        name: Option<String>,
379        /// Maximum number of entries to show (0 = no limit).
380        #[arg(long, default_value_t = 25)]
381        limit: usize,
382        /// Output format (text or json).
383        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
384        output: OutputFormat,
385    },
386
387    /// Translate a path between host and container.
388    #[command(display_order = 81, group(
389        clap::ArgGroup::new("direction")
390            .args(["to_container", "to_host"])
391            .required(true)
392            .multiple(false)
393    ))]
394    TranslatePath {
395        /// Direction of translation.
396        #[arg(long)]
397        to_container: bool,
398        /// Direction of translation.
399        #[arg(long)]
400        to_host: bool,
401        /// Path to translate.
402        path: String,
403    },
404
405    /// Migrate legacy config files from ~/.config/podbox/ to ~/.config/podbox/profiles/
406    #[command(display_order = 75)]
407    Migrate {
408        /// Overwrite files in target profiles/ directory if they exist.
409        #[arg(long)]
410        force: bool,
411    },
412
413    /// Print known container names, one per line (shell completion helper).
414    #[command(hide = true, name = "__complete-names")]
415    CompleteNames,
416}
417
418#[derive(Subcommand)]
419pub enum ExportCommand {
420    /// Export a .desktop application.
421    App {
422        /// Application name to export (omit with --all).
423        name: Option<String>,
424        /// Export all apps listed in the config.
425        #[arg(long, conflicts_with = "name")]
426        all: bool,
427    },
428    /// Export a binary shim.
429    Bin {
430        /// Binary name to export (omit with --all).
431        name: Option<String>,
432        /// Export all bins listed in the config.
433        #[arg(long, conflicts_with = "name")]
434        all: bool,
435    },
436    /// Remove all exports for the container.
437    Clean,
438    /// List apps and bins exported to the host for the container.
439    List,
440}
441
442#[derive(Subcommand)]
443pub enum ProfileCommand {
444    /// List all available profiles (built-in and custom).
445    List,
446    /// Show the configuration of a specific profile.
447    Show {
448        /// Name of the profile to display.
449        name: String,
450    },
451}
452
453#[derive(Debug, Clone, Subcommand)]
454pub enum SnapshotCommand {
455    /// Take a snapshot of the current container state.
456    Create {
457        /// Container name (overrides auto-detection / active context).
458        name: Option<String>,
459        /// Snapshot tag (defaults to timestamp).
460        #[arg(long, short)]
461        tag: Option<String>,
462    },
463    /// List snapshots for a container.
464    List {
465        /// Container name (overrides auto-detection / active context).
466        name: Option<String>,
467        /// Output format (text or json).
468        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
469        output: OutputFormat,
470    },
471    /// Prune old snapshots, keeping the newest N.
472    Prune {
473        /// Container name (overrides auto-detection / active context).
474        name: Option<String>,
475        /// Number of snapshots to keep (default: 5).
476        #[arg(long, default_value_t = 5)]
477        keep: usize,
478    },
479}