Starting configuration, probably doesn't work
This commit is contained in:
parent
4470108f3e
commit
efb7ef6929
6 changed files with 227 additions and 11 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
|
@ -165,12 +165,14 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"home",
|
||||||
"log",
|
"log",
|
||||||
"open",
|
"open",
|
||||||
"procfs",
|
"procfs",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -188,6 +190,15 @@ version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.50"
|
version = "0.1.50"
|
||||||
|
|
@ -422,6 +433,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.145"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
|
|
@ -552,6 +569,15 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ edition = "2021"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
crossterm = { version = "0.25", features = ["event-stream"] }
|
crossterm = { version = "0.25", features = ["event-stream"] }
|
||||||
|
home = "0.5.4"
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
open = "3"
|
open = "3"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
toml = "0.5"
|
||||||
|
|
||||||
[target.'cfg(target_os="linux")'.dependencies]
|
[target.'cfg(target_os="linux")'.dependencies]
|
||||||
procfs = "0.14.1"
|
procfs = "0.14.1"
|
||||||
|
|
|
||||||
9
config.toml
Normal file
9
config.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# This is an example config file
|
||||||
|
auto = true
|
||||||
|
|
||||||
|
[servers."coder.doty-dev"]
|
||||||
|
auto = true
|
||||||
|
|
||||||
|
[servers."coder.doty-dev".ports]
|
||||||
|
10350 = "Tilt UI"
|
||||||
|
8080 = true
|
||||||
156
src/client/config.rs
Normal file
156
src/client/config.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use toml::Value;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PortConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
auto: bool,
|
||||||
|
ports: HashMap<u16, PortConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerConfig {
|
||||||
|
pub fn get(&self, port: u16) -> PortConfig {
|
||||||
|
match self.ports.get(&port) {
|
||||||
|
None => PortConfig { enabled: self.auto, description: None },
|
||||||
|
Some(c) => c.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
auto: bool,
|
||||||
|
servers: HashMap<String, ServerConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn get(&self, remote: &str) -> ServerConfig {
|
||||||
|
match self.servers.get(remote) {
|
||||||
|
Some(cfg) => cfg.clone(),
|
||||||
|
None => ServerConfig { auto: self.auto, ports: HashMap::new() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config() -> Result<Config> {
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
|
let mut home = match home::home_dir() {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return Ok(default()),
|
||||||
|
};
|
||||||
|
home.push(".fwd");
|
||||||
|
|
||||||
|
let contents = match std::fs::read_to_string(home) {
|
||||||
|
Ok(contents) => contents,
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
ErrorKind::NotFound => return Ok(default()),
|
||||||
|
_ => return Err(e.into()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(parse_config(&contents.parse::<Value>()?)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default() -> Config {
|
||||||
|
Config { auto: true, servers: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_config(value: &Value) -> Result<Config> {
|
||||||
|
match value {
|
||||||
|
Value::Table(table) => Ok({
|
||||||
|
let auto = match table.get("auto") {
|
||||||
|
None => true,
|
||||||
|
Some(Value::Boolean(v)) => *v,
|
||||||
|
Some(v) => bail!("expected a true or false, got {:?}", v),
|
||||||
|
};
|
||||||
|
Config {
|
||||||
|
auto,
|
||||||
|
servers: get_servers(&table, auto)?,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => bail!("top level must be a table"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_servers(
|
||||||
|
table: &toml::value::Table,
|
||||||
|
auto: bool,
|
||||||
|
) -> Result<HashMap<String, ServerConfig>> {
|
||||||
|
match table.get("servers") {
|
||||||
|
None => Ok(HashMap::new()),
|
||||||
|
Some(Value::Table(table)) => Ok({
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
for (k, v) in table {
|
||||||
|
servers.insert(k.clone(), get_server(v, auto)?);
|
||||||
|
}
|
||||||
|
servers
|
||||||
|
}),
|
||||||
|
v => bail!("expected a table in the servers key, got {:?}", v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_server(value: &Value, auto: bool) -> Result<ServerConfig> {
|
||||||
|
match value {
|
||||||
|
Value::Table(table) => Ok(ServerConfig {
|
||||||
|
auto: match table.get("auto") {
|
||||||
|
None => auto, // Default to global default
|
||||||
|
Some(Value::Boolean(v)) => *v,
|
||||||
|
Some(v) => bail!("expected true or false, got {:?}", v),
|
||||||
|
},
|
||||||
|
ports: get_ports(table)?,
|
||||||
|
}),
|
||||||
|
value => bail!("expected a table, got {:?}", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ports(table: &toml::value::Table) -> Result<HashMap<u16, PortConfig>> {
|
||||||
|
match table.get("ports") {
|
||||||
|
None => Ok(HashMap::new()),
|
||||||
|
Some(Value::Table(table)) => Ok({
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
for (k,v) in table {
|
||||||
|
let port:u16 = k.parse()?;
|
||||||
|
let config = match v {
|
||||||
|
Value::Boolean(enabled) => PortConfig{enabled:*enabled, description:None},
|
||||||
|
Value::Table(table) => PortConfig{
|
||||||
|
enabled: match table.get("enabled") {
|
||||||
|
Some(Value::Boolean(enabled)) => *enabled,
|
||||||
|
_ => bail!("not implemented"),
|
||||||
|
},
|
||||||
|
description: match table.get("description") {
|
||||||
|
Some(Value::String(desc)) => Some(desc.clone()),
|
||||||
|
Some(v) => bail!("expect a string description, got {:?}", v),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => bail!("expected either a boolean (enabled) or a table for a port config, got {:?}", v),
|
||||||
|
};
|
||||||
|
ports.insert(port, config);
|
||||||
|
}
|
||||||
|
ports
|
||||||
|
}),
|
||||||
|
Some(Value::Array(array)) => Ok({
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
for v in array {
|
||||||
|
ports.insert(get_port_number(v)?, PortConfig{enabled:true, description:None});
|
||||||
|
}
|
||||||
|
ports
|
||||||
|
}),
|
||||||
|
Some(v) => bail!("ports must be either a table of '<port> = ...' or an array of ports, got {:?}", v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_port_number(v: &Value) -> Result<u16> {
|
||||||
|
let port: u16 = match v {
|
||||||
|
Value::Integer(i) => (*i).try_into()?,
|
||||||
|
v => bail!("port must be a small number, got {:?}", v),
|
||||||
|
};
|
||||||
|
Ok(port)
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::process;
|
use tokio::process;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
mod config;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
/// Wait for the server to be ready; we know the server is there and
|
/// Wait for the server to be ready; we know the server is there and
|
||||||
|
|
@ -374,7 +375,15 @@ pub async fn run_client(remote: &str) {
|
||||||
_ = log::set_boxed_logger(ui::Logger::new(event_sender.clone()));
|
_ = log::set_boxed_logger(ui::Logger::new(event_sender.clone()));
|
||||||
log::set_max_level(LevelFilter::Info);
|
log::set_max_level(LevelFilter::Info);
|
||||||
|
|
||||||
let mut ui = ui::UI::new(event_receiver);
|
let config = match config::load_config() {
|
||||||
|
Ok(config) => config.get(remote),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error loading configuration: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ui = ui::UI::new(event_receiver, config);
|
||||||
|
|
||||||
// Start the reconnect loop.
|
// Start the reconnect loop.
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::client_listen;
|
use super::{client_listen, config::ServerConfig};
|
||||||
use crate::message::PortDesc;
|
use crate::message::PortDesc;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
|
@ -65,12 +65,16 @@ struct Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Listener {
|
impl Listener {
|
||||||
pub fn from_desc(desc: PortDesc) -> Listener {
|
pub fn from_desc(
|
||||||
Listener {
|
socks_port: Option<u16>,
|
||||||
enabled: false,
|
desc: PortDesc,
|
||||||
stop: None,
|
enabled: bool,
|
||||||
desc: Some(desc),
|
) -> Listener {
|
||||||
|
let mut listener = Listener { enabled, stop: None, desc: Some(desc) };
|
||||||
|
if enabled {
|
||||||
|
listener.start(socks_port);
|
||||||
}
|
}
|
||||||
|
listener
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled(&self) -> bool {
|
pub fn enabled(&self) -> bool {
|
||||||
|
|
@ -102,6 +106,7 @@ impl Listener {
|
||||||
if let (Some(desc), Some(socks_port), None) =
|
if let (Some(desc), Some(socks_port), None) =
|
||||||
(&self.desc, socks_port, &self.stop)
|
(&self.desc, socks_port, &self.stop)
|
||||||
{
|
{
|
||||||
|
info!("Starting port {port} to {socks_port}", port = desc.port);
|
||||||
let (l, stop) = oneshot::channel();
|
let (l, stop) = oneshot::channel();
|
||||||
let port = desc.port;
|
let port = desc.port;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
|
@ -127,16 +132,17 @@ pub struct UI {
|
||||||
events: mpsc::Receiver<UIEvent>,
|
events: mpsc::Receiver<UIEvent>,
|
||||||
ports: HashMap<u16, Listener>,
|
ports: HashMap<u16, Listener>,
|
||||||
socks_port: Option<u16>,
|
socks_port: Option<u16>,
|
||||||
|
lines: VecDeque<String>,
|
||||||
|
config: ServerConfig,
|
||||||
|
selection: usize,
|
||||||
running: bool,
|
running: bool,
|
||||||
show_logs: bool,
|
show_logs: bool,
|
||||||
selection: usize,
|
|
||||||
lines: VecDeque<String>,
|
|
||||||
alternate_screen: bool,
|
alternate_screen: bool,
|
||||||
raw_mode: bool,
|
raw_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UI {
|
impl UI {
|
||||||
pub fn new(events: mpsc::Receiver<UIEvent>) -> UI {
|
pub fn new(events: mpsc::Receiver<UIEvent>, config: ServerConfig) -> UI {
|
||||||
UI {
|
UI {
|
||||||
events,
|
events,
|
||||||
ports: HashMap::new(),
|
ports: HashMap::new(),
|
||||||
|
|
@ -145,6 +151,7 @@ impl UI {
|
||||||
show_logs: false,
|
show_logs: false,
|
||||||
selection: 0,
|
selection: 0,
|
||||||
lines: VecDeque::with_capacity(1024),
|
lines: VecDeque::with_capacity(1024),
|
||||||
|
config,
|
||||||
alternate_screen: false,
|
alternate_screen: false,
|
||||||
raw_mode: false,
|
raw_mode: false,
|
||||||
}
|
}
|
||||||
|
|
@ -419,9 +426,16 @@ impl UI {
|
||||||
{
|
{
|
||||||
listener.connect(self.socks_port, port_desc);
|
listener.connect(self.socks_port, port_desc);
|
||||||
} else {
|
} else {
|
||||||
|
let config = self.config.get(port_desc.port);
|
||||||
|
info!("Port config {port_desc:?} -> {config:?}");
|
||||||
|
|
||||||
self.ports.insert(
|
self.ports.insert(
|
||||||
port_desc.port,
|
port_desc.port,
|
||||||
Listener::from_desc(port_desc),
|
Listener::from_desc(
|
||||||
|
self.socks_port,
|
||||||
|
port_desc,
|
||||||
|
config.enabled,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue