Compare commits

..

26 Commits

Author SHA1 Message Date
blek a685f5db84
fix drag and drop size 2023-11-03 14:50:02 +10:00
blek 72abd4d5bd
add a ToS switch 2023-11-03 00:08:45 +10:00
blek 57ea39ceda
make code blocks always in one line 2023-11-03 00:02:40 +10:00
blek 575f7d31e8
match colors with the console UI 2023-11-02 23:49:26 +10:00
blek 79ef0634f9
web UI for curlapi/help 2023-11-02 23:44:46 +10:00
blek aca37bc52b
curl API help 2023-11-02 20:00:39 +10:00
blek 164af11b8b
fix compile warning 2023-11-02 19:01:21 +10:00
blek 9ccd3d6212
scratch curl API 2023-11-01 19:19:51 +10:00
blek bae978d1ec
fix wrong .env.example time format 2023-11-01 03:42:14 +10:00
blek 7b607c5ba6
fix typo 2023-11-01 03:11:19 +10:00
blek 6120ce7a30
get rid of function panics in the build.rs 2023-11-01 01:28:00 +10:00
blek 6302d11acd
fix filed dockerfile and use alpine as a base image 2023-11-01 01:26:57 +10:00
blek b1463519d7
fix prod dockerfile build error 2023-11-01 00:46:54 +10:00
blek a0c6c27e8c
bump Cargo.toml version 2023-10-29 19:50:09 +10:00
blek 12614078ad
print version at the footer 2023-10-29 19:44:31 +10:00
blek 99807b9722
fix few obscure errors 2023-10-29 19:25:53 +10:00
blek 32375127a9
display errors 2023-10-29 19:25:33 +10:00
blek 8d5d739568
include branch data 2023-10-29 19:14:58 +10:00
blek b9f0d80dc3
move out code to a function 2023-10-29 19:13:27 +10:00
blek d09f88f7fa
load commit hash at compile time 2023-10-29 19:11:38 +10:00
blek b7b303afb3
remove warp static dir in favor of static_dir! macro 2023-10-29 19:00:54 +10:00
blek ac393b2b7b
add note about extensive configuration 2023-10-29 14:14:50 +10:00
blek 0d02c91626
init: DEPLOYING.md 2023-10-29 14:14:43 +10:00
blek a6d69c319e
Merge remote-tracking branch 'origin/master' into 0.2-dev 2023-10-27 21:29:45 +10:00
blek 8a41d4bef2
add instances list 2023-10-27 10:39:29 +10:00
blek b0cc93c488
remove the indev banner 2023-10-27 10:26:55 +10:00
25 changed files with 539 additions and 49 deletions

33
DEPLOYING.md Normal file
View File

