Compare commits

..

4 Commits

Author SHA1 Message Date
sigoden
f103e15e15 chore(release): version v0.11.0 2022-06-03 11:19:57 +08:00
sigoden
9dda55b7c8 feat: listen 0.0.0.0 by default 2022-06-03 11:19:16 +08:00
sigoden
c3dd0f0ec5 feat: support gracefully shutdown server 2022-06-03 11:00:12 +08:00
sigoden
4167e5c07e chore(ci): publish to docker
* ci: publish to docker

* update release.yaml

* update Dockerfile
2022-06-03 10:36:06 +08:00
10 changed files with 160 additions and 83 deletions

13
.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
# Directories
/.git/
/.github/
/target/
/examples/
/docs/
/benches/
/tmp/
# Files
.gitignore
*.md
LICENSE*

View File

@@ -6,8 +6,10 @@ on:
- v[0-9]+.[0-9]+.[0-9]+* - v[0-9]+.[0-9]+.[0-9]+*
jobs: jobs:
all: release:
name: All name: Publish to Github Reelases
outputs:
rc: ${{ steps.check-tag.outputs.rc }}
strategy: strategy:
matrix: matrix:
@@ -124,3 +126,40 @@ jobs:
prerelease: ${{ steps.check-tag.outputs.rc == 'true' }} prerelease: ${{ steps.check-tag.outputs.rc == 'true' }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker:
name: Publish to Docker Hub
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: release
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: ${{ needs.release.outputs.rc == 'false' }}
tags: sigoden/duf:latest, sigoden/duf:${{ github.ref_name }}
publish-crate:
name: Publish to crates.io
if: ${{ needs.release.outputs.rc == 'false' }}
runs-on: ubuntu-latest
needs: release
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}
run: cargo publish

View File

@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.11.0] - 2022-06-03
### Features
- Support gracefully shutdown server
- Listen 0.0.0.0 by default
## [0.10.1] - 2022-06-02 ## [0.10.1] - 2022-06-02
### Bug Fixes ### Bug Fixes

12
Cargo.lock generated
View File

@@ -286,7 +286,7 @@ dependencies = [
[[package]] [[package]]
name = "duf" name = "duf"
version = "0.10.1" version = "0.11.0"
dependencies = [ dependencies = [
"async-walkdir", "async-walkdir",
"async_zip", "async_zip",
@@ -882,6 +882,15 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.6" version = "0.4.6"
@@ -965,6 +974,7 @@ dependencies = [
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi 0.3.9", "winapi 0.3.9",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "duf" name = "duf"
version = "0.10.1" version = "0.11.0"
edition = "2021" edition = "2021"
authors = ["sigoden <sigoden@gmail.com>"] authors = ["sigoden <sigoden@gmail.com>"]
description = "Duf is a fully functional file server." description = "Duf is a fully functional file server."
@@ -14,7 +14,7 @@ keywords = ["static", "file", "server", "http", "cli"]
[dependencies] [dependencies]
clap = { version = "3", default-features = false, features = ["std", "cargo"] } clap = { version = "3", default-features = false, features = ["std", "cargo"] }
chrono = "0.4" chrono = "0.4"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util"]} tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"]}
tokio-rustls = "0.23" tokio-rustls = "0.23"
tokio-stream = { version = "0.1", features = ["net"] } tokio-stream = { version = "0.1", features = ["net"] }
tokio-util = { version = "0.7", features = ["codec", "io-util"] } tokio-util = { version = "0.7", features = ["codec", "io-util"] }

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM rust:1.61 as builder
RUN rustup target add x86_64-unknown-linux-musl
RUN apt-get update && apt-get install --no-install-recommends -y musl-tools
WORKDIR /app
COPY . .
RUN cargo build --target x86_64-unknown-linux-musl --release
FROM alpine
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/duf /bin/
ENTRYPOINT ["/bin/duf"]

View File

@@ -28,6 +28,12 @@ Duf is a fully functional file server.
cargo install duf cargo install duf
``` ```
### With docker
```
docker run -v /tmp:/tmp -p 5000:5000 --rm -it docker.io/sigoden/duf /tmp
```
### Binaries on macOS, Linux, Windows ### Binaries on macOS, Linux, Windows
Download from [Github Releases](https://github.com/sigoden/duf/releases), unzip and add duf to your $PATH. Download from [Github Releases](https://github.com/sigoden/duf/releases), unzip and add duf to your $PATH.
@@ -50,7 +56,7 @@ OPTIONS:
--allow-delete Allow delete files/folders --allow-delete Allow delete files/folders
--allow-symlink Allow symlink to files/folders outside root directory --allow-symlink Allow symlink to files/folders outside root directory
--allow-upload Allow upload files/folders --allow-upload Allow upload files/folders
-b, --bind <address> Specify bind address [default: 127.0.0.1] -b, --bind <address> Specify bind address [default: 0.0.0.0]
--cors Enable CORS, sets `Access-Control-Allow-Origin: *` --cors Enable CORS, sets `Access-Control-Allow-Origin: *`
-h, --help Print help information -h, --help Print help information
-p, --port <port> Specify port to listen on [default: 5000] -p, --port <port> Specify port to listen on [default: 5000]
@@ -76,18 +82,10 @@ duf
duf folder_name duf folder_name
``` ```
Listen on all Interfaces and port 3000
```
duf -b 0.0.0.0 -p 3000
```
Allow all operations such as upload, delete Allow all operations such as upload, delete
```sh ```sh
duf --allow-all duf --allow-all
# or
duf -A
``` ```
Only allow upload operation Only allow upload operation
@@ -102,7 +100,7 @@ Serve a single page application (SPA)
duf --render-spa duf --render-spa
``` ```
Serve https Use https
``` ```
duf --tls-cert my.crt --tls-key my.key duf --tls-cert my.crt --tls-key my.key

View File

@@ -16,7 +16,7 @@ fn app() -> clap::Command<'static> {
Arg::new("address") Arg::new("address")
.short('b') .short('b')
.long("bind") .long("bind")
.default_value("127.0.0.1") .default_value("0.0.0.0")
.help("Specify bind address") .help("Specify bind address")
.value_name("address"), .value_name("address"),
) )
@@ -173,7 +173,7 @@ impl Args {
fn parse_path<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> { fn parse_path<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
let path = path.as_ref(); let path = path.as_ref();
if !path.exists() { if !path.exists() {
bail!("error: path \"{}\" doesn't exist", path.display()); return Err(format!("Path `{}` doesn't exist", path.display()).into());
} }
env::current_dir() env::current_dir()
@@ -181,27 +181,14 @@ impl Args {
p.push(path); // If path is absolute, it replaces the current path. p.push(path); // If path is absolute, it replaces the current path.
std::fs::canonicalize(p) std::fs::canonicalize(p)
}) })
.or_else(|err| { .map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
bail!(
"error: failed to access path \"{}\": {}",
path.display(),
err,
)
})
} }
/// Construct socket address from arguments. /// Construct socket address from arguments.
pub fn address(&self) -> BoxResult<SocketAddr> { pub fn address(&self) -> BoxResult<SocketAddr> {
format!("{}:{}", self.address, self.port) format!("{}:{}", self.address, self.port)
.parse() .parse()
.or_else(|err| { .map_err(|_| format!("Invalid bind address `{}:{}`", self.address, self.port).into())
bail!(
"error: invalid address {}:{} : {}",
self.address,
self.port,
err,
)
})
} }
} }

