Skip to main content

podbox/
cli.rs

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