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}