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}