@ -0,0 +1,33 @@
# Deploying a production instance
Hi fellow sysadmins!
First of all, I want to thank you for using my piece of software.
The instructions can be found below
## Deploying a basic instance
To deploy a basic instance for general public use, follow these simple steps:
1. Clone this repo
2. Copy `docker-compose.prod.yml` to `docker-compose.yml` and edit it to fit your environment
3. Now, there are a few config files that need to be edited by you: `.env`, `filed/.env` and `janitord/.env`. Each directory contains an `.env.example`, and the configuration is pretty straightforward. However, if you are lost check this out: [filed config](#filed-configuration), [janitord config](#janitord-configuration).
4. Configure fileD using `filed/config/filed.toml`. The example is in the same folder. Example contains a lot of self-documenting comments, so it should be pretty simple too.
5. Set `REDIS_PASS` to a secure long string. Not exactly required, but this is something you would want to do
6. Create and start containers with `docker-compose up -d`
7. Route your top level reverse proxy to the `caddy` service or to the port that you opened via the docker compose file.
## More extensive configuration
Well, generally, time-wise, it is not really a good idea to create a custom services configuration.
However, I will guide you through the basic minimal configuration.
Basically, the most minimal blek!File is a fileD service connected to a redis database.
I think that if you are clinically insance, you can set these up as a systemd services or a `screen`ed program.
However, its not really recommended to run this without janitorD as unused files will just clog up your filesystem.
The two requirements for janitorD are to have access to the fileD's usercontent directory and the Redis database.
The default docker configuration mounts `/opt/user_content` to the same volume for both fileD and janitorD.
## FileD configuration
Unless you are running in some kind of super customized docker compose environment, just copying the `.env.example` to `.env` should be enough to get it to run.
Don't forget to set the `REDIS_PASS` to the same value across all services
## JanitorD configuration
Same as [filed config](#filed-configuration), don't forget to set `REDIS_PASS` to a valid value

View File

@ -1,10 +1,3 @@
| ⚠️ This is in a rather early stage of development and shouldn't be deployed |
| --------------------------------------------------------------------------- |
Even though this project is mature enough to be deployed in a public instance,
this is highly discouraged.
However, if you do this, be prepared for [DOS](https://en.wikipedia.org/wiki/Denial-of-service_attack) issues and API changes.
<br/>
<h1 align='center'> <h1 align='center'>
<img src="./filed/static/android-chrome-192x192.png"/> <img src="./filed/static/android-chrome-192x192.png"/>
@ -16,6 +9,21 @@ blek! File is a free service that would help you with file sharing.
The principle is very simple: you upload a file, then download it from another device. The file will be deleted after 1 download or 30 minutes. The principle is very simple: you upload a file, then download it from another device. The file will be deleted after 1 download or 30 minutes.
## Public instances
List of official instances
| Name | Administrator | URL |
| --- | --- | --- |
| 🌠 blek! File | b1ek &lt;me@blek.codes&gt; | [https://file.blek.codes](file.blek.codes) |
To add your instance in this list, fork and open a PR.
To qualify, your instance must be having:
1. Uploads without a password turned on
2. Have proper ToS
3. Come up with a unique name
4. Have a public administrator email
## Licensing ## Licensing
This software is released under GPL3 license, a copyleft license that protects users' freedom by ensuring that all future copies of this software are open source as well. This software is released under GPL3 license, a copyleft license that protects users' freedom by ensuring that all future copies of this software are open source as well.

View File

@ -7,6 +7,7 @@ services:
networks: networks:
bfile: bfile:
volumes: volumes:
- './.git:/opt/code/.git'
- './filed:/opt/code' - './filed:/opt/code'
- './filed/config:/etc/filed' - './filed/config:/etc/filed'
- '/opt/code/target' - '/opt/code/target'

View File

@ -2,8 +2,8 @@ version: '3.7'
services: services:
filed: filed:
build: build:
context: filed context: .
dockerfile: Dockerfile.prod dockerfile: filed/Dockerfile.prod
networks: networks:
bfile: bfile:
volumes: volumes:

2
filed/Cargo.lock generated
View File

@ -358,7 +358,7 @@ dependencies = [
[[package]] [[package]]
name = "filed" name = "filed"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"argon2", "argon2",
"askama", "askama",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "filed" name = "filed"
version = "0.1.0" version = "0.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

View File

@ -2,16 +2,17 @@
FROM rust:alpine as builder FROM rust:alpine as builder
WORKDIR /opt/build WORKDIR /opt/build
COPY . . COPY filed .
COPY ./.git ./.git
RUN apk add --no-cache musl-dev upx nodejs yarn && \ RUN apk add --no-cache git musl-dev upx nodejs yarn && \
yarn global add uglify-js yarn global add uglify-js@3.17.4
RUN cargo b -r RUN cargo b -r
RUN strip target/release/filed && upx --best target/release/filed RUN strip target/release/filed && upx --best target/release/filed
# --- deploy --- # --- deploy ---
FROM busybox:musl FROM alpine:3.17
RUN mkdir /config RUN mkdir /config
WORKDIR /config WORKDIR /config

View File

@ -1,5 +1,5 @@
use std::{fs, path::PathBuf, ffi::OsStr, process::Command}; use std::{fs, path::PathBuf, ffi::OsStr, process::Command, error::Error};
use css_minify::optimizations::{Minifier, Level}; use css_minify::optimizations::{Minifier, Level};
@ -17,6 +17,19 @@ fn extfilter(valid: String, x: Option<&OsStr>) -> bool {
ext == valid ext == valid
} }
fn system(cmd: &str, args: &[&str]) -> Result<String, Box<dyn Error>> {
let out = Command::new(cmd)
.args(args)
.output()
?;
if out.stderr.len() != 0 {
panic!("Got this while running {cmd} with \"{}\": {}", args.join(" "), String::from_utf8(out.stderr).unwrap())
}
Ok(String::from_utf8(out.stdout)?)
}
fn main() { fn main() {
println!("cargo:rerun-if-changed=static/assets"); println!("cargo:rerun-if-changed=static/assets");
@ -52,11 +65,24 @@ fn main() {
scripts.iter().for_each(|asset| { scripts.iter().for_each(|asset| {
Command::new("uglifyjs") Command::new("uglifyjs")
.arg("-c")
.arg(asset) .arg(asset)
.arg("-o") .arg("-o")
.arg(asset_path(asset)) .arg(asset_path(asset))
.arg("-c")
.spawn() .spawn()
.unwrap(); .unwrap();
}) });
let commit = system("git", &["rev-parse", "HEAD"]).map_err(|x| x.to_string());
let branch = system("git", &["rev-parse", "--abbrev-ref", "HEAD"]).map_err(|x| x.to_string());
match commit {
Err(err) => panic!("Can't get commit: {}", err),
Ok(commit) => println!("cargo:rustc-env=COMMIT_HASH={commit}")
}
match branch {
Err(err) => panic!("Can't get commit: {}", err),
Ok(branch) => println!("cargo:rustc-env=COMMIT_BRANCH={branch}")
}
} }

View File

@ -84,3 +84,7 @@ sudo_delete=false
# Whether /api/upload is enabled # Whether /api/upload is enabled
# It is not recommended to enable it if API key auth is not enabled # It is not recommended to enable it if API key auth is not enabled
upload=false upload=false
# Whether curlapi is enabled
# curl {url}/curlapi/help for more info
curlapi=true

View File

@ -112,6 +112,10 @@ pub struct APISettings {
/// Whether /api/upload is enabled /// Whether /api/upload is enabled
#[serde(default)] #[serde(default)]
pub upload: bool, pub upload: bool,
/// Whether curlapi is enabled
#[serde(default)]
pub curlapi: bool
} }
impl Default for APISettings { impl Default for APISettings {
@ -123,7 +127,8 @@ impl Default for APISettings {
get_all_own_only: true, get_all_own_only: true,
delete: false, delete: false,
sudo_delete: false, sudo_delete: false,
upload: false upload: false,
curlapi: true
} }
} }
} }

View File

@ -15,6 +15,23 @@ pub struct Redis {
pub prefix: String pub prefix: String
} }
#[derive(Debug, Clone)]
pub struct VersionData {
pub commit: String,
pub short_commit: String,
pub branch: String
}
impl Default for VersionData {
fn default() -> Self {
VersionData {
commit: env!("COMMIT_HASH").to_string(),
short_commit: env!("COMMIT_HASH").to_string().chars().take(6).collect(),
branch: env!("COMMIT_BRANCH").to_string()
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Env { pub struct Env {
pub logging: bool, pub logging: bool,
@ -24,7 +41,8 @@ pub struct Env {
pub filedir: String, pub filedir: String,
pub instanceurl: String, pub instanceurl: String,
pub uploadspath: String, pub uploadspath: String,
pub confpath: String pub confpath: String,
pub version: VersionData
} }
fn get_var<T: Into<String>, O: From<String>>(name: T) -> Result<O, String> { fn get_var<T: Into<String>, O: From<String>>(name: T) -> Result<O, String> {
@ -140,7 +158,8 @@ pub fn loadenv() -> Result<Env, Box<dyn std::error::Error>> {
return Err(format!("CONF_FILE is {}, which is not a file!", spath).into()) return Err(format!("CONF_FILE is {}, which is not a file!", spath).into())
} }
spath spath
} },
version: VersionData::default()
} }
) )
} }

