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