View File

@@ -1,9 +1,3 @@
macro_rules! bail {
($($tt:tt)*) => {
return Err(From::from(format!($($tt)*)))
}
}
mod args; mod args;
mod server; mod server;
@@ -23,6 +17,6 @@ async fn run() -> BoxResult<()> {
} }
fn handle_err<T>(err: Box<dyn std::error::Error>) -> T { fn handle_err<T>(err: Box<dyn std::error::Error>) -> T {
eprintln!("Server error: {}", err); eprintln!("error: {}", err);
std::process::exit(1); std::process::exit(1);
} }

View File

@@ -53,14 +53,21 @@ macro_rules! status {
} }
pub async fn serve(args: Args) -> BoxResult<()> { pub async fn serve(args: Args) -> BoxResult<()> {
match args.tls.as_ref() {
Some(_) => serve_https(args).await,
None => serve_http(args).await,
}
}
pub async fn serve_https(args: Args) -> BoxResult<()> {
let args = Arc::new(args); let args = Arc::new(args);
let socket_addr = args.address()?; let socket_addr = args.address()?;
let (certs, key) = args.tls.clone().unwrap();
let inner = Arc::new(InnerService::new(args.clone())); let inner = Arc::new(InnerService::new(args.clone()));
if let Some((certs, key)) = args.tls.as_ref() {
let config = ServerConfig::builder() let config = ServerConfig::builder()
.with_safe_defaults() .with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(certs.clone(), key.clone())?; .with_single_cert(certs, key)?;
let tls_acceptor = TlsAcceptor::from(Arc::new(config)); let tls_acceptor = TlsAcceptor::from(Arc::new(config));
let arc_acceptor = Arc::new(tls_acceptor); let arc_acceptor = Arc::new(tls_acceptor);
let listener = TcpListener::bind(&socket_addr).await?; let listener = TcpListener::bind(&socket_addr).await?;
@@ -84,8 +91,15 @@ pub async fn serve(args: Args) -> BoxResult<()> {
} }
})); }));
print_listening(args.address.as_str(), args.port, true); print_listening(args.address.as_str(), args.port, true);
server.await?; let graceful = server.with_graceful_shutdown(shutdown_signal());
} else { graceful.await?;
Ok(())
}
pub async fn serve_http(args: Args) -> BoxResult<()> {
let args = Arc::new(args);
let socket_addr = args.address()?;
let inner = Arc::new(InnerService::new(args.clone()));
let server = hyper::Server::try_bind(&socket_addr)?.serve(make_service_fn(move |_| { let server = hyper::Server::try_bind(&socket_addr)?.serve(make_service_fn(move |_| {
let inner = inner.clone(); let inner = inner.clone();
async move { async move {
@@ -96,9 +110,8 @@ pub async fn serve(args: Args) -> BoxResult<()> {
} }
})); }));
print_listening(args.address.as_str(), args.port, false); print_listening(args.address.as_str(), args.port, false);
server.await?; let graceful = server.with_graceful_shutdown(shutdown_signal());
} graceful.await?;
Ok(()) Ok(())
} }
@@ -751,3 +764,9 @@ fn retrive_listening_addrs(address: &str) -> Vec<String> {
} }
vec![address.to_owned()] vec![address.to_owned()]
} }
async fn shutdown_signal() {
tokio::signal::ctrl_c()
.await
.expect("Failed to install CTRL+C signal handler")
}