mirror of https://github.com/raftario/filite.git
Config and base structure
This commit is contained in:
parent
47abc2bc3d
commit
cf79946c8e
|
@ -1,8 +0,0 @@
|
|||
PORT=8080
|
||||
|
||||
DATABASE=target/database.db
|
||||
|
||||
FILES_DIR=target/static/
|
||||
TEXT_DIR=target/static/
|
||||
|
||||
FILITE_LOG=debug
|
|
@ -71,8 +71,6 @@ jobs:
|
|||
use-cross: true
|
||||
command: build
|
||||
args: --release --target armv7-unknown-linux-musleabihf
|
||||
- name: Strip binary
|
||||
run: strip target/armv7-unknown-linux-musleabihf/release/filite
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
|
|
|
@ -4,3 +4,6 @@ target/
|
|||
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
filite.toml
|
||||
filite.json
|
||||
|
|
File diff suppressed because it is too large
Load Diff
60
Cargo.toml
60
Cargo.toml
|
@ -3,30 +3,64 @@ name = "filite"
|
|||
version = "0.3.0"
|
||||
authors = ["Raphaël Thériault <raphael_theriault@outlook.com>"]
|
||||
edition = "2018"
|
||||
description = "A simple, light and standalone pastebin, URL shortener and file-sharing service"
|
||||
description = "A modular and standalone pastebin, URL shortener and file-sharing service"
|
||||
homepage = "https://github.com/raftario/filite"
|
||||
repository = "https://github.com/raftario/filite"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"file-sharing",
|
||||
"url-shortener",
|
||||
"pastebin"
|
||||
"pastebin",
|
||||
]
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
dotenv = { version = "0.15.0", optional = true }
|
||||
anyhow = "1.0.31"
|
||||
cfg-if = "0.1.10"
|
||||
chrono = "0.4.11"
|
||||
env_logger = "0.7.1"
|
||||
envy = "0.4.1"
|
||||
log = "0.4.8"
|
||||
rusqlite = { version = "0.23.1", features = ["bundled", "chrono"] }
|
||||
log = { version = "0.4.8", features = ["serde"] }
|
||||
rust-argon2 = "0.8.2"
|
||||
serde = { version = "1.0.112", features = ["derive"] }
|
||||
tokio = { version = "0.2.21", features = ["blocking", "fs", "macros"] }
|
||||
toml = "0.5.6"
|
||||
warp = { version = "0.2.3", features = ["multipart", "tls"], default-features = false }
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
serde_json = "1.0.55"
|
||||
sqlx = { version = "0.3.5", features = ["chrono", "runtime-tokio"], default-features = false }
|
||||
structopt = "0.3.15"
|
||||
tokio = { version = "0.2.21", features = ["blocking", "fs"] }
|
||||
warp = { version = "0.2.3", features = ["multipart"], default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
dev = ["dotenv"]
|
||||
default = ["full"]
|
||||
lite = ["sqlite"]
|
||||
classic = [
|
||||
"sqlite",
|
||||
"threaded",
|
||||
"highlight",
|
||||
]
|
||||
full = [
|
||||
"sqlite",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"tls",
|
||||
"threaded",
|
||||
"analytics",
|
||||
"highlight",
|
||||
]
|
||||
|
||||
# Database backends
|
||||
sqlite = ["sqlx/sqlite"]
|
||||
postgres = ["sqlx/postgres"]
|
||||
mysql = ["sqlx/mysql"]
|
||||
|
||||
# TLS support for the database and web server
|
||||
tls = [
|
||||
"sqlx/tls",
|
||||
"warp/tls",
|
||||
]
|
||||
|
||||
# Threaded runtime
|
||||
threaded = ["tokio/rt-threaded"]
|
||||
|
||||
# Various analytics (no external services)
|
||||
analytics = []
|
||||
|
||||
# Syntax highlighting
|
||||
highlight = []
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> The README isn't representative of the current status of the `next` branch and will only be updated once the changes are stabilised.
|
||||
|
||||
A simple, light and standalone pastebin, URL shortener and file-sharing service that hosts **fi**les, redirects **li**nks and stores **te**xts.
|
||||
A modular pastebin, URL shortener and file-sharing service that hosts **fi**les, redirects **li**nks and stores **te**xts.
|
||||
|
||||
[![GitHub Actions](https://github.com/raftario/filite/workflows/Build/badge.svg)](https://github.com/raftario/filite/actions?workflowID=Build)
|
||||
[![Crates.io](https://img.shields.io/crates/v/filite.svg)](https://crates.io/crates/filite)
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
use anyhow::Error;
|
||||
use log::LevelFilter;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
pub database_url: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub logger: LoggerConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub pool: PoolConfig,
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
#[serde(default)]
|
||||
pub tls: TlsConfig,
|
||||
|
||||
#[cfg(feature = "threaded")]
|
||||
#[serde(default)]
|
||||
pub threads: ThreadsConfig,
|
||||
|
||||
#[cfg(feature = "analytics")]
|
||||
#[serde(default)]
|
||||
pub analytics: AnalyticsConfig,
|
||||
|
||||
#[cfg(feature = "highlight")]
|
||||
#[serde(default)]
|
||||
pub highlight: HighlightConfig,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn read(path: impl AsRef<Path>) -> Result<Self, Error> {
|
||||
let file = File::open(path)?;
|
||||
let config = serde_json::from_reader(BufReader::new(file))?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn write(path: impl AsRef<Path>) -> Result<(), Error> {
|
||||
let config = Self {
|
||||
port: 80,
|
||||
database_url: {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "sqlite")] {
|
||||
"filite.db"
|
||||
} else if #[cfg(feature = "postgres")] {
|
||||
"postgresql://localhost:5432/filite"
|
||||
} else if #[cfg(feature = "mysql")] {
|
||||
"mysql://localhost:3306/filite"
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_owned(),
|
||||
logger: Default::default(),
|
||||
pool: Default::default(),
|
||||
#[cfg(feature = "tls")]
|
||||
tls: Default::default(),
|
||||
#[cfg(feature = "threaded")]
|
||||
threads: Default::default(),
|
||||
#[cfg(feature = "analytics")]
|
||||
analytics: Default::default(),
|
||||
#[cfg(feature = "highlight")]
|
||||
highlight: Default::default(),
|
||||
};
|
||||
let file = File::create(path)?;
|
||||
serde_json::to_writer_pretty(BufWriter::new(file), &config)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct LoggerConfig {
|
||||
pub console: LogLevel,
|
||||
pub file: Option<FileLoggerConfig>,
|
||||
}
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileLoggerConfig {
|
||||
#[serde(default)]
|
||||
pub level: LogLevel,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct LogLevel(pub LevelFilter);
|
||||
impl Default for LogLevel {
|
||||
fn default() -> Self {
|
||||
Self(LevelFilter::Info)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct PoolConfig {
|
||||
pub min_size: Option<u32>,
|
||||
pub max_size: Option<u32>,
|
||||
pub connect_timeout: Option<u64>,
|
||||
pub idle_timeout: Option<u64>,
|
||||
pub max_lifetime: Option<u64>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct TlsConfig {
|
||||
pub cert: Option<PathBuf>,
|
||||
pub key: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "threaded")]
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct ThreadsConfig {
|
||||
pub core_threads: Option<usize>,
|
||||
pub max_threads: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "analytics")]
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct AnalyticsConfig {
|
||||
pub views: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "highlight")]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct HighlightConfig {
|
||||
pub theme: String,
|
||||
pub languages: Vec<String>,
|
||||
}
|
||||
#[cfg(feature = "highlight")]
|
||||
impl Default for HighlightConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme: "default".to_owned(),
|
||||
languages: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
use crate::config::LoggerConfig;
|
||||
use anyhow::Error;
|
||||
use log::{self, Level, LevelFilter, Log, Metadata, Record};
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{self, BufWriter, Stderr, Stdout, Write},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
pub fn init(config: &LoggerConfig) -> Result<(), Error> {
|
||||
let logger = Logger {
|
||||
console: (
|
||||
config.console.0,
|
||||
Mutex::new(io::stderr()),
|
||||
Mutex::new(io::stdout()),
|
||||
),
|
||||
file: match &config.file {
|
||||
Some(fc) => Some((
|
||||
fc.level.0,
|
||||
Mutex::new(BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&fc.path)?,
|
||||
)),
|
||||
)),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
log::set_boxed_logger(Box::new(logger))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Logger {
|
||||
console: (LevelFilter, Mutex<Stderr>, Mutex<Stdout>),
|
||||
file: Option<(LevelFilter, Mutex<BufWriter<File>>)>,
|
||||
}
|
||||
|
||||
impl Log for Logger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= self.console.0
|
||||
|| self
|
||||
.file
|
||||
.as_ref()
|
||||
.map_or(false, |(lf, ..)| metadata.level() <= *lf)
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
let target = record.target();
|
||||
let level = record.level();
|
||||
let args = record.args();
|
||||
|
||||
if let Some((lf, bw)) = &self.file {
|
||||
if level <= *lf {
|
||||
bw.lock()
|
||||
.unwrap()
|
||||
.write_fmt(format_args!("[{}]::[{}] {}\n", target, level, args))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
if level <= self.console.0 {
|
||||
if level <= Level::Warn {
|
||||
self.console
|
||||
.1
|
||||
.lock()
|
||||
.unwrap()
|
||||
.write_fmt(format_args!("[{}]::[{}] {}\n", target, level, args))
|
||||
.ok();
|
||||
} else {
|
||||
self.console
|
||||
.2
|
||||
.lock()
|
||||
.unwrap()
|
||||
.write_fmt(format_args!("[{}]::[{}] {}\n", target, level, args))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
self.console.1.lock().unwrap().flush().ok();
|
||||
self.console.2.lock().unwrap().flush().ok();
|
||||
if let Some((_, bw)) = &self.file {
|
||||
bw.lock().unwrap().flush().ok();
|
||||
}
|
||||
}
|
||||
}
|
85
src/main.rs
85
src/main.rs
|
@ -1,9 +1,80 @@
|
|||
use std::env;
|
||||
#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))]
|
||||
compile_error!("You need to select at least one database backend");
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
if env::var_os("FILITE_LOG").is_none() {
|
||||
env::set_var("FILITE_LOG", "INFO");
|
||||
}
|
||||
env_logger::init_from_env("FILITE_LOG");
|
||||
mod config;
|
||||
mod logger;
|
||||
mod runtime;
|
||||
|
||||
use anyhow::Error;
|
||||
use config::Config;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
#[structopt(author, about)]
|
||||
struct Opt {
|
||||
/// Configuration file to use
|
||||
///
|
||||
/// If unspecified, will look for a filite.json
|
||||
/// file in the current working directory.
|
||||
#[structopt(short, long, name = "FILE")]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(StructOpt)]
|
||||
enum Command {
|
||||
/// Initialises the configuration file with default values
|
||||
InitConfig {
|
||||
/// File to write
|
||||
///
|
||||
/// If unspecified, will write to a filite.json
|
||||
/// file in the current working directory.
|
||||
#[structopt(name = "FILE")]
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
/// Initialises the database tables
|
||||
InitDatabase {
|
||||
/// Database connection URL
|
||||
#[structopt(name = "URL")]
|
||||
url: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let args: Opt = Opt::from_args();
|
||||
match &args.command {
|
||||
Some(Command::InitConfig { path }) => {
|
||||
return init_config(match path {
|
||||
Some(_) => path.as_ref(),
|
||||
None => args.config.as_ref(),
|
||||
})
|
||||
}
|
||||
Some(Command::InitDatabase { url }) => return init_database(url),
|
||||
None => (),
|
||||
}
|
||||
|
||||
let config = Config::read(args.config.unwrap_or_else(|| PathBuf::from("filite.json")))?;
|
||||
logger::init(&config.logger)?;
|
||||
|
||||
let mut runtime = runtime::build(&config)?;
|
||||
runtime.block_on(run(&config))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(_config: &Config) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_config(path: Option<&PathBuf>) -> Result<(), Error> {
|
||||
config::Config::write(path.unwrap_or(&PathBuf::from("filite.json")))?;
|
||||
println!("Default config written");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_database(_url: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use crate::config::Config;
|
||||
use anyhow::Error;
|
||||
use log::debug;
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
|
||||
#[cfg_attr(not(feature = "threaded"), allow(unused_variables))]
|
||||
pub fn build(config: &Config) -> Result<Runtime, Error> {
|
||||
let mut builder = Builder::new();
|
||||
builder.basic_scheduler().enable_io();
|
||||
|
||||
#[cfg(feature = "threaded")]
|
||||
{
|
||||
builder.threaded_scheduler();
|
||||
|
||||
let config = &config.threads;
|
||||
if let Some(ct) = config.core_threads {
|
||||
builder.core_threads(ct);
|
||||
}
|
||||
if let Some(mt) = config.max_threads {
|
||||
builder.max_threads(mt);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Building Tokio runtime");
|
||||
Ok(builder.build()?)
|
||||
}
|
Loading…
Reference in New Issue