diff options
author | RaindropsSys <raindrops@equestria.dev> | 2024-03-18 21:01:37 +0100 |
---|---|---|
committer | RaindropsSys <raindrops@equestria.dev> | 2024-03-18 21:01:37 +0100 |
commit | 68c7b35722065d6d2855b73e3f4a9321864a2c1c (patch) | |
tree | a1a4d7a32c70856d05d85e0a89863b22a9f59b6f | |
parent | 23496dd23edf3662ad705c123086f1cb8e647135 (diff) | |
download | where-rs-68c7b35722065d6d2855b73e3f4a9321864a2c1c.tar.gz where-rs-68c7b35722065d6d2855b73e3f4a9321864a2c1c.tar.bz2 where-rs-68c7b35722065d6d2855b73e3f4a9321864a2c1c.zip |
I don't even know what I did
-rw-r--r-- | Cargo.lock | 130 | ||||
-rw-r--r-- | where-rs/Cargo.toml | 7 | ||||
-rw-r--r-- | where-rs/default_config.toml (renamed from config.toml) | 2 | ||||
-rw-r--r-- | where-rs/src/args.rs | 9 | ||||
-rw-r--r-- | where-rs/src/config.rs | 129 | ||||
-rw-r--r-- | where-rs/src/main.rs | 182 | ||||
-rw-r--r-- | where-rs/src/servers.rs | 66 | ||||
-rw-r--r-- | where-rs/src/ui.rs | 75 | ||||
-rw-r--r-- | whered/services/dev.equestria.whered.plist | 22 | ||||
-rw-r--r-- | whered/services/whered | 12 | ||||
-rw-r--r-- | whered/services/whered.service | 14 |
11 files changed, 475 insertions, 173 deletions
@@ -27,6 +27,54 @@ dependencies = [ ] [[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -82,6 +130,52 @@ dependencies = [ ] [[package]] +name = "clap" +version = "4.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] name = "const_fn" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -124,6 +218,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] name = "iana-time-zone" version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -413,6 +513,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -474,9 +580,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -495,9 +601,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.7" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +checksum = "c12219811e0c1ba077867254e5ad62ee2c9c190b0d957110750ac0cda1ae96cd" dependencies = [ "indexmap", "serde", @@ -513,6 +619,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -577,6 +689,7 @@ name = "where-rs" version = "0.1.0" dependencies = [ "chrono", + "clap", "serde", "toml", "where-shared", @@ -628,6 +741,15 @@ dependencies = [ ] [[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/where-rs/Cargo.toml b/where-rs/Cargo.toml index 690d02b..9e1b2c5 100644 --- a/where-rs/Cargo.toml +++ b/where-rs/Cargo.toml @@ -2,8 +2,8 @@ name = "where-rs" version = "0.1.0" edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "A small Rust client for the WHRD/UDP protocol, to access a list of logged in users on multiple systems at once." +authors = ["Raindrops", "ryze132"] [[bin]] name = "where" @@ -12,5 +12,6 @@ path = "src/main.rs" [dependencies] where-shared = { path = "../where-shared" } chrono = "0.4.35" -toml = "0.8.11" +toml = "0.8.12" serde = { version = "1.0.197", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } diff --git a/config.toml b/where-rs/default_config.toml index 44fb53a..c0c65a9 100644 --- a/config.toml +++ b/where-rs/default_config.toml @@ -1,4 +1,4 @@ -# where-rs: .where.toml, v1.0 2024/03/13 +# where-rs: where.toml, v1.0 2024/03/18 # This is the where-rs configuration file. Documentation is provided in-line. diff --git a/where-rs/src/args.rs b/where-rs/src/args.rs new file mode 100644 index 0000000..fd08f36 --- /dev/null +++ b/where-rs/src/args.rs @@ -0,0 +1,9 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(name = "where", version, about)] +pub struct Args { + /// Generate a config file when none is available + #[arg(short = 'c', long)] + pub generate_config: bool, +} diff --git a/where-rs/src/config.rs b/where-rs/src/config.rs new file mode 100644 index 0000000..c45ba93 --- /dev/null +++ b/where-rs/src/config.rs @@ -0,0 +1,129 @@ +use std::{env, fs}; +use std::path::PathBuf; +use serde::Deserialize; +use crate::args::Args; + +const TIMEOUT: u64 = 2000; +const MAX_SEND_RETRIES: usize = 3; +const CONFIG_FILENAME: &str = "where.toml"; + +#[derive(Deserialize, Debug, Default)] +#[serde(default)] +pub struct Config { + pub global: GlobalConfig, + pub server: Vec<Server> +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(default)] +pub struct GlobalConfig { + pub timeout: u64, + pub max_retries: usize, + pub include_inactive: bool, + pub port: u16, + pub source: String +} + +#[derive(Deserialize, Debug)] +pub struct Server { + pub endpoint: String, + pub label: Option<String>, + pub timeout: Option<u64>, + pub max_retries: Option<usize>, + pub failsafe: Option<bool> +} + +impl Default for GlobalConfig { + fn default() -> Self { + Self { + timeout: TIMEOUT, + max_retries: MAX_SEND_RETRIES, + include_inactive: true, + port: 15, + source: "Local".to_string() + } + } +} + +impl Config { + fn get_config_locations() -> Vec<PathBuf> { + vec![ + { + let mut path = PathBuf::new(); + + if let Ok(home) = env::var("XDG_CONFIG_HOME") { + path.push(home); + } else if let Ok(home) = env::var("HOME") { + path.push(home); + path.push(".config"); + } else { + path.push("/"); + } + + path.push(CONFIG_FILENAME); + path + }, + { + let mut path = PathBuf::new(); + + path.push("/etc"); + path.push(CONFIG_FILENAME); + path + } + ] + } + + pub fn build(args: Args) -> Self { + let config: Option<Config> = Self::get_config_locations() + .iter() + .flat_map(|path| fs::read_to_string(path).ok()) + .map(|str| toml::from_str(&str).unwrap_or_else(|e| { + eprintln!("where: Failed to parse configuration file: {e}"); + std::process::exit(1); + })) + .next(); + + if args.generate_config { + let default_config = include_str!("../default_config.toml"); + + let mut saved_path: Option<&PathBuf> = None; + let mut save_locations = Self::get_config_locations(); + save_locations.reverse(); + + let res: Option<()> = save_locations + .iter() + .flat_map(|path| { + let res = fs::write(path, default_config).ok(); + + if res.is_some() { + saved_path = Some(path); + } + + res + }) + .next(); + + if res.is_some() { + println!("where: Generated default configuration file at {}. Please edit it and run 'where' again.", saved_path.unwrap().to_str().unwrap()); + std::process::exit(0); + } else { + let save_locations_strings: Vec<String> = save_locations + .into_iter() + .map(|path| path.to_str().unwrap().to_string()) + .collect(); + println!("where: Failed to generate the default configuration file, tried: {}", save_locations_strings.join(", ")); + std::process::exit(1); + } + } else { + let locations_strings: Vec<String> = Self::get_config_locations() + .into_iter() + .map(|path| path.to_str().unwrap().to_string()) + .collect(); + + config.unwrap_or_else(|| { + eprintln!("where: Valid configuration file found nowhere, tried: {}\nPass -c to generate a default config file.", locations_strings.join(", ")); + std::process::exit(1); + }) + } + } +} diff --git a/where-rs/src/main.rs b/where-rs/src/main.rs index 4391c52..ce6a8bc 100644 --- a/where-rs/src/main.rs +++ b/where-rs/src/main.rs @@ -1,38 +1,12 @@ -use std::fs; -use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; -use std::io::ErrorKind; -use std::time::Duration; -use where_shared::error::{WhereError, WhereResult}; -use where_shared::{Session, SessionCollection, MAX_PAYLOAD_LENGTH, WHERED_MAGIC}; -use chrono::prelude::*; -use serde::Deserialize; +mod config; +mod servers; +mod ui; +mod args; -pub const TIMEOUT: u64 = 2000; -pub const MAX_SEND_RETRIES: usize = 3; - -#[derive(Deserialize, Debug)] -struct Config { - global: Option<GlobalConfig>, - server: Vec<ServerConfig> -} - -#[derive(Deserialize, Debug)] -struct ServerConfig { - endpoint: String, - label: Option<String>, - timeout: Option<u64>, - max_retries: Option<usize>, - failsafe: Option<bool> -} - -#[derive(Deserialize, Debug, Clone, Default)] -struct GlobalConfig { - timeout: Option<u64>, - max_retries: Option<usize>, - include_inactive: Option<bool>, - port: Option<u16>, - source: Option<String> -} +use clap::Parser; +use args::Args; +use where_shared::error::WhereResult; +use config::{Config, Server}; fn main() { if let Err(e) = start_client() { @@ -42,28 +16,17 @@ fn main() { } fn start_client() -> WhereResult<()> { - // TODO: Make it load from an actual path: /etc/where.toml, or ~/.where.toml if it exists - let config_path = "./config.toml"; - - let config: Config = toml::from_str(&fs::read_to_string(config_path).unwrap_or_else(|e| { - eprintln!("where: Failed to open configuration file: {e}"); - std::process::exit(1); - })).unwrap_or_else(|e| { - eprintln!("where: Failed to parse configuration file: {e}"); - std::process::exit(1); - }); + let args = Args::parse(); + let config = Config::build(args); + let global_config = config.global; - println!("{:?}", config); - let global_config = config.global.unwrap_or_default(); - - let servers: Vec<ServerConfig> = config.server; + let servers: Vec<Server> = config.server; let mut sessions = vec![]; for server in servers { - // I know using .clone() sucks! - let res = match process_server(&server, global_config.clone()) { - Ok(data) => { - data + let res = match server.process(&global_config) { + Ok(collection) => { + collection } Err(e) => { eprintln!("where: {e}"); @@ -72,124 +35,13 @@ fn start_client() -> WhereResult<()> { std::process::exit(1); } - SessionCollection::get_empty() + continue } }; sessions.extend(res.into_vec()); } - print_summary(sessions, global_config); + ui::print_summary(sessions, global_config); Ok(()) } - -fn process_server(server: &ServerConfig, config: GlobalConfig) -> WhereResult<SessionCollection> { - let label = server.label.clone().unwrap_or(server.endpoint.to_owned()); - let timeout = Duration::from_millis(server.timeout.unwrap_or(config.timeout.unwrap_or(TIMEOUT))); - let retries = server.max_retries.unwrap_or(config.max_retries.unwrap_or(MAX_SEND_RETRIES)); - - let address: SocketAddr = match server.endpoint.to_socket_addrs() { - Ok(addr) => addr.as_slice()[0], - Err(_) => { - let mut endpoint = server.endpoint.clone(); - endpoint.push_str(&format!(":{}", config.port.unwrap_or(15))); - endpoint.to_socket_addrs()?.as_slice()[0] - } - }; - - let socket = UdpSocket::bind(if address.is_ipv4() { - "0.0.0.0:0" - } else { - "[::]:0" - })?; - socket.set_read_timeout(Some(timeout))?; - - let mut buf = [0; MAX_PAYLOAD_LENGTH]; - - for _ in 0..retries { - socket.send_to(&WHERED_MAGIC, address)?; - - match socket.recv_from(&mut buf) { - Ok(_) => { - let collection = SessionCollection::from_udp_payload(buf, &label)?; - return Ok(collection); - }, - Err(e) if e.kind() == ErrorKind::TimedOut || e.kind() == ErrorKind::WouldBlock => continue, - Err(e) => return Err(WhereError::from(e)), - } - } - - Err(WhereError::TimedOut(server.endpoint.to_string(), address.to_string(), retries, timeout)) -} - -fn print_summary(mut sessions: Vec<Session>, config: GlobalConfig) { - fn max_key_with_min<T, F>(sessions: &[Session], get_key: F, floor: T) -> T - where - T: Ord + Default, - F: Fn(&Session) -> T - { - sessions.iter() - .max_by_key(|s| get_key(s)) - .map(get_key) - .unwrap_or_default() - .max(floor) - } - - - sessions.sort_unstable_by_key(|s| s.login_time); - sessions.sort_by_key(|s| !s.active); // We want active first - - const ACTIVE_PADDING: usize = 2; - let host_padding = max_key_with_min(&sessions, |s| s.host.as_deref().map_or(0, |str| str.len()), 5); - let remote_padding = max_key_with_min(&sessions, |s| s.remote.as_deref().map_or(0, |str| str.len()), 7); - let username_padding = max_key_with_min(&sessions, |s| s.user.len(), 5); - let tty_padding = max_key_with_min(&sessions, |s| s.tty.len(), 4); - let pid_padding = max_key_with_min(&sessions, |s| s.pid.abs().checked_ilog10().unwrap_or_default() + 1 + (s.pid < 0) as u32, 4); - - println!("{:pad_0$} {:<pad_1$} {:<pad_2$} {:<pad_3$} {:<pad_4$} {:<pad_5$} Since", - "Act", - "Host", - "Source", - "User", - "TTY", - "PID", - pad_0 = ACTIVE_PADDING, - pad_1 = host_padding, - pad_2 = remote_padding, - pad_3 = username_padding, - pad_4 = tty_padding, - pad_5 = pid_padding as usize); - - for session in sessions { - if !config.include_inactive.unwrap_or(true) && !session.active { - continue; - } - - let active = if session.active { - '*' - } else { - ' ' - }; - - let host = session.host.unwrap_or_else(|| ' '.to_string()); - let remote = session.remote.unwrap_or_else(|| config.source.clone().unwrap_or("Local".to_string())); - - let datetime = DateTime::from_timestamp(session.login_time, 0).unwrap(); - let time = datetime.format("%Y-%m-%d %H:%M:%S"); - - println!(" {:<pad_0$} {:<pad_1$} {:<pad_2$} {:<pad_3$} {:<pad_4$} {:<pad_5$} {}", - active, - host, - remote, - session.tty, - session.user, - session.pid, - time, - pad_0 = ACTIVE_PADDING, - pad_1 = host_padding, - pad_2 = remote_padding, - pad_3 = username_padding, - pad_4 = tty_padding, - pad_5 = pid_padding as usize); - } -} diff --git a/where-rs/src/servers.rs b/where-rs/src/servers.rs new file mode 100644 index 0000000..aab4322 --- /dev/null +++ b/where-rs/src/servers.rs @@ -0,0 +1,66 @@ +use std::io::ErrorKind; +use std::net::{SocketAddr, UdpSocket}; +use std::time::Duration; +use where_shared::error::{WhereError, WhereResult}; +use where_shared::{MAX_PAYLOAD_LENGTH, SessionCollection, WHERED_MAGIC}; +use crate::config::{GlobalConfig, Server}; + +impl Server { + fn get_address(&self, config: &GlobalConfig) -> WhereResult<SocketAddr> { + let res: SocketAddr = match self.endpoint.parse() { + Ok(addr) => addr, + Err(_) => { + let mut endpoint = self.endpoint.clone(); + let port = config.port.to_string(); + + endpoint.push(':'); + endpoint.push_str(&port); + endpoint.parse()? + } + }; + + Ok(res) + } + + fn create_socket(&self, address: &SocketAddr, timeout: Duration) -> WhereResult<UdpSocket> { + let socket = UdpSocket::bind(if address.is_ipv4() { + "0.0.0.0:0" + } else { + "[::]:0" + })?; + socket.set_read_timeout(Some(timeout))?; + + Ok(socket) + } + + fn attempt_fetch(socket: &UdpSocket, address: &SocketAddr, mut buf: [u8; MAX_PAYLOAD_LENGTH], label: &str) -> WhereResult<Option<SessionCollection>> { + socket.send_to(&WHERED_MAGIC, address)?; + + match socket.recv_from(&mut buf) { + Ok(_) => { + let collection = SessionCollection::from_udp_payload(buf, &label)?; + Ok(Some(collection)) + }, + Err(e) if e.kind() == ErrorKind::TimedOut || e.kind() == ErrorKind::WouldBlock => Ok(None), + Err(e) => Err(WhereError::from(e)), + } + } + + pub fn process(&self, config: &GlobalConfig) -> WhereResult<SessionCollection> { + let label = self.label.clone().unwrap_or(self.endpoint.to_owned()); + let retries = self.max_retries.unwrap_or(config.max_retries); + let address = self.get_address(config)?; + let timeout = Duration::from_millis(self.timeout.unwrap_or(config.timeout)); + let socket = self.create_socket(&address, timeout)?; + let buf = [0; MAX_PAYLOAD_LENGTH]; + + for _ in 0..retries { + match Self::attempt_fetch(&socket, &address, buf, &label)? { + Some(c) => return Ok(c), + None => continue + }; + } + + Err(WhereError::TimedOut(self.endpoint.to_string(), address.to_string(), retries, timeout)) + } +} diff --git a/where-rs/src/ui.rs b/where-rs/src/ui.rs new file mode 100644 index 0000000..d2c1826 --- /dev/null +++ b/where-rs/src/ui.rs @@ -0,0 +1,75 @@ +use chrono::DateTime; +use where_shared::Session; +use crate::config::GlobalConfig; + +pub fn print_summary(mut sessions: Vec<Session>, config: GlobalConfig) { + fn max_key_with_min<T, F>(sessions: &[Session], get_key: F, floor: T) -> T + where + T: Ord + Default, + F: Fn(&Session) -> T + { + sessions.iter() + .max_by_key(|s| get_key(s)) + .map(get_key) + .unwrap_or_default() + .max(floor) + } + + + sessions.sort_unstable_by_key(|s| s.login_time); + sessions.sort_by_key(|s| !s.active); // We want active first + + const ACTIVE_PADDING: usize = 2; + let host_padding = max_key_with_min(&sessions, |s| s.host.as_deref().map_or(0, |str| str.len()), 5); + let remote_padding = max_key_with_min(&sessions, |s| s.remote.as_deref().map_or(0, |str| str.len()), 7); + let username_padding = max_key_with_min(&sessions, |s| s.user.len(), 5); + let tty_padding = max_key_with_min(&sessions, |s| s.tty.len(), 4); + let pid_padding = max_key_with_min(&sessions, |s| s.pid.abs().checked_ilog10().unwrap_or_default() + 1 + (s.pid < 0) as u32, 4); + + println!("{:pad_0$} {:<pad_1$} {:<pad_2$} {:<pad_3$} {:<pad_4$} {:<pad_5$} Since", + "Act", + "Host", + "Source", + "User", + "TTY", + "PID", + pad_0 = ACTIVE_PADDING, + pad_1 = host_padding, + pad_2 = remote_padding, + pad_3 = username_padding, + pad_4 = tty_padding, + pad_5 = pid_padding as usize); + + for session in sessions { + if !config.include_inactive && !session.active { + continue; + } + + let active = if session.active { + '*' + } else { + ' ' + }; + + let host = session.host.unwrap_or_else(|| ' '.to_string()); + let remote = session.remote.unwrap_or_else(|| config.source.clone()); + + let datetime = DateTime::from_timestamp(session.login_time, 0).unwrap(); + let time = datetime.format("%Y-%m-%d %H:%M:%S"); + + println!(" {:<pad_0$} {:<pad_1$} {:<pad_2$} {:<pad_3$} {:<pad_4$} {:<pad_5$} {}", + active, + host, + remote, + session.tty, + session.user, + session.pid, + time, + pad_0 = ACTIVE_PADDING, + pad_1 = host_padding, + pad_2 = remote_padding, + pad_3 = username_padding, + pad_4 = tty_padding, + pad_5 = pid_padding as usize); + } +} diff --git a/whered/services/dev.equestria.whered.plist b/whered/services/dev.equestria.whered.plist new file mode 100644 index 0000000..72fdb2c --- /dev/null +++ b/whered/services/dev.equestria.whered.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>dev.equestria.whered</string> + + <key>ProgramArguments</key> + <array> + <string>/usr/local/bin/whered</string> + </array> + + <key>KeepAlive</key> + <true/> + + <key>RunAtLoad</key> + <true/> + + <key>StandardOutPath</key> + <string>/Library/Logs/whered.log</string> +</dict> +</plist> diff --git a/whered/services/whered b/whered/services/whered new file mode 100644 index 0000000..a38a950 --- /dev/null +++ b/whered/services/whered @@ -0,0 +1,12 @@ +#!/sbin/openrc-run + +name="whered" +description="WHRD/UDP Protocol Server" +command="/usr/bin/whered" +command_args="${service_args}" +command_user="whered:whered" + +depend() { + need net + use logger +} diff --git a/whered/services/whered.service b/whered/services/whered.service new file mode 100644 index 0000000..cee24eb --- /dev/null +++ b/whered/services/whered.service @@ -0,0 +1,14 @@ +[Unit] +Description=WHRD/UDP Protocol Server +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=whered +ExecStart=/usr/bin/whered + +[Install] +WantedBy=multi-user.target |