use std::{net::{SocketAddr, ToSocketAddrs}, env};
use super::*;
use structopt::StructOpt;
const HOST_ENV: &'static str = "CTRL_HOST";
const PORT_ENV: &'static str = "CTRL_PORT";
const TLS_OFF_ENV: &'static str = "CTRL_TLS_OFF";
const DEFAULT_HOST: &'static str = "actnel.derekxwang.com";
const DEFAULT_CONTROL_HOST: &'static str = "wormhole.actnel.derekxwang.com";
const DEFAULT_CONTROL_PORT: &'static str = "10001";
const SETTINGS_DIR: &'static str = ".actnel";
const SECRET_KEY_FILE: &'static str = "key.token";
#[derive(Debug, StructOpt)]
#[structopt(
name = "actnel",
author = "derekxinzhewang@gmail.com",
about = "Expose your local web server to the internet with a public url."
)]
struct Opts {
#[structopt(short = "v", long = "verbose")]
verbose: bool,
#[structopt(subcommand)]
command: Option<SubCommand>,
#[structopt(short = "k", long = "key")]
key: Option<String>,
#[structopt(short = "s", long = "subdomain")]
sub_domain: Option<String>,
#[structopt(long = "host", default_value = "localhost")]
local_host: String,
#[structopt(long = "use-tls", short = "t")]
use_tls: bool,
#[structopt(short = "p", long = "port", default_value = "8000")]
port: u16,
#[structopt(long = "dashboard-port")]
dashboard_port: Option<u16>,
}
#[derive(Debug, StructOpt)]
enum SubCommand {
SetAuth {
#[structopt(short = "k", long = "key")]
key: String,
},
}
#[derive(Debug, Clone)]
pub struct Config {
pub client_id: ClientId,
pub control_url: String,
pub use_tls: bool,
pub host: String,
pub local_host: String,
pub local_port: u16,
pub local_addr: SocketAddr,
pub sub_domain: Option<String>,
pub secret_key: Option<SecretKey>,
pub control_tls_off: bool,
pub first_run: bool,
pub dashboard_port: u16,
pub verbose: bool,
}
impl Config {
pub fn get() -> Result<Config, ()> {
let opts: Opts = Opts::from_args();
if opts.verbose {
std::env::set_var("RUST_LOG", "actnel=debug");
}
pretty_env_logger::init();
let (secret_key, sub_domain) = match opts.command {
Some(SubCommand::SetAuth { key }) => {
let key = opts.key.unwrap_or(key);
let settings_dir = match dirs::home_dir().map(|h| h.join(SETTINGS_DIR)) {
Some(path) => path,
None => {
panic!("Could not find home directory to store token.")
}
};
std::fs::create_dir_all(&settings_dir)
.expect("Fail to create file in home directory");
std::fs::write(settings_dir.join(SECRET_KEY_FILE), key)
.expect("Failed to save authentication key file.");
eprintln!("Authentication key stored successfully!");
std::process::exit(0);
}
None => {
let key = opts.key;
let sub_domain = opts.sub_domain;
(
match key {
Some(key) => Some(key),
None => dirs::home_dir()
.map(|h| h.join(SETTINGS_DIR).join(SECRET_KEY_FILE))
.map(|path| {
if path.exists() {
std::fs::read_to_string(path)
.map_err(|e| {
error!("Error reading authentication token: {:?}", e)
})
.ok()
} else {
None
}
})
.unwrap_or(None),
},
sub_domain,
)
}
};
let local_addr = match (opts.local_host.as_str(), opts.port)
.to_socket_addrs()
.unwrap_or(vec![].into_iter())
.next()
{
Some(addr) => addr,
None => {
error!(
"An invalid local address was specified: {}:{}",
opts.local_host.as_str(),
opts.port
);
return Err(());
}
};
let tls_off = env::var(TLS_OFF_ENV).is_ok();
let host = env::var(HOST_ENV).unwrap_or(format!("{}", DEFAULT_HOST));
let control_host = env::var(HOST_ENV).unwrap_or(format!("{}", DEFAULT_CONTROL_HOST));
let port = env::var(PORT_ENV).unwrap_or(format!("{}", DEFAULT_CONTROL_PORT));
let scheme = if tls_off { "ws" } else { "wss" };
let control_url = format!("{}://{}:{}/wormhole", scheme, control_host, port);
info!("Control Server URL: {}", &control_url);
Ok(Config {
client_id: ClientId::generate(),
local_host: opts.local_host,
use_tls: opts.use_tls,
control_url,
host,
local_port: opts.port,
local_addr,
sub_domain,
dashboard_port: opts.dashboard_port.unwrap_or(0),
verbose: opts.verbose,
secret_key: secret_key.map(|s| SecretKey(s)),
control_tls_off: tls_off,
first_run: true,
})
}
pub fn generate(secret_key: Option<String>, sub_domain: Option<String>, verbose: bool) -> Result<Config, ()> {
let opts: Opts = Opts::from_args();
if verbose {
std::env::set_var("RUST_LOG", "actnel=debug");
}
pretty_env_logger::try_init().ok();
let local_addr = match (opts.local_host.as_str(), opts.port)
.to_socket_addrs()
.unwrap_or(vec![].into_iter())
.next()
{
Some(addr) => addr,
None => {
error!(
"An invalid local address was specified: {}:{}",
opts.local_host.as_str(),
opts.port
);
return Err(());
}
};
let tls_off = env::var(TLS_OFF_ENV).is_ok();
let host = env::var(HOST_ENV).unwrap_or(format!("{}", DEFAULT_HOST));
let control_host = env::var(HOST_ENV).unwrap_or(format!("{}", DEFAULT_CONTROL_HOST));
let port = env::var(PORT_ENV).unwrap_or(format!("{}", DEFAULT_CONTROL_PORT));
let scheme = if tls_off { "ws" } else { "wss" };
let control_url = format!("{}://{}:{}/wormhole", scheme, control_host, port);
info!("Control Server URL: {}", &control_url);
Ok(Config {
client_id: ClientId::generate(),
local_host: opts.local_host,
use_tls: opts.use_tls,
control_url,
host,
local_port: opts.port,
local_addr,
sub_domain,
dashboard_port: opts.dashboard_port.unwrap_or(0),
verbose: opts.verbose,
secret_key: secret_key.map(|s| SecretKey(s)),
control_tls_off: tls_off,
first_run: true,
})
}
pub fn activation_url(&self, full_hostname: &str) -> String {
format!(
"{}://{}",
if self.control_tls_off {
"http"
} else {
"https"
},
full_hostname
)
}
pub fn forward_url(&self) -> String {
let scheme = if self.use_tls { "https" } else { "http" };
format!("{}://{}:{}", &scheme, &self.local_host, &self.local_port)
}
pub fn ws_forward_url(&self) -> String {
let scheme = if self.use_tls { "wss" } else { "ws" };
format!("{}://{}:{}", scheme, &self.local_host, &self.local_port)
}
}