use clap::Parser;
mod agent;
mod cli;
mod client;
mod config;
#[cfg(unix)]
mod daemon;
mod webrtc_config;
use agent::Agent;
use cli::Cli;
use client::CliClient;
use config::RportConfig;
use serde::{Deserialize, Serialize};
use tracing_subscriber::EnvFilter;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OfferMessage {
pub id: String,
pub offer: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct AnswerMessage {
pub answer: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ServerMessage {
pub message_type: String,
pub data: serde_json::Value,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
if cli.daemon {
#[cfg(unix)]
{
let log_file = if let Some(log_path) = &cli.log_file {
log_path.to_string_lossy().to_string()
} else {
if cli.target.is_some() {
"/tmp/rport-agent.log".to_string()
} else if cli.port.is_some() {
"/tmp/rport-forward.log".to_string()
} else if !cli.proxy_args.is_empty() {
"/tmp/rport-proxy.log".to_string()
} else {
"/tmp/rport.log".to_string()
}
};
daemon::daemonize_with_log(&log_file)?;
}
#[cfg(not(unix))]
{
return Err(anyhow::anyhow!(
"Daemon mode is only supported on Unix systems"
));
}
}
tokio::runtime::Runtime::new()?.block_on(async_main(cli))
}
async fn async_main(cli: Cli) -> anyhow::Result<()> {
let mut config = if let Some(config_path) = &cli.config {
RportConfig::load_from_file(config_path)?
} else {
RportConfig::load_default()?
};
let log_env = if cli.debug {
EnvFilter::new("debug")
} else {
EnvFilter::from_default_env()
};
config.merge_with_cli(cli);
let token = config.token.clone().ok_or_else(|| {
anyhow::anyhow!("Token is required. Provide it via --token or in config file")
})?;
let server = config.server.clone().ok_or_else(|| {
anyhow::anyhow!("Server is required. Provide it via --server or in config file")
})?;
if let Some(target) = config.target {
tracing_subscriber::fmt().with_env_filter(log_env).init();
let (host, port) = parse_target(&target)?;
let agent_id = config
.id
.unwrap_or_else(|| format!("agent-{}", std::process::id()));
let agent = Agent::new(
server,
token,
agent_id,
host,
port,
config.ice_servers.clone(),
);
agent.run().await?;
} else if let Some(local_port) = config.port {
tracing_subscriber::fmt().with_env_filter(log_env).init();
let agent_id = config.id.ok_or_else(|| {
anyhow::anyhow!("Agent ID is required for port forwarding mode. Use --id <AGENT_ID>")
})?;
let client = CliClient::new(server, token, config.ice_servers.clone());
client.connect_port_forward(agent_id, local_port).await?;
} else {
if let Some(log_file) = config.log_file.clone() {
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(log_file)?;
tracing_subscriber::fmt()
.with_env_filter(log_env)
.with_writer(file)
.init();
} else {
tracing_subscriber::fmt()
.with_env_filter(log_env)
.with_writer(std::io::stderr)
.init();
}
let agent_id = config.id.ok_or_else(|| {
anyhow::anyhow!("Agent ID is required for port forwarding mode. Use --id <AGENT_ID>")
})?;
let client = CliClient::new(server, token, config.ice_servers.clone());
client
.connect_proxy_command(config.connect_timeout.clone(), agent_id)
.await?;
}
Ok(())
}
fn parse_target(target: &str) -> anyhow::Result<(String, u16)> {
if let Some(colon_pos) = target.rfind(':') {
let host = target[..colon_pos].to_string();
let port: u16 = target[colon_pos + 1..].parse()?;
Ok((host, port))
} else {
let port: u16 = target.parse()?;
Ok(("127.0.0.1".to_string(), port))
}
}