Skip to main content

ssh_channels_hub/
cli.rs

1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4/// SSH Channels Hub — manage SSH port-forwarding tunnels with auto-reconnect.
5///
6/// Workflow:
7///   1. `generate` — scaffold a config.toml from ~/.ssh/config
8///   2. edit config.toml — uncomment channels, fill in ports
9///   3. `validate` — sanity-check the config against ~/.ssh/config
10///   4. `start -D` — run as a background daemon
11///   5. `status` / `test` — inspect the live state
12///
13/// Configuration lookup (when `-c` is not given):
14///   ./config.toml        →  $XDG_CONFIG_HOME/ssh-channels-hub/config.toml
15#[derive(Parser)]
16#[command(name = "ssh-channels-hub")]
17#[command(version)]
18#[command(
19  about = "Manage SSH port-forwarding tunnels with auto-reconnect",
20  long_about = None,
21)]
22#[command(propagate_version = true)]
23#[command(arg_required_else_help = true)]
24pub struct Cli {
25  #[command(subcommand)]
26  pub command: Commands,
27
28  /// Path to config.toml (default: ./config.toml or platform config dir).
29  #[arg(short, long, global = true, value_name = "FILE")]
30  pub config: Option<PathBuf>,
31
32  /// Enable debug-level logging (overrides RUST_LOG=info).
33  #[arg(short, long, global = true)]
34  pub debug: bool,
35
36  /// Disable ANSI colors in output (also honors NO_COLOR env var).
37  #[arg(long, global = true)]
38  pub no_color: bool,
39}
40
41#[derive(Subcommand)]
42pub enum Commands {
43  /// Start the service (foreground, or detached with -D).
44  #[command(long_about = "\
45Bring up every channel defined in config.toml.
46
47By default runs in the foreground until Ctrl+C; pass -D to detach.
48While running, `status` / `stop` / `restart` connect over a local
49IPC socket recorded next to config.toml.")]
50  Start {
51    /// Detach as a background daemon (writes PID/port file next to config).
52    #[arg(short = 'D', long)]
53    daemon: bool,
54  },
55
56  /// Stop the running service (signals the daemon via IPC).
57  Stop,
58
59  /// Stop the running service, then start it again in daemon mode.
60  Restart,
61
62  /// Show whether the service is running, plus configured channels.
63  #[command(long_about = "\
64Connects to the running daemon over IPC to report live state.
65
66When no daemon is running, falls back to printing the channels
67declared in config.toml so you can still see the intended setup.")]
68  Status,
69
70  /// Validate config.toml — resolves each channel against ~/.ssh/config.
71  #[command(long_about = "\
72Parses config.toml and resolves every `[[channels]]` entry against
73~/.ssh/config. Catches missing host aliases, missing HostName/User,
74unparseable ports, and hosts that need a password but have no
75[auth.<alias>] block.")]
76  Validate {
77    /// config.toml to validate (overrides -c).
78    config: Option<PathBuf>,
79  },
80
81  /// Scaffold a config.toml from your SSH config (~/.ssh/config).
82  #[command(long_about = "\
83Reads ~/.ssh/config and writes a config.toml with one commented-out
84`[[channels]]` template per Host alias, plus stub [auth.<alias>]
85blocks for hosts that have no IdentityFile.")]
86  Generate {
87    /// SSH config to read (default: ~/.ssh/config).
88    #[arg(short, long, value_name = "FILE")]
89    ssh_config: Option<PathBuf>,
90
91    /// Output path for the scaffolded TOML (default: ./config.toml).
92    #[arg(short, long, value_name = "FILE")]
93    output: Option<PathBuf>,
94  },
95
96  /// Probe each local→remote channel by connecting to its local port.
97  #[command(long_about = "\
98For every `local->remote` channel, try a TCP connect to the local
99listen address and verify the tunnel is alive. `remote->local`
100channels are skipped — those can only be verified from the SSH
101server side.")]
102  Test {
103    /// config.toml to test against (overrides -c).
104    #[arg(short, long, value_name = "FILE")]
105    config: Option<PathBuf>,
106  },
107}