wip: integrating docker/redis

Still untested, but this commit is a checkpoint as there are currently
no errors in the Rust database.
DOCKERFILE AND DOCKER-COMPOSE ARE STILL COMPLETELY UNTESTED AT THIS
POINT
This commit is contained in:
meeg_leeto 2022-04-08 22:29:03 +01:00
parent 4405448fa2
commit 4d83500fed
5 changed files with 282 additions and 147 deletions

201
Cargo.lock generated
View File

@ -2,17 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -22,12 +11,52 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "argh"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb41d85d92dfab96cb95ab023c265c5e4261bb956c0fb49ca06d90c570f1958"
dependencies = [
"argh_derive",
"argh_shared",
]
[[package]]
name = "argh_derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be69f70ef5497dd6ab331a50bd95c6ac6b8f7f17a7967838332743fbd58dc3b5"
dependencies = [
"argh_shared",
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "argh_shared"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f8c380fa28aa1b36107cd97f0196474bb7241bb95a453c5c01a15ac74b2eac"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "atomic" name = "atomic"
version = "0.5.1" version = "0.5.1"
@ -110,6 +139,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "combine"
version = "4.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
dependencies = [
"bytes",
"futures-core",
"memchr",
"pin-project-lite",
"tokio",
"tokio-util",
]
[[package]] [[package]]
name = "cow-utils" name = "cow-utils"
version = "0.1.2" version = "0.1.2"
@ -160,6 +203,12 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]] [[package]]
name = "educe" name = "educe"
version = "0.4.19" version = "0.4.19"
@ -192,18 +241,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.7.0" version = "1.7.0"
@ -330,18 +367,6 @@ name = "hashbrown"
version = "0.11.2" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
dependencies = [
"hashbrown",
]
[[package]] [[package]]
name = "headers" name = "headers"
@ -368,6 +393,15 @@ dependencies = [
"http", "http",
] ]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -385,7 +419,7 @@ checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
"itoa", "itoa 1.0.1",
] ]
[[package]] [[package]]
@ -426,7 +460,7 @@ dependencies = [
"http-body", "http-body",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa 1.0.1",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@ -480,6 +514,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.1" version = "1.0.1"
@ -511,16 +551,6 @@ version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]]
name = "libsqlite3-sys"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb644c388dfaefa18035c12614156d285364769e818893da0dda9030c80ad2ba"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.4" version = "0.5.4"
@ -549,12 +579,13 @@ dependencies = [
name = "lonk" name = "lonk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"argh",
"base64", "base64",
"either",
"figment", "figment",
"rusqlite", "redis",
"serde", "serde",
"tokio", "tokio",
"toml",
"validators", "validators",
"warp", "warp",
] ]
@ -819,12 +850,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.16" version = "0.2.16"
@ -907,6 +932,26 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "redis"
version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852"
dependencies = [
"async-trait",
"bytes",
"combine",
"dtoa",
"futures-util",
"itoa 0.4.8",
"percent-encoding",
"pin-project-lite",
"sha1",
"tokio",
"tokio-util",
"url",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.11" version = "0.2.11"
@ -954,21 +999,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rusqlite"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"memchr",
"smallvec",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -1034,7 +1064,7 @@ version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
dependencies = [ dependencies = [
"itoa", "itoa 1.0.1",
"ryu", "ryu",
"serde", "serde",
] ]
@ -1046,7 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"itoa", "itoa 1.0.1",
"ryu", "ryu",
"serde", "serde",
] ]
@ -1075,6 +1105,21 @@ dependencies = [
"digest 0.10.3", "digest 0.10.3",
] ]
[[package]]
name = "sha1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
dependencies = [
"sha1_smol",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.0" version = "1.4.0"
@ -1360,6 +1405,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -1429,12 +1480,6 @@ dependencies = [
"enum-ordinalize", "enum-ordinalize",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"

View File

@ -6,11 +6,12 @@ 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
[dependencies] [dependencies]
argh = "0.1.7"
base64 = "0.13.0" base64 = "0.13.0"
either = "1.6.1"
figment = { version = "0.10.6", features = ["toml", "env"] } figment = { version = "0.10.6", features = ["toml", "env"] }
rusqlite = "0.27.0" redis = { version = "~0.21.5", features = ["tokio-comp"] }
serde = { version = "~1.0.136", features = ["derive"] } serde = { version = "~1.0.136", features = ["derive"] }
tokio = { version = "~1.17.0", features = ["full"] } tokio = { version = "~1.17.0", features = ["full"] }
toml = "0.5.8"
validators = { version = "~0.24.1", features = ["url-dep"] } validators = { version = "~0.24.1", features = ["url-dep"] }
warp = "0.3.2" warp = "0.3.2"

22
Dockerfile Normal file
View File

@ -0,0 +1,22 @@
# Create the build container to compile
FROM rust:latest as builder
RUN USER=root cargo new --bin lonk
WORKDIR lonk
# Compile dependencies
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
RUN cargo build --release
RUN src/*.rs
# Compile the source
COPY ./src ./src
RUN rm ./target/release/deps/lonk*
RUN cargo build
# Execution container
FROM scratch
COPY --from=build /lonk/target/release/lonk .
CMD ["./lonk"]

17
docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
version: '3.9'
services:
lonk:
build: .
environment:
- PROFILE: release
- LONK_CONFIG: /data/config.toml
volumes:
- ./data:/data
redis:
image: 'redis:alpine'
command: redis-server --save 20 1 --loglevel warning
volumes:
- redis:/data
volumes:
redis:
driver: local

View File

@ -1,11 +1,13 @@
use argh::FromArgs;
use figment::{ use figment::{
providers::{Format, Toml}, providers::{Format, Toml},
Figment, Figment,
}; };
use rusqlite::Connection; use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, str::FromStr, sync::Arc}; use std::{collections::BTreeSet, path::PathBuf, str::FromStr, sync::Arc};
use tokio::sync::mpsc::{self, UnboundedSender};
use validators::prelude::*; use validators::prelude::*;
use warp::{hyper::StatusCode, Filter}; use warp::{filters::BoxedFilter, hyper::StatusCode, Filter};
macro_rules! unwrap_or_unwrap_err { macro_rules! unwrap_or_unwrap_err {
($x:expr) => { ($x:expr) => {
@ -16,62 +18,73 @@ macro_rules! unwrap_or_unwrap_err {
}; };
} }
mod config { #[derive(Serialize, Deserialize, Debug, Validator, Clone)]
use serde::{Deserialize, Serialize}; #[validator(domain(ipv4(Allow), local(NotAllow), at_least_two_labels(Allow), port(Allow)))]
use std::{path::PathBuf, str::FromStr}; struct Url {
use warp::{filters::BoxedFilter, Filter}; domain: String,
port: Option<u16>,
}
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Config { struct DbConfig {
pub db_location: PathBuf, pub address: Url,
pub slug_rules: SlugRules, }
pub serve_rules: ServeRules,
impl Default for DbConfig {
fn default() -> Self {
Self { address: Url::parse_str("redis://127.0.0.1/").unwrap() }
} }
}
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SlugRules { pub struct SlugRules {
pub length: usize, pub length: usize,
pub chars: String, pub chars: String,
} }
#[derive(Deserialize, Serialize, Debug, Clone)] impl Default for SlugRules {
pub enum ServeRules { fn default() -> Self {
File(PathBuf), Self {
Dir(PathBuf), length: 5,
} chars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-".to_string(),
impl ServeRules {
pub fn to_filter(&self) -> BoxedFilter<(warp::fs::File,)> {
match self {
ServeRules::File(file) => warp::fs::file(file.clone()).boxed(),
ServeRules::Dir(dir) => warp::fs::dir(dir.clone()).boxed(),
}
} }
} }
}
impl Default for SlugRules { #[derive(Deserialize, Serialize, Debug, Clone)]
fn default() -> Self { pub enum ServeRules {
Self { File(PathBuf),
length: 5, Dir(PathBuf),
chars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-" }
.to_string(),
} impl ServeRules {
pub fn to_filter(&self) -> BoxedFilter<(warp::fs::File,)> {
match self {
ServeRules::File(file) => warp::fs::file(file.clone()).boxed(),
ServeRules::Dir(dir) => warp::fs::dir(dir.clone()).boxed(),
} }
} }
}
impl Default for ServeRules { impl Default for ServeRules {
fn default() -> Self { fn default() -> Self {
ServeRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap()) ServeRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap())
}
} }
}
impl Default for Config { #[derive(Deserialize, Serialize, Debug, Clone)]
fn default() -> Self { struct Config {
Self { pub db: DbConfig,
db_location: PathBuf::from_str("/etc/lonk/data.db").unwrap(), pub slug_rules: SlugRules,
slug_rules: Default::default(), pub serve_rules: ServeRules,
serve_rules: Default::default(), }
}
impl Default for Config {
fn default() -> Self {
Self {
db: Default::default(),
slug_rules: Default::default(),
serve_rules: Default::default(),
} }
} }
} }
@ -80,13 +93,6 @@ mod config {
#[validator(base64_url(padding(NotAllow)))] #[validator(base64_url(padding(NotAllow)))]
struct Base64WithoutPaddingUrl(String); struct Base64WithoutPaddingUrl(String);
#[derive(Validator)]
#[validator(domain(ipv4(Allow), local(Allow), at_least_two_labels(Allow), port(Allow)))]
struct Url {
domain: String,
port: Option<u16>,
}
impl FromStr for Base64WithoutPaddingUrl { impl FromStr for Base64WithoutPaddingUrl {
type Err = <Self as ValidateString>::Error; type Err = <Self as ValidateString>::Error;
@ -95,17 +101,25 @@ impl FromStr for Base64WithoutPaddingUrl {
} }
} }
struct SlugDatabase(rusqlite::Connection); #[derive(Debug)]
struct SlugDatabase {
tx: UnboundedSender<SlugDbMessage>,
}
#[derive(Debug)]
enum SlugDbMessage {
Add(Slug, Url),
}
impl SlugDatabase { impl SlugDatabase {
fn from_connection(connection: rusqlite::Connection) -> Self { fn from_client(client: redis::Client) -> Self {
// TODO: Check that the database has the necessary format todo!()
SlugDatabase(connection)
} }
fn insert_slug(slug: Slug, url: Url) -> Result<(), ()> { fn insert_slug(&self, slug: Slug, url: Url) -> Result<(), ()> {
todo!(); self.tx
.send(SlugDbMessage::Add(slug, url))
.expect("Could not send message.");
Ok(()) Ok(())
} }
} }
@ -115,6 +129,7 @@ struct SlugFactory {
slug_chars: BTreeSet<char>, slug_chars: BTreeSet<char>,
} }
#[derive(Debug)]
struct Slug(String); struct Slug(String);
enum InvalidSlug { enum InvalidSlug {
@ -123,7 +138,7 @@ enum InvalidSlug {
} }
impl SlugFactory { impl SlugFactory {
fn from_rules(rules: config::SlugRules) -> Self { fn from_rules(rules: SlugRules) -> Self {
let mut slug_chars = BTreeSet::<char>::new(); let mut slug_chars = BTreeSet::<char>::new();
slug_chars.extend(rules.chars.chars()); slug_chars.extend(rules.chars.chars());
@ -152,7 +167,11 @@ impl SlugFactory {
} }
} }
fn shorten<'s>(slug_factory: &SlugFactory, db: SlugDatabase, b64url: &'s str) -> Result<StatusCode, StatusCode> { fn shorten(
slug_factory: &SlugFactory,
db: &SlugDatabase,
b64url: &str,
) -> Result<StatusCode, StatusCode> {
let url = { let url = {
let raw = base64::decode_config(b64url, base64::URL_SAFE_NO_PAD) let raw = base64::decode_config(b64url, base64::URL_SAFE_NO_PAD)
.map_err(|_| warp::http::StatusCode::BAD_REQUEST)?; .map_err(|_| warp::http::StatusCode::BAD_REQUEST)?;
@ -166,10 +185,10 @@ fn shorten<'s>(slug_factory: &SlugFactory, db: SlugDatabase, b64url: &'s str) ->
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn serve() {
// Read configuration // Read configuration
let config_file = std::env::var("LONK_CONFIG").unwrap_or("lonk.toml".to_string()); let config_file = std::env::var("LONK_CONFIG").unwrap_or("lonk.toml".to_string());
let config: config::Config = Figment::new() let config: Config = Figment::new()
.merge(Toml::file(&config_file)) .merge(Toml::file(&config_file))
.extract() .extract()
.expect("Could not parse configuration file."); .expect("Could not parse configuration file.");
@ -178,7 +197,16 @@ async fn main() {
let slug_factory = Arc::new(SlugFactory::from_rules(config.slug_rules)); let slug_factory = Arc::new(SlugFactory::from_rules(config.slug_rules));
// Initialize database // Initialize database
let db = Connection::open(config.db_location); let db = {
let client = if let Some(port) = config.db.address.port {
redis::Client::open((config.db.address.domain, port))
} else {
redis::Client::open(config.db.address.domain)
};
let client = client.expect("Error opening Redis database.");
//let conn = Connection::open(config.db_location).expect("Could not open database.");
Arc::new(SlugDatabase::from_client(client))
};
// GET / // GET /
let homepage = warp::path::end().and(config.serve_rules.to_filter()); let homepage = warp::path::end().and(config.serve_rules.to_filter());
@ -188,7 +216,7 @@ async fn main() {
move |link: Base64WithoutPaddingUrl| { move |link: Base64WithoutPaddingUrl| {
warp::reply::with_status( warp::reply::with_status(
warp::reply(), warp::reply(),
unwrap_or_unwrap_err!(shorten(&slug_factory, &link.0)), unwrap_or_unwrap_err!(shorten(&slug_factory, &db, &link.0)),
) )
} }
}); });
@ -202,3 +230,25 @@ async fn main() {
warp::serve(routes).run(([127, 0, 0, 1], 8892)).await; warp::serve(routes).run(([127, 0, 0, 1], 8892)).await;
} }
#[derive(FromArgs, PartialEq, Debug)]
/// Start lonk.
struct Run {
/// write a default configuration to stdout and quit
#[argh(switch)]
print_default_config: bool,
}
fn main() {
let run = argh::from_env::<Run>();
if run.print_default_config {
println!(
"{}",
toml::to_string(&Config::default())
.expect("Default configuration should always be TOML serializable")
);
} else {
serve();
}
}