From f82cbfea7978f590a1ad15a00db5bb3b93a910bb Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 10 Mar 2026 22:01:27 -0400 Subject: [PATCH] feat: add bridge-ctl CLI for runtime control Made-with: Cursor --- src/bin/bridge_ctl.rs | 94 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/bin/bridge_ctl.rs b/src/bin/bridge_ctl.rs index e0e486e..84b33bb 100644 --- a/src/bin/bridge_ctl.rs +++ b/src/bin/bridge_ctl.rs @@ -1,3 +1,93 @@ -fn main() { - println!("bridge-ctl"); +use std::io::{BufRead, BufReader, Write}; +use std::os::unix::net::UnixStream; +use std::path::PathBuf; +use std::time::Duration; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(name = "bridge-ctl")] +#[command(about = "Control a running owncast-irc-bridge instance")] +struct Cli { + /// Path to the bridge control socket + #[arg(short, long, default_value = "/tmp/owncast-irc-bridge.sock")] + socket: PathBuf, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Show bridge status + Status, + /// Control IRC connection + Irc { + #[command(subcommand)] + action: ConnectionAction, + }, + /// Control Owncast connection + Owncast { + #[command(subcommand)] + action: ConnectionAction, + }, + /// Shut down the bridge + Quit, +} + +#[derive(Subcommand)] +enum ConnectionAction { + Connect, + Disconnect, + Reconnect, +} + +fn main() { + let cli = Cli::parse(); + + let command_str = match &cli.command { + Commands::Status => "status".to_string(), + Commands::Quit => "quit".to_string(), + Commands::Irc { action } => format!("irc {}", action_str(action)), + Commands::Owncast { action } => format!("owncast {}", action_str(action)), + }; + + match send_command(&cli.socket, &command_str) { + Ok(response) => print!("{response}"), + Err(e) => { + eprintln!("Error: {e}"); + std::process::exit(1); + } + } +} + +fn action_str(action: &ConnectionAction) -> &'static str { + match action { + ConnectionAction::Connect => "connect", + ConnectionAction::Disconnect => "disconnect", + ConnectionAction::Reconnect => "reconnect", + } +} + +fn send_command(socket_path: &PathBuf, command: &str) -> Result> { + let mut stream = UnixStream::connect(socket_path)?; + stream.set_read_timeout(Some(Duration::from_secs(5)))?; + + writeln!(stream, "{command}")?; + stream.flush()?; + + let reader = BufReader::new(stream); + let mut response = String::new(); + for line in reader.lines() { + match line { + Ok(l) => { + response.push_str(&l); + response.push('\n'); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break, + Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => break, + Err(e) => return Err(e.into()), + } + } + Ok(response) }