use anyhow::Result;
use clap::Args;
use crate::config::{load_config, PortMapping, Protocol, Role};
use crate::ufw::rules::UfwManager;
#[derive(Args)]
pub struct AddPortArgs {
#[arg(short, long)]
pub remote: u16,
#[arg(short, long)]
pub local: u16,
#[arg(short, long, default_value = "tcp")]
pub protocol: Protocol,
#[arg(short, long, default_value = "")]
pub desc: String,
#[arg(long)]
pub no_ufw: bool,
}
pub fn execute(args: &AddPortArgs) -> Result<()> {
let mut config = load_config()?;
if config.ports().iter().any(|p| p.remote_port == args.remote && p.protocol == args.protocol) {
return Err(anyhow::anyhow!(
"Remote port {} ({}) is already in use",
args.remote,
args.protocol
));
}
let id = format!("port-{}", args.remote);
let mapping = PortMapping {
id: id.clone(),
local_port: args.local,
remote_port: args.remote,
protocol: args.protocol.clone(),
description: args.desc.clone(),
enabled: true,
};
config.ports_mut().push(mapping);
config.save()?;
if config.role() == Role::Server && !args.no_ufw {
let ufw = UfwManager::new();
if ufw.is_active() {
match ufw.allow_port(args.remote, &args.protocol) {
Ok(()) => {
println!("\x1b[1;32m✓ UFW rule added: allow {} {}\x1b[0m", args.remote, args.protocol);
}
Err(e) => {
println!("\x1b[1;33m⚠ Failed to add UFW rule: {}\x1b[0m", e);
}
}
} else {
println!("\x1b[1;33m⚠ UFW is not active, skipping firewall rule\x1b[0m");
}
}
println!("\n\x1b[1;32m✓ Port forwarding added:\x1b[0m");
println!(" Remote: :{} ({})", args.remote, args.protocol);
println!(" Local: localhost:{}", args.local);
println!(" ID: {}", id);
if !args.desc.is_empty() {
println!(" Desc: {}", args.desc);
}
println!();
Ok(())
}