cli.rs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. use clap::{Parser, Subcommand};
  2. #[derive(Parser)]
  3. #[command(author, version, about = "DGX Spark Launcher for vLLM and SGLang")]
  4. pub struct Cli {
  5. #[arg(long, short, default_value = "config.toml")]
  6. pub config: String,
  7. #[arg(long, short, help = "Show commands without executing")]
  8. pub dry_run: bool,
  9. #[command(subcommand)]
  10. pub command: Commands,
  11. }
  12. #[derive(Subcommand)]
  13. pub enum Commands {
  14. /// Launch a cluster
  15. Serve {
  16. #[arg(long, required = true)]
  17. recipe: String,
  18. #[arg(long, default_value = "auto")]
  19. cluster: String,
  20. /// Target a specific node within the cluster (default: all).
  21. #[arg(long)]
  22. node: Option<String>,
  23. /// Keep containers after stop (default: auto-remove on stop)
  24. #[arg(long, default_value = "false")]
  25. keep: bool,
  26. /// Skip pre-serve hooks (cluster-level + recipe-level).
  27. #[arg(long, default_value = "false")]
  28. skip_before_serve: bool,
  29. /// Follow logs after serving (use --no-follow to skip).
  30. #[arg(long, action = clap::ArgAction::SetFalse, default_value_t = true)]
  31. follow: bool,
  32. /// Power on cluster if switch is off (requires HA config).
  33. #[arg(long, default_value = "false")]
  34. auto_power_on: bool,
  35. /// Sync Docker image across nodes before starting.
  36. #[arg(long, default_value_t = true)]
  37. sync_image: bool,
  38. /// Explicit source node for image sync (default: auto-detect).
  39. #[arg(long)]
  40. sync_image_source: Option<String>,
  41. /// Build image on this node if not found (requires docker-images/<recipe>/).
  42. #[arg(long)]
  43. build_node: Option<String>,
  44. /// Force rebuild even if checksum matches.
  45. #[arg(long, default_value = "false")]
  46. force_build: bool,
  47. },
  48. /// Show container logs
  49. Logs {
  50. #[arg(long, required = true)]
  51. recipe: String,
  52. #[arg(long, action = clap::ArgAction::SetTrue)]
  53. follow: bool,
  54. #[arg(long, default_value = "auto")]
  55. cluster: String,
  56. /// Target a specific node within the cluster (default: head node if following, all nodes if not).
  57. #[arg(long)]
  58. node: Option<String>,
  59. },
  60. /// Stop a cluster
  61. Stop {
  62. #[arg(long, required = true)]
  63. recipe: String,
  64. /// Target specific nodes (repeatable, e.g. --node dgx1 --node dgx2).
  65. #[arg(long)]
  66. node: Vec<String>,
  67. #[arg(long, default_value = "auto")]
  68. cluster: String,
  69. /// Power off cluster after stopping containers (requires HA config).
  70. #[arg(long, default_value = "false")]
  71. power_off: bool,
  72. },
  73. /// Check cluster health
  74. Health {
  75. #[arg(
  76. long,
  77. help = "Cluster name (e.g. 4nodes-switch). If omitted — checks all."
  78. )]
  79. cluster: Option<String>,
  80. #[arg(long, help = "Show node host keys (for adding to config.toml)")]
  81. fingerprint: bool,
  82. },
  83. /// Check and repair a recipe (doctor mode)
  84. Doctor {
  85. #[arg(long, required = true, help = "Recipe name to check")]
  86. recipe: String,
  87. #[arg(
  88. long,
  89. default_value = "auto",
  90. help = "Cluster (if not auto — uses recipe.cluster)"
  91. )]
  92. cluster: String,
  93. #[arg(long, help = "Auto-fix issues (create Dockerfile + build image)")]
  94. repair: bool,
  95. },
  96. /// Manage tmux sessions (Docker alternative)
  97. #[command(subcommand)]
  98. Tmux(TmuxCommands),
  99. /// Auto-discover host keys and write them into config.toml
  100. InitHostKeys {
  101. #[arg(long, help = "Cluster name (e.g. 4nodes-switch). If omitted — all.")]
  102. cluster: Option<String>,
  103. },
  104. /// Run an arbitrary shell command on a cluster or single node
  105. Exec {
  106. /// Cluster name (mutually exclusive with --recipe).
  107. #[arg(long, conflicts_with = "recipe")]
  108. cluster: Option<String>,
  109. /// Recipe name; resolves to the recipe's pinned cluster.
  110. #[arg(long, conflicts_with = "cluster")]
  111. recipe: Option<String>,
  112. /// Target a specific node within the cluster (default: all).
  113. #[arg(long)]
  114. node: Option<String>,
  115. /// Name of an alias from `[exec_aliases]` in config.toml.
  116. /// Mutually exclusive with a trailing command.
  117. #[arg(long)]
  118. alias: Option<String>,
  119. /// Command to run (everything after `--`).
  120. #[arg(trailing_var_arg = true)]
  121. command: Vec<String>,
  122. },
  123. /// Power on/off nodes via Home Assistant (smart plugs / Wake-on-LAN).
  124. Power {
  125. #[command(subcommand)]
  126. action: PowerAction,
  127. },
  128. }
  129. #[derive(Subcommand)]
  130. pub enum PowerAction {
  131. /// Power off: SSH `sudo shutdown now` + wait + HA `turn_off`.
  132. Off {
  133. #[arg(long)]
  134. cluster: String,
  135. /// Target a specific node within the cluster (default: all).
  136. #[arg(long)]
  137. node: Option<String>,
  138. },
  139. /// Power on: HA `turn_on` only (no SSH; the smart plug wakes the machine).
  140. On {
  141. #[arg(long)]
  142. cluster: String,
  143. /// Target a specific node within the cluster (default: all).
  144. #[arg(long)]
  145. node: Option<String>,
  146. },
  147. /// Switch clusters: disable nodes from source, enable nodes in target.
  148. Switch {
  149. /// Current cluster (nodes + entities to shut down).
  150. #[arg(long)]
  151. from: String,
  152. /// Target cluster (nodes + entities to wake up).
  153. #[arg(long)]
  154. to: String,
  155. },
  156. }
  157. #[derive(Subcommand)]
  158. pub enum TmuxCommands {
  159. /// Launch cluster via tmux
  160. Serve {
  161. #[arg(long, required = true)]
  162. recipe: String,
  163. #[arg(long, default_value = "auto")]
  164. cluster: String,
  165. /// Skip pre-serve hooks (cluster-level + recipe-level).
  166. #[arg(long, default_value = "false")]
  167. skip_before_serve: bool,
  168. },
  169. /// Stop tmux sessions
  170. Stop {
  171. #[arg(long, required = true)]
  172. recipe: String,
  173. #[arg(long, default_value = "auto")]
  174. cluster: String,
  175. },
  176. /// Detailed tmux session status
  177. Status {
  178. #[arg(long, required = true)]
  179. recipe: String,
  180. #[arg(long, default_value = "auto")]
  181. cluster: String,
  182. },
  183. /// Show tmux session logs
  184. Logs {
  185. #[arg(long, required = true)]
  186. recipe: String,
  187. #[arg(long, action = clap::ArgAction::SetTrue)]
  188. follow: bool,
  189. #[arg(long, default_value = "auto")]
  190. cluster: String,
  191. },
  192. /// Attach to head node tmux session
  193. Attach {
  194. #[arg(long, required = true)]
  195. recipe: String,
  196. #[arg(long, default_value = "auto")]
  197. cluster: String,
  198. },
  199. }