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