11
filed/src/web/curlapi.rs Normal file
View File

@ -0,0 +1,11 @@
use warp::{Filter, reply::Reply, reject::Rejection};
use super::state::SharedState;
mod upload;
mod help;
pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
upload::get_routes(state.clone())
.or(help::get_routes(state))
}

View File

@ -0,0 +1,85 @@
use askama::Template;
use warp::{Filter, reply::{Reply, html}, reject::Rejection};
use crate::web::{state::SharedState, pages::CurlHelpPage, rejection::HttpReject};
pub async fn help(state: SharedState, ua: String) -> Result<Box<dyn Reply>, Rejection> {
if ! ua.starts_with("curl/") {
let page = CurlHelpPage { conf: state.config.clone(), env: state.env.clone() };
return Ok(
Box::new(
html(page.render().map_err(|x| HttpReject::AskamaError(x))?)
)
)
}
let brand = format!(
"{} \x1b[1m{}\x1b[0m {}",
state.config.brand.instance_emoji,
state.config.brand.instance_name,
{
if state.config.brand.instance_name != "blek! File" {
"\n\x1b[90mPowered by blek! File\x1b[0m"
} else { "" }
}
);
let mut warns: String = String::new();
if ! state.config.api.curlapi {
warns += "\x1b[1;31mWarning: curl API is disabled on this instance.\nYou can use the web UI to upload files.\x1b[0m\n\n"
}
if ! state.config.files.allow_uploads {
warns += {
format!(
"\x1b[1;31mWarning: all uploads are disabled on this instance{}\x1b[0m",
{
if let Some(reason) = state.config.files.upload_disable_reason {
format!(" for this reason:\n\"{}\"", reason)
} else { ".".to_string() }
}
).as_str()
}
}
let instance = state.env.instanceurl;
let help =
format!(
"To upload a new file, you can POST it like this:
\x1b[32mcurl\x1b[0m \x1b[33m-X POST\x1b[0m \x1b[34m{instance}/curlapi/upload\x1b[0m \x1b[33m-F'file=@file.txt'\x1b[0m \x1b[33m-F'tos_consent=on'\x1b[0m
You can also add a password:
\x1b[32mcurl\x1b[0m \x1b[33m-X POST\x1b[0m \x1b[34m{instance}/curlapi/upload\x1b[0m \x1b[33m-F'file=@file.txt'\x1b[0m \x1b[33m-F'filename=uwu'\x1b[0m \x1b[33m-F'tos_consent=on'\x1b[0m \x1b[33m-F'named=on'\x1b[0m
The `named=on` switch is neede because this API is basically
the HTML used at the regular web UI form wrapped into this URL
\x1b[1;32mIMPORTANT:\x1b[0m Read the terms of service \x1b[1mbefore\x1b[0m uploading the file!
The ToS can be found here: \x1b[34m{instance}/tos\x1b[0m .
{warns}"
);
Ok(
Box::new(
format!("
\x1b[31m \x1b[0m \x1b[35m \x1b[0m
\x1b[32m\x1b[0m \x1b[95m \x1b[0m
\x1b[34m\x1b[0m \x1b[35m \x1b[0m
{brand}
{help}
").to_string())
)
}
pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::any()
.and(warp::path!("curlapi" / "help"))
.and(
warp::any()
.map(move || state.clone())
)
.and(
warp::header::<String>("user-agent")
)
.and_then(help)
}

View File

@ -0,0 +1,139 @@
use std::net::IpAddr;
use warp::{filters::multipart::FormData, reply::{Reply, with_status}, reject::Rejection, Filter};
use warp_real_ip::real_ip;
use crate::{web::{state::SharedState, forms::{FormElement, UploadFormData}, rejection::HttpReject}, files::File};
pub async fn upload(form: FormData, ip: Option<IpAddr>, state: SharedState) -> Result<Box<dyn Reply>, Rejection> {
if ! state.config.files.allow_uploads {
return Ok(
Box::new(
with_status(
match state.config.files.upload_disable_reason {
Some(reason) => format!("Uploads are disabled for the following reason:\n{reason}"),
None => "Uploads are disabled.".into()
},
warp::http::StatusCode::SERVICE_UNAVAILABLE
)
)
)
}
let params = FormElement::from_formdata(form)
.await
.map_err(|x| HttpReject::WarpError(x))?;
if let Some(consent) = params.get("tos_consent") {
if consent.data != "on".bytes().collect::<Vec<u8>>() {
return Ok(
Box::new(
format!("You need to agree to the ToS to upload a file.\nSee {}/curlapi/help for details\n\nTo agree to the ToS, add a -F'tos_consent=on'\n", state.env.instanceurl)
)
)
}
} else {
return Ok(
Box::new(
format!("You need to agree to the ToS to upload a file.\nSee {}/curlapi/help for details\n\nTo agree to the ToS, add a -F'tos_consent=on'\n", state.env.instanceurl)
)
)
}
let formdata = UploadFormData::from_formdata(params, true);
if let Some(formdata) = formdata {
let mut breaks_conf = false;
if (!state.config.files.allow_custom_names) && formdata.filename.is_some() {
breaks_conf = true;
}
if (!state.config.files.allow_pass_protection) && formdata.password.is_some() {
breaks_conf = true;
}
if breaks_conf {
return Ok(
Box::new(
with_status(
"Attempt to set name or password when they are disabled".to_string(),
warp::http::StatusCode::BAD_REQUEST
)
)
);
}
if let Some(pass) = state.config.files.upload_pass {
let pass_valid: bool;
if let Some(upass) = formdata.instancepass {
pass_valid = upass == pass;
} else {
pass_valid = false
}
if ! pass_valid {
return Ok(
Box::new(
with_status(
"Invalid instance password".to_string(),
warp::http::StatusCode::BAD_REQUEST
)
)
);
}
}
let file = File::create(
formdata.file,
formdata.mime,
formdata.filename.clone(),
state.env.clone(),
formdata.delmode,
formdata.password,
ip
).await.map_err(|x| HttpReject::StringError(x.to_string()))?;
state.file_mgr.save(&file, formdata.lookup_kind).map_err(|x| HttpReject::StringError(x.to_string()))?;
return Ok(
Box::new(
format!(
concat!(
"File uploaded successfully.\n",
"It is available via this link:\n\n",
"{}/upload/{}\n"
),
state.env.instanceurl,
urlencoding::encode(
match formdata.filename {
Some(name) => name,
None => file.hash()
}.as_str()
)
)
)
);
} else {
Ok(
Box::new(
with_status(
"Invalid form".to_string(),
warp::http::StatusCode::BAD_REQUEST
)
)
)
}
}
pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::post()
.and(warp::path!("curlapi" / "upload"))
.and(warp::multipart::form())
.and(real_ip(vec![state.env.proxy_addr]))
.and(
warp::any()
.map(move || state.clone())
)
.and_then(upload)
}

View File

@ -17,9 +17,9 @@ use crate::files::{File, lookup::LookupKind, DeleteMode};
use super::{state::SharedState, pages::{UploadSuccessPage, ErrorPage}, rejection::HttpReject}; use super::{state::SharedState, pages::{UploadSuccessPage, ErrorPage}, rejection::HttpReject};
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
struct FormElement { pub struct FormElement {
data: Vec<u8>, pub data: Vec<u8>,
mime: String pub mime: String
} }
impl FormElement { impl FormElement {
@ -58,15 +58,15 @@ impl FormElement {
} }
} }
struct UploadFormData { pub struct UploadFormData {
filename: Option<String>, pub filename: Option<String>,
password: Option<String>, pub password: Option<String>,
instancepass: Option<String>, pub instancepass: Option<String>,
lookup_kind: LookupKind, pub lookup_kind: LookupKind,
delmode: DeleteMode, pub delmode: DeleteMode,
file: Vec<u8>, pub file: Vec<u8>,
mime: String, pub mime: String,
tos_consent: bool pub tos_consent: bool
} }
impl Default for UploadFormData { impl Default for UploadFormData {
@ -86,7 +86,7 @@ impl Default for UploadFormData {
impl UploadFormData { impl UploadFormData {
pub fn from_formdata(data: HashMap<String, FormElement>) -> Option<UploadFormData> { pub fn from_formdata(data: HashMap<String, FormElement>, use_defaults: bool) -> Option<UploadFormData> {
let mut out = Self::default(); let mut out = Self::default();
// Add a name // Add a name
@ -125,7 +125,9 @@ impl UploadFormData {
} }
}, },
None => { None => {
return None if ! use_defaults {
return None
}
} }
} }
@ -169,7 +171,7 @@ pub async fn upload(form: FormData, ip: Option<IpAddr>, state: SharedState) -> R
} }
let params: HashMap<String, FormElement> = FormElement::from_formdata(form).await.map_err(|x| HttpReject::WarpError(x))?; let params: HashMap<String, FormElement> = FormElement::from_formdata(form).await.map_err(|x| HttpReject::WarpError(x))?;
let formdata = UploadFormData::from_formdata(params.clone()); let formdata = UploadFormData::from_formdata(params.clone(), false);
if let Some(formdata) = formdata { if let Some(formdata) = formdata {

View File

@ -2,32 +2,28 @@
web - The part of filed that handles everything related to HTTP web - The part of filed that handles everything related to HTTP
*/ */
use std::env::current_dir;
use static_dir::static_dir; use static_dir::static_dir;
use warp::{Filter, reply::Reply, reject::Rejection}; use warp::{Filter, reply::Reply, reject::Rejection};
use crate::{env::Env, files::lookup::FileManager, config::types::Config}; use crate::{env::Env, files::lookup::FileManager, config::types::Config};
mod pages; mod pages;
mod forms; pub mod forms;
pub mod state; pub mod state;
mod rejection; mod rejection;
mod api; mod api;
mod uploaded; mod uploaded;
mod curlapi;
use state::SharedState; use state::SharedState;
pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let staticpath = current_dir().unwrap(); static_dir!("static")
let staticpath = staticpath.to_str().unwrap().to_string() + "/static"; .or(curlapi::get_routes(state.clone()))
.or(pages::get_routes(state.clone()))
pages::get_routes(state.clone())
.or(forms::get_routes(state.clone())) .or(forms::get_routes(state.clone()))
.or(api::get_routes(state.clone())) .or(api::get_routes(state.clone()))
.or(uploaded::get_uploaded(state)) .or(uploaded::get_uploaded(state))
.or(static_dir!("static"))
.or(warp::fs::dir(staticpath.to_string()))
} }
/* /*

View File

@ -88,6 +88,14 @@ pub struct ErrorPage {
pub link_text: Option<String> pub link_text: Option<String>
} }
#[derive(Template)]
#[template( path = "curlapi_help.html" )]
#[allow(dead_code)]
pub struct CurlHelpPage {
pub env: Env,
pub conf: Config
}
pub async fn uploaded(query: HashMap<String, String>, state: SharedState) -> Result<Html<String>, Rejection> { pub async fn uploaded(query: HashMap<String, String>, state: SharedState) -> Result<Html<String>, Rejection> {
if ! query.contains_key("file") { if ! query.contains_key("file") {

View File

@ -25,3 +25,6 @@
.alert.blue .alert-title { .alert.blue .alert-title {
background: #203050; background: #203050;
} }
.alert.green .alert-title {
background: #205030;
}

View File

@ -0,0 +1,23 @@
.code {
display: block;
padding: 1em;
border: 1px solid var(--header-sec-color);
border-radius: 12px;
overflow-x: auto;
}
.code .inner {
width: max-content;
display: block;
}
.code-inline {
display: inline;
background: #00000010;
border: 1px solid #c2c4c210;
border-radius: 4px;
padding: 2px 4px;
}
.code, .code-inline {
font-family: monospace;
line-height: 1.5em;
}

View File

@ -6,6 +6,11 @@
/** @type {HTMLElement} */ /** @type {HTMLElement} */
const root_drag_rop = document.getElementsByClassName('file-drag-n-drop')[0]; const root_drag_rop = document.getElementsByClassName('file-drag-n-drop')[0];
// make the root drag&drop element an ideal circle
root_drag_rop.style.width = root_drag_rop.offsetWidth + 'px';
root_drag_rop.style.height = root_drag_rop.offsetWidth + 'px';
/** @type {HTMLElement} */ /** @type {HTMLElement} */
const drag_rop = document.getElementsByClassName('file-drag-n-drop-inside')[0]; const drag_rop = document.getElementsByClassName('file-drag-n-drop-inside')[0];
const dr_rop_t = document.getElementsByClassName('file-drag-n-drop-inside-text')[0]; const dr_rop_t = document.getElementsByClassName('file-drag-n-drop-inside-text')[0];

View File

@ -1,7 +1,7 @@
.file-drag-n-drop { .file-drag-n-drop {
display: block; display: block;
cursor: pointer; cursor: pointer;
width: 440px; width: 100%;
height: 440px; height: 440px;
background: var(--header-sec-color); background: var(--header-sec-color);
border-radius: 16px; border-radius: 16px;

View File

@ -67,6 +67,13 @@
<td> <td>
<small>Made with Rust and &lt;3</small> <small>Made with Rust and &lt;3</small>
<small style="display:block">
Version
<a href="https://git.blek.codes/blek/bfile/commit/{{ env.version.commit }}" target="_blank">
{{ env!("CARGO_PKG_VERSION") }} ({{ env.version.branch -}}/{{- env.version.short_commit }})
</a>
</small>
<ul style='margin:10px 0'> <ul style='margin:10px 0'>
<li> <li>
<a href="https://git.blek.codes/blek/bfile"> <a href="https://git.blek.codes/blek/bfile">

View File

@ -0,0 +1,114 @@
{% extends "base.html" %}
{% block head %}
<link rel='stylesheet' href="/code.css" />
<link rel='stylesheet' href="/alert.css" />
<link rel='stylesheet' href="/js-only.css" />
<style>
.copy-btn { font-size: 70%; transform: translateY(-25%); display: inline-block }
</style>
{% endblock %}
{% block body %}
<div style="max-width:95vw;width:fit-content;margin:0 auto">
<h1 style="text-align:center">Curl API</h1>
<p>
blek! File has an API for uploading files via cURL.
To upload a file via cURL, follow these instructions:
</p>
<p>
To upload a file, POST it like this:
<a href="#" class="copy-btn" data-clipboard-text="curl -X POST {{env.instanceurl}}/curlapi/upload -F'file=@file.txt' -F'tos_consent=on'">
Copy!
</a>
</p>
<div class='code'>
<span class='inner'>
<span style='color:green'>curl</span>
<span style='color:orange'>-X POST</span>
<span style='color:darkcyan'>{{env.instanceurl}}/curlapi/upload</span>
<span style='color:orange'>-F'file=@file.txt'</span>
<span style='color:orange'>-F'tos_consent=on'</span>
</span>
</div>
<p>
To add a password, do it like this:
<a href="#" class="copy-btn" data-clipboard-text="curl -X POST {{env.instanceurl}}/curlapi/upload -F'file=@file.txt' -F'filename=uwu' -F'tos_consent=on' -F'named=on'">
Copy!
</a>
</p>
<div class='code'>
<span class='inner'>
<span style='color:green'>curl</span>
<span style='color:orange'>-X POST</span>
<span style='color:darkcyan'>{{env.instanceurl}}/curlapi/upload</span>
<span style='color:orange'>-F'file=@file.txt'</span>
<span style='color:orange'>-F'tos_consent=on'</span>
<span style='color:orange'>-F'named=on'</span>
</span>
</div>
<p>
Note that the
<span class='code-inline'>named=on</span>
switch is required.
Its needed because the curl API is basically a wrapper of
<a href="/">this</a>
HTML form.
</p>
<div class="alert green">
<h1 class="alert-title">
Important
</h1>
<p class="alert-text">
Read the
<a href="/tos">Terms of Service</a>
<b>before</b>
uploading a file.
<br/>
You agree to them by adding the
<span class="code-inline">tos_consent=on</span>
switch.
</p>
</div>
<div class='alert blue'>
<h1 class='alert-title'>Web UI</h1>
<div class='alert-text'>
<p>
Hey, it looks like you are viewing this page from a browser!<br/>
You can use the Web UI as well to upload a file!
</p>
<p style='margin:32px 0'>
<a href='/' role='button' class='btn'>
Go to the web UI
</a>
</p>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="https://unpkg.com/clipboard@2/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.copy-btn');
{#- -#}
( {#- -#}
() => { {#- -#}
let btns = document.getElementsByClassName('copy-btn') {#- -#}
for (const button of btns) { {#- -#}
button.onclick = () => { {#- -#}
let old = button.innerHTML; {#- -#}
button.innerHTML = 'Copied!'; {#- -#}
setTimeout(() => { button.innerHTML = old }, 500); {#- -#}
} {#- -#}
} {#- -#}
} {#- -#}
)() {#- -#}
</script>
<script src='/js-only.js'></script>
{% endblock %}

View File

@ -5,5 +5,5 @@ REDIS_PREFIX=bfile-
USERCONTENT_DIR=/opt/user_uploads USERCONTENT_DIR=/opt/user_uploads
CLEAN_DEL=30 minutes CLEAN_DEL=30min
CLEAN_ERRDEL=2 minutes CLEAN_ERRDEL=2min

View File

@ -10,7 +10,7 @@ RUN cargo b -r
RUN strip target/release/janitor && upx --best target/release/janitor RUN strip target/release/janitor && upx --best target/release/janitor
# --- deploy --- # --- deploy ---
FROM busybox:musl FROM alpine:3.17
RUN mkdir /config RUN mkdir /config
WORKDIR /config WORKDIR /config