Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
meeg_leeto | d2e82de338 | |
meeg_leeto | 739b3ad0c5 |
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lonk"
|
name = "lonk"
|
||||||
version = "1.0.1"
|
version = "1.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -13,6 +13,6 @@ rand = "~0.8.5"
|
||||||
redis = { version = "~0.21.5", features = ["tokio-native-tls-comp"] }
|
redis = { version = "~0.21.5", features = ["tokio-native-tls-comp"] }
|
||||||
serde = { version = "~1.0.136", features = ["derive"] }
|
serde = { version = "~1.0.136", features = ["derive"] }
|
||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
tokio = { version = "~1.17.0", features = ["full"] }
|
tokio = { version = "1.22.0", features = ["full"] }
|
||||||
validators = { version = "~0.24.1", features = ["url-dep"] }
|
validators = { version = "~0.24.1", features = ["url-dep"] }
|
||||||
warp = "~0.3.2"
|
warp = "~0.3.2"
|
||||||
|
|
|
@ -47,11 +47,6 @@ way.)
|
||||||
|
|
||||||
**Please make sure you know what you are doing.**
|
**Please make sure you know what you are doing.**
|
||||||
|
|
||||||
> Although I've tried my best for `lonk` to be correct, I cannot guarantee its safety.
|
|
||||||
> Using containerization will provide (at least) some level of extra security, in case
|
|
||||||
> `lonk` is exploitable. By running `lonk` directly as an executable, you are removing a
|
|
||||||
> layer between a potential attacker and your system.
|
|
||||||
|
|
||||||
To run `lonk` as a binary, preferably create a dedicated user and directory:
|
To run `lonk` as a binary, preferably create a dedicated user and directory:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -122,4 +117,4 @@ appreciate the software I've published.
|
||||||
💕 If you like and use `lonk`, consider
|
💕 If you like and use `lonk`, consider
|
||||||
[buying me a coffee](https://www.paypal.me/miguelmurca/2.50).
|
[buying me a coffee](https://www.paypal.me/miguelmurca/2.50).
|
||||||
|
|
||||||
[GPLv3]: https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)
|
[GPLv3]: https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
},
|
},
|
||||||
"log_rules": {
|
"log_rules": {
|
||||||
"error_log_file": "/data/log/error.log",
|
"error_log_file": "/data/log/error.log",
|
||||||
"access_log_file": "/data/log/access.log"
|
"access_log_file": "/data/log/access.log",
|
||||||
|
"link_log_file": "/data/log/links.log"
|
||||||
},
|
},
|
||||||
"serve_rules": {
|
"serve_rules": {
|
||||||
"dir": {
|
"dir": {
|
||||||
|
|
151
src/main.rs
151
src/main.rs
|
@ -131,19 +131,44 @@ mod conf {
|
||||||
/// See the definitions of [`ServeDirRules`] and [`ServeAddr`] for more
|
/// See the definitions of [`ServeDirRules`] and [`ServeAddr`] for more
|
||||||
/// information on the specific configuration.
|
/// information on the specific configuration.
|
||||||
pub struct ServeRules {
|
pub struct ServeRules {
|
||||||
|
#[serde(default)]
|
||||||
/// Configuration for the contents served.
|
/// Configuration for the contents served.
|
||||||
pub dir: ServeDirRules,
|
pub dir: ServeDirRules,
|
||||||
|
#[serde(default)]
|
||||||
/// Configuration for the serve location.
|
/// Configuration for the serve location.
|
||||||
pub addr: ServeAddr,
|
pub addr: ServeAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait for any path to a log file.
|
||||||
|
///
|
||||||
|
/// Implemented for the types of log files (which have sensible defaults),
|
||||||
|
/// and for `Option<T>` where `T` is a type of log file, so that an omitted option
|
||||||
|
/// in the configuration files can be resolved with `.path()` into the default path.
|
||||||
|
pub trait LogFilePath {
|
||||||
|
fn path(&self) -> &PathBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct ErrorLogFilePath(PathBuf);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct AccessLogFilePath(PathBuf);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct LinkLogFilePath(PathBuf);
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
/// Configuration of logging by lonk.
|
/// Configuration of logging by lonk.
|
||||||
pub struct LogRules {
|
pub struct LogRules {
|
||||||
|
#[serde(default)]
|
||||||
/// Where to write error logs to. The file will be appended to.
|
/// Where to write error logs to. The file will be appended to.
|
||||||
pub error_log_file: PathBuf,
|
pub error_log_file: ErrorLogFilePath,
|
||||||
/// Where to write access ogs to. The file will be appended to.
|
#[serde(default)]
|
||||||
pub access_log_file: PathBuf,
|
/// Where to write access logs to. The file will be appended to.
|
||||||
|
pub access_log_file: AccessLogFilePath,
|
||||||
|
#[serde(default)]
|
||||||
|
/// Where to write link shortening logs to. The file will be appended to.
|
||||||
|
pub link_log_file: LinkLogFilePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
@ -161,10 +186,13 @@ mod conf {
|
||||||
pub version: Option<usize>,
|
pub version: Option<usize>,
|
||||||
/// Configuration regarding the Redis database.
|
/// Configuration regarding the Redis database.
|
||||||
pub db: DbConfig,
|
pub db: DbConfig,
|
||||||
|
#[serde(default)]
|
||||||
/// Configuration regarding logging.
|
/// Configuration regarding logging.
|
||||||
pub log_rules: LogRules,
|
pub log_rules: LogRules,
|
||||||
|
#[serde(default)]
|
||||||
/// Configuration regarding the types of (URL shorten) slugs produced.
|
/// Configuration regarding the types of (URL shorten) slugs produced.
|
||||||
pub slug_rules: SlugRules,
|
pub slug_rules: SlugRules,
|
||||||
|
#[serde(default)]
|
||||||
/// Configuration regarding where and how the HTTP server is served.
|
/// Configuration regarding where and how the HTTP server is served.
|
||||||
pub serve_rules: ServeRules,
|
pub serve_rules: ServeRules,
|
||||||
}
|
}
|
||||||
|
@ -183,6 +211,7 @@ mod conf {
|
||||||
ServeDirNotExists(PathBuf),
|
ServeDirNotExists(PathBuf),
|
||||||
AccessLogDirectoryNotExists(PathBuf),
|
AccessLogDirectoryNotExists(PathBuf),
|
||||||
ErrorLogDirectoryNotExists(PathBuf),
|
ErrorLogDirectoryNotExists(PathBuf),
|
||||||
|
LinkLogDirectoryNotExists(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -223,24 +252,30 @@ mod conf {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check access and error log parent directories
|
// Check access and error log parent directories
|
||||||
|
fn check_exists<E>(path: &PathBuf, to_err: impl FnOnce(PathBuf) -> E) -> Result<(), E> {
|
||||||
|
let weak_canonical = normalize_path(path);
|
||||||
|
if let Some(parent) = weak_canonical.parent() {
|
||||||
|
if !parent.exists() {
|
||||||
|
return Err(to_err(parent.to_path_buf()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
// - Access log file
|
// - Access log file
|
||||||
let weak_canonical = normalize_path(&self.log_rules.access_log_file);
|
check_exists(
|
||||||
if let Some(parent) = weak_canonical.parent() {
|
self.log_rules.access_log_file.path(),
|
||||||
if !parent.exists() {
|
|path| ConfigParseError::AccessLogDirectoryNotExists(path),
|
||||||
return Err(ConfigParseError::AccessLogDirectoryNotExists(
|
)?;
|
||||||
parent.to_path_buf(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// - Error log file
|
// - Error log file
|
||||||
let weak_canonical = normalize_path(&self.log_rules.error_log_file);
|
check_exists(
|
||||||
if let Some(parent) = weak_canonical.parent() {
|
self.log_rules.error_log_file.path(),
|
||||||
if !parent.exists() {
|
|path| ConfigParseError::ErrorLogDirectoryNotExists(path),
|
||||||
return Err(ConfigParseError::ErrorLogDirectoryNotExists(
|
)?;
|
||||||
parent.to_path_buf(),
|
// - Link log file
|
||||||
));
|
check_exists(
|
||||||
}
|
&self.log_rules.link_log_file.path(),
|
||||||
}
|
|path| ConfigParseError::LinkLogDirectoryNotExists(path),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
@ -308,8 +343,8 @@ mod conf {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
concat!("Configuration file has outdated version.\n",
|
concat!("Configuration file has outdated version.\n",
|
||||||
"Expected version field to be {} but got {}."),
|
"Expected version field to be {} but got {}."),
|
||||||
|
config_version(),
|
||||||
old_version,
|
old_version,
|
||||||
config_version()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ConfigParseError::ServeDirNotExists(dir) => {
|
ConfigParseError::ServeDirNotExists(dir) => {
|
||||||
|
@ -342,10 +377,31 @@ mod conf {
|
||||||
ConfigParseError::ErrorLogDirectoryNotExists(dir) => {
|
ConfigParseError::ErrorLogDirectoryNotExists(dir) => {
|
||||||
eprintln!("Error log file should have parent directory {}, but this directory does not exist.", dir.to_string_lossy())
|
eprintln!("Error log file should have parent directory {}, but this directory does not exist.", dir.to_string_lossy())
|
||||||
}
|
}
|
||||||
|
ConfigParseError::LinkLogDirectoryNotExists(dir) => {
|
||||||
|
eprintln!("Link registration log file should have parent directory {}, but this directory does not exist.", dir.to_string_lossy())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LogFilePath for ErrorLogFilePath {
|
||||||
|
fn path(&self) -> &PathBuf {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogFilePath for AccessLogFilePath {
|
||||||
|
fn path(&self) -> &PathBuf {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogFilePath for LinkLogFilePath {
|
||||||
|
fn path(&self) -> &PathBuf {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Default implementations
|
// Default implementations
|
||||||
|
|
||||||
|
@ -401,11 +457,30 @@ mod conf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ErrorLogFilePath {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self("/etc/lonk/log/error.log".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AccessLogFilePath {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self("/etc/lonk/log/access.log".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LinkLogFilePath {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self("/etc/lonk/log/links.log".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for LogRules {
|
impl Default for LogRules {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
error_log_file: "/etc/lonk/log/error.log".into(),
|
error_log_file: ErrorLogFilePath::default(),
|
||||||
access_log_file: "/etc/lonk/log/access.log".into(),
|
access_log_file: AccessLogFilePath::default(),
|
||||||
|
link_log_file: LinkLogFilePath::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -916,7 +991,6 @@ mod service {
|
||||||
/// Affine to logging
|
/// Affine to logging
|
||||||
pub mod log {
|
pub mod log {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use tokio::{fs::OpenOptions, io::AsyncWriteExt, sync};
|
use tokio::{fs::OpenOptions, io::AsyncWriteExt, sync};
|
||||||
|
|
||||||
/// A struct responsible for logging events, per messages received from
|
/// A struct responsible for logging events, per messages received from
|
||||||
|
@ -924,25 +998,37 @@ mod service {
|
||||||
pub struct Logger {
|
pub struct Logger {
|
||||||
access_tx: sync::mpsc::UnboundedSender<String>,
|
access_tx: sync::mpsc::UnboundedSender<String>,
|
||||||
error_tx: sync::mpsc::UnboundedSender<String>,
|
error_tx: sync::mpsc::UnboundedSender<String>,
|
||||||
|
link_tx: sync::mpsc::UnboundedSender<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
pub fn from_log_rules(config: &crate::conf::LogRules) -> Self {
|
pub fn from_log_rules(config: &crate::conf::LogRules) -> Self {
|
||||||
|
use crate::conf::LogFilePath;
|
||||||
|
|
||||||
// Create the communication channels
|
// Create the communication channels
|
||||||
let (access_tx, access_rx) = sync::mpsc::unbounded_channel::<String>();
|
let (access_tx, access_rx) = sync::mpsc::unbounded_channel::<String>();
|
||||||
let (error_tx, error_rx) = sync::mpsc::unbounded_channel::<String>();
|
let (error_tx, error_rx) = sync::mpsc::unbounded_channel::<String>();
|
||||||
|
let (link_tx, link_rx) = sync::mpsc::unbounded_channel::<String>();
|
||||||
|
|
||||||
// Start the logging tasks
|
// Start the logging tasks
|
||||||
tokio::spawn(Self::logging_task(
|
tokio::spawn(Self::logging_task(
|
||||||
access_rx,
|
access_rx,
|
||||||
config.access_log_file.clone(),
|
config.access_log_file.path().clone(),
|
||||||
|
));
|
||||||
|
tokio::spawn(Self::logging_task(
|
||||||
|
error_rx,
|
||||||
|
config.error_log_file.path().clone(),
|
||||||
|
));
|
||||||
|
tokio::spawn(Self::logging_task(
|
||||||
|
link_rx,
|
||||||
|
config.link_log_file.path().clone(),
|
||||||
));
|
));
|
||||||
tokio::spawn(Self::logging_task(error_rx, config.error_log_file.clone()));
|
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
Logger {
|
Logger {
|
||||||
access_tx,
|
access_tx,
|
||||||
error_tx,
|
error_tx,
|
||||||
|
link_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -966,6 +1052,16 @@ mod service {
|
||||||
self.error_tx.send(msg).map_err(|_| ())
|
self.error_tx.send(msg).map_err(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a message into the link log file.
|
||||||
|
///
|
||||||
|
/// Returns a Result with empty type; if posting the log message
|
||||||
|
/// failed for any reason, it's unlikely to recover, and the user
|
||||||
|
/// should decide either to stop logging, ignore these errors, or
|
||||||
|
/// halt the program.
|
||||||
|
pub fn link(&self, msg: String) -> Result<(), ()> {
|
||||||
|
self.link_tx.send(msg).map_err(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
/// The task responsible for receiving the log messages and actually
|
/// The task responsible for receiving the log messages and actually
|
||||||
/// writing them into the corresponding files. One task is created
|
/// writing them into the corresponding files. One task is created
|
||||||
/// for each target file.
|
/// for each target file.
|
||||||
|
@ -1068,7 +1164,7 @@ async fn shorten(
|
||||||
Ok(result) => match result {
|
Ok(result) => match result {
|
||||||
service::db::AddResult::Success(slug) => {
|
service::db::AddResult::Success(slug) => {
|
||||||
logger
|
logger
|
||||||
.access(format!("{} -> {}\n", slug.inner_str(), url))
|
.link(format!("{} -> {}\n", slug.inner_str(), url))
|
||||||
.ok();
|
.ok();
|
||||||
Ok(slug)
|
Ok(slug)
|
||||||
}
|
}
|
||||||
|
@ -1237,8 +1333,7 @@ async fn serve() {
|
||||||
// GET /
|
// GET /
|
||||||
// This should be the last thing matched, so that anything that doesn't
|
// This should be the last thing matched, so that anything that doesn't
|
||||||
// match another filter will try to match a file.
|
// match another filter will try to match a file.
|
||||||
let homepage = warp::get()
|
let homepage = warp::get().and(config.serve_rules.dir.to_filter());
|
||||||
.and(config.serve_rules.dir.to_filter());
|
|
||||||
|
|
||||||
let get_routes = warp::get().and(link.or(homepage));
|
let get_routes = warp::get().and(link.or(homepage));
|
||||||
let post_routes = warp::post().and(shorten);
|
let post_routes = warp::post().and(shorten);
|
||||||
|
|
Loading…
Reference in New Issue