ssh-channels-hub 0.3.2

A CLI tool for managing SSH port forwarding tunnels with auto-reconnect
Documentation
use clap::{Parser, Subcommand};
use std::path::PathBuf;

/// SSH Channels Hub — manage SSH port-forwarding tunnels with auto-reconnect.
///
/// Workflow:
///   1. `generate` — scaffold a config.toml from ~/.ssh/config
///   2. edit config.toml — uncomment channels, fill in ports
///   3. `validate` — sanity-check the config against ~/.ssh/config
///   4. `start -D` — run as a background daemon
///   5. `status` / `test` — inspect the live state
///
/// Configuration lookup (when `-c` is not given):
///   ./config.toml        →  $XDG_CONFIG_HOME/ssh-channels-hub/config.toml
#[derive(Parser)]
#[command(name = "ssh-channels-hub")]
#[command(version)]
#[command(
  about = "Manage SSH port-forwarding tunnels with auto-reconnect",
  long_about = None,
)]
#[command(propagate_version = true)]
#[command(arg_required_else_help = true)]
pub struct Cli {
  #[command(subcommand)]
  pub command: Commands,

  /// Path to config.toml (default: ./config.toml or platform config dir).
  #[arg(short, long, global = true, value_name = "FILE")]
  pub config: Option<PathBuf>,

  /// Enable debug-level logging (overrides RUST_LOG=info).
  #[arg(short, long, global = true)]
  pub debug: bool,

  /// Disable ANSI colors in output (also honors NO_COLOR env var).
  #[arg(long, global = true)]
  pub no_color: bool,
}

#[derive(Subcommand)]
pub enum Commands {
  /// Start the service (foreground, or detached with -D).
  #[command(long_about = "\
Bring up every channel defined in config.toml.

By default runs in the foreground until Ctrl+C; pass -D to detach.
While running, `status` / `stop` / `restart` connect over a local
IPC socket recorded next to config.toml.")]
  Start {
    /// Detach as a background daemon (writes PID/port file next to config).
    #[arg(short = 'D', long)]
    daemon: bool,
  },

  /// Stop the running service (signals the daemon via IPC).
  Stop,

  /// Stop the running service, then start it again in daemon mode.
  Restart,

  /// Show whether the service is running, plus configured channels.
  #[command(long_about = "\
Connects to the running daemon over IPC to report live state.

When no daemon is running, falls back to printing the channels
declared in config.toml so you can still see the intended setup.")]
  Status,

  /// Validate config.toml — resolves each channel against ~/.ssh/config.
  #[command(long_about = "\
Parses config.toml and resolves every `[[channels]]` entry against
~/.ssh/config. Catches missing host aliases, missing HostName/User,
unparseable ports, and hosts that need a password but have no
[auth.<alias>] block.")]
  Validate {
    /// config.toml to validate (overrides -c).
    config: Option<PathBuf>,
  },

  /// Scaffold a config.toml from your SSH config (~/.ssh/config).
  #[command(long_about = "\
Reads ~/.ssh/config and writes a config.toml with one commented-out
`[[channels]]` template per Host alias, plus stub [auth.<alias>]
blocks for hosts that have no IdentityFile.")]
  Generate {
    /// SSH config to read (default: ~/.ssh/config).
    #[arg(short, long, value_name = "FILE")]
    ssh_config: Option<PathBuf>,

    /// Output path for the scaffolded TOML (default: ./config.toml).
    #[arg(short, long, value_name = "FILE")]
    output: Option<PathBuf>,
  },

  /// Probe each local→remote channel by connecting to its local port.
  #[command(long_about = "\
For every `local->remote` channel, try a TCP connect to the local
listen address and verify the tunnel is alive. `remote->local`
channels are skipped — those can only be verified from the SSH
server side.")]
  Test {
    /// config.toml to test against (overrides -c).
    #[arg(short, long, value_name = "FILE")]
    config: Option<PathBuf>,
  },
}