| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- use clap::{Parser, Subcommand};
- #[derive(Parser)]
- #[command(author, version, about = "DGX Spark Launcher for vLLM and SGLang")]
- pub struct Cli {
- #[arg(long, short, default_value = "config.toml")]
- pub config: String,
- #[arg(long, short, help = "Show commands without executing")]
- pub dry_run: bool,
- #[command(subcommand)]
- pub command: Commands,
- }
- #[derive(Subcommand)]
- pub enum Commands {
- /// Launch a cluster
- Serve {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, default_value = "auto")]
- cluster: String,
- /// Target a specific node within the cluster (default: all).
- #[arg(long)]
- node: Option<String>,
- /// Keep containers after stop (default: auto-remove on stop)
- #[arg(long, default_value = "false")]
- keep: bool,
- /// Skip pre-serve hooks (cluster-level + recipe-level).
- #[arg(long, default_value = "false")]
- skip_before_serve: bool,
- /// Follow logs after serving (use --no-follow to skip).
- #[arg(long, action = clap::ArgAction::SetFalse, default_value_t = true)]
- follow: bool,
- /// Power on cluster if switch is off (requires HA config).
- #[arg(long, default_value = "false")]
- auto_power_on: bool,
- /// Sync Docker image across nodes before starting.
- #[arg(long, default_value_t = true)]
- sync_image: bool,
- /// Explicit source node for image sync (default: auto-detect).
- #[arg(long)]
- sync_image_source: Option<String>,
- /// Build image on this node if not found (requires docker-images/<recipe>/).
- #[arg(long)]
- build_node: Option<String>,
- /// Force rebuild even if checksum matches.
- #[arg(long, default_value = "false")]
- force_build: bool,
- },
- /// Show container logs
- Logs {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, action = clap::ArgAction::SetTrue)]
- follow: bool,
- #[arg(long, default_value = "auto")]
- cluster: String,
- /// Target a specific node within the cluster (default: head node if following, all nodes if not).
- #[arg(long)]
- node: Option<String>,
- },
- /// Stop a cluster
- Stop {
- #[arg(long, required = true)]
- recipe: String,
- /// Target specific nodes (repeatable, e.g. --node dgx1 --node dgx2).
- #[arg(long)]
- node: Vec<String>,
- #[arg(long, default_value = "auto")]
- cluster: String,
- /// Power off cluster after stopping containers (requires HA config).
- #[arg(long, default_value = "false")]
- power_off: bool,
- },
- /// Check cluster health
- Health {
- #[arg(
- long,
- help = "Cluster name (e.g. 4nodes-switch). If omitted — checks all."
- )]
- cluster: Option<String>,
- #[arg(long, help = "Show node host keys (for adding to config.toml)")]
- fingerprint: bool,
- },
- /// Check and repair a recipe (doctor mode)
- Doctor {
- #[arg(long, required = true, help = "Recipe name to check")]
- recipe: String,
- #[arg(
- long,
- default_value = "auto",
- help = "Cluster (if not auto — uses recipe.cluster)"
- )]
- cluster: String,
- #[arg(long, help = "Auto-fix issues (create Dockerfile + build image)")]
- repair: bool,
- },
- /// Manage tmux sessions (Docker alternative)
- #[command(subcommand)]
- Tmux(TmuxCommands),
- /// Auto-discover host keys and write them into config.toml
- InitHostKeys {
- #[arg(long, help = "Cluster name (e.g. 4nodes-switch). If omitted — all.")]
- cluster: Option<String>,
- },
- /// Run an arbitrary shell command on a cluster or single node
- Exec {
- /// Cluster name (mutually exclusive with --recipe).
- #[arg(long, conflicts_with = "recipe")]
- cluster: Option<String>,
- /// Recipe name; resolves to the recipe's pinned cluster.
- #[arg(long, conflicts_with = "cluster")]
- recipe: Option<String>,
- /// Target a specific node within the cluster (default: all).
- #[arg(long)]
- node: Option<String>,
- /// Name of an alias from `[exec_aliases]` in config.toml.
- /// Mutually exclusive with a trailing command.
- #[arg(long)]
- alias: Option<String>,
- /// Command to run (everything after `--`).
- #[arg(trailing_var_arg = true)]
- command: Vec<String>,
- },
- /// Power on/off nodes via Home Assistant (smart plugs / Wake-on-LAN).
- Power {
- #[command(subcommand)]
- action: PowerAction,
- },
- }
- #[derive(Subcommand)]
- pub enum PowerAction {
- /// Power off: SSH `sudo shutdown now` + wait + HA `turn_off`.
- Off {
- #[arg(long)]
- cluster: String,
- /// Target a specific node within the cluster (default: all).
- #[arg(long)]
- node: Option<String>,
- },
- /// Power on: HA `turn_on` only (no SSH; the smart plug wakes the machine).
- On {
- #[arg(long)]
- cluster: String,
- /// Target a specific node within the cluster (default: all).
- #[arg(long)]
- node: Option<String>,
- },
- /// Switch clusters: disable nodes from source, enable nodes in target.
- Switch {
- /// Current cluster (nodes + entities to shut down).
- #[arg(long)]
- from: String,
- /// Target cluster (nodes + entities to wake up).
- #[arg(long)]
- to: String,
- },
- }
- #[derive(Subcommand)]
- pub enum TmuxCommands {
- /// Launch cluster via tmux
- Serve {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, default_value = "auto")]
- cluster: String,
- /// Skip pre-serve hooks (cluster-level + recipe-level).
- #[arg(long, default_value = "false")]
- skip_before_serve: bool,
- },
- /// Stop tmux sessions
- Stop {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, default_value = "auto")]
- cluster: String,
- },
- /// Detailed tmux session status
- Status {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, default_value = "auto")]
- cluster: String,
- },
- /// Show tmux session logs
- Logs {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, action = clap::ArgAction::SetTrue)]
- follow: bool,
- #[arg(long, default_value = "auto")]
- cluster: String,
- },
- /// Attach to head node tmux session
- Attach {
- #[arg(long, required = true)]
- recipe: String,
- #[arg(long, default_value = "auto")]
- cluster: String,
- },
- }
|