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.
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]]
name = "aho-corasick"
version = "0.7.18"
@ -22,12 +11,52 @@ dependencies = [
"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]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "atomic"
version = "0.5.1"
@ -110,6 +139,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "cow-utils"
version = "0.1.2"
@ -160,6 +203,12 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "educe"
version = "0.4.19"
@ -192,18 +241,6 @@ dependencies = [
"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]]
name = "fastrand"
version = "1.7.0"
@ -330,18 +367,6 @@ name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "headers"
@ -368,6 +393,15 @@ dependencies = [
"http",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -385,7 +419,7 @@ checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
dependencies = [
"bytes",
"fnv",
"itoa",
"itoa 1.0.1",
]
[[package]]
@ -426,7 +460,7 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
"itoa",
"itoa 1.0.1",
"pin-project-lite",
"socket2",
"tokio",
@ -480,6 +514,12 @@ dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.1"
@ -511,16 +551,6 @@ version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "linked-hash-map"
version = "0.5.4"
@ -549,12 +579,13 @@ dependencies = [
name = "lonk"
version = "0.1.0"
dependencies = [
"argh",
"base64",
"either",
"figment",
"rusqlite",
"redis",
"serde",
"tokio",
"toml",
"validators",
"warp",
]
@ -819,12 +850,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
@ -907,6 +932,26 @@ dependencies = [
"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]]
name = "redox_syscall"
version = "0.2.11"
@ -954,21 +999,6 @@ dependencies = [
"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]]
name = "rustc_version"
version = "0.4.0"
@ -1034,7 +1064,7 @@ version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
dependencies = [
"itoa",
"itoa 1.0.1",
"ryu",
"serde",
]
@ -1046,7 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"itoa 1.0.1",
"ryu",
"serde",
]
@ -1075,6 +1105,21 @@ dependencies = [
"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]]
name = "signal-hook-registry"
version = "1.4.0"
@ -1360,6 +1405,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.2"
@ -1429,12 +1480,6 @@ dependencies = [
"enum-ordinalize",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
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
[dependencies]
argh = "0.1.7"
base64 = "0.13.0"
either = "1.6.1"
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"] }
tokio = { version = "~1.17.0", features = ["full"] }
toml = "0.5.8"
validators = { version = "~0.24.1", features = ["url-dep"] }
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::{
providers::{Format, Toml},
Figment,
};
use rusqlite::Connection;
use std::{collections::BTreeSet, str::FromStr, sync::Arc};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, path::PathBuf, str::FromStr, sync::Arc};
use tokio::sync::mpsc::{self, UnboundedSender};
use validators::prelude::*;
use warp::{hyper::StatusCode, Filter};
use warp::{filters::BoxedFilter, hyper::StatusCode, Filter};
macro_rules! unwrap_or_unwrap_err {
($x:expr) => {
@ -16,62 +18,73 @@ macro_rules! unwrap_or_unwrap_err {
};
}
mod config {
use serde::{Deserialize, Serialize};
use std::{path::PathBuf, str::FromStr};
use warp::{filters::BoxedFilter, Filter};
#[derive(Serialize, Deserialize, Debug, Validator, Clone)]
#[validator(domain(ipv4(Allow), local(NotAllow), at_least_two_labels(Allow), port(Allow)))]
struct Url {
domain: String,
port: Option<u16>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Config {
pub db_location: PathBuf,
pub slug_rules: SlugRules,
pub serve_rules: ServeRules,
#[derive(Deserialize, Serialize, Debug, Clone)]
struct DbConfig {
pub address: Url,
}
impl Default for DbConfig {
fn default() -> Self {
Self { address: Url::parse_str("redis://127.0.0.1/").unwrap() }
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SlugRules {
pub length: usize,
pub chars: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SlugRules {
pub length: usize,
pub chars: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ServeRules {
File(PathBuf),
Dir(PathBuf),
}
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 {
fn default() -> Self {
Self {
length: 5,
chars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-".to_string(),
}
}
}
impl Default for SlugRules {
fn default() -> Self {
Self {
length: 5,
chars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
.to_string(),
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ServeRules {
File(PathBuf),
Dir(PathBuf),
}
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 {
fn default() -> Self {
ServeRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap())
}
impl Default for ServeRules {
fn default() -> Self {
ServeRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap())
}
}
impl Default for Config {
fn default() -> Self {
Self {
db_location: PathBuf::from_str("/etc/lonk/data.db").unwrap(),
slug_rules: Default::default(),
serve_rules: Default::default(),
}
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Config {
pub db: DbConfig,
pub slug_rules: SlugRules,
pub serve_rules: ServeRules,
}
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)))]
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 {
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 {
fn from_connection(connection: rusqlite::Connection) -> Self {
// TODO: Check that the database has the necessary format
SlugDatabase(connection)
fn from_client(client: redis::Client) -> Self {
todo!()
}
fn insert_slug(slug: Slug, url: Url) -> Result<(), ()> {
todo!();
fn insert_slug(&self, slug: Slug, url: Url) -> Result<(), ()> {
self.tx
.send(SlugDbMessage::Add(slug, url))
.expect("Could not send message.");
Ok(())
}
}
@ -115,6 +129,7 @@ struct SlugFactory {
slug_chars: BTreeSet<char>,
}
#[derive(Debug)]
struct Slug(String);
enum InvalidSlug {
@ -123,7 +138,7 @@ enum InvalidSlug {
}
impl SlugFactory {
fn from_rules(rules: config::SlugRules) -> Self {
fn from_rules(rules: SlugRules) -> Self {
let mut slug_chars = BTreeSet::<char>::new();
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 raw = base64::decode_config(b64url, base64::URL_SAFE_NO_PAD)
.map_err(|_| warp::http::StatusCode::BAD_REQUEST)?;
@ -166,10 +185,10 @@ fn shorten<'s>(slug_factory: &SlugFactory, db: SlugDatabase, b64url: &'s str) ->
}
#[tokio::main]
async fn main() {
async fn serve() {
// Read configuration
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))
.extract()
.expect("Could not parse configuration file.");
@ -178,7 +197,16 @@ async fn main() {
let slug_factory = Arc::new(SlugFactory::from_rules(config.slug_rules));
// 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 /
let homepage = warp::path::end().and(config.serve_rules.to_filter());
@ -188,7 +216,7 @@ async fn main() {
move |link: Base64WithoutPaddingUrl| {
warp::reply::with_status(
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;
}
#[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();
}
}