mirror of
https://github.com/RGBCube/Site
synced 2025-08-01 13:37:49 +00:00
Add HTTP redirect
This commit is contained in:
parent
019e1c7091
commit
dc306f1ed7
2 changed files with 106 additions and 14 deletions
44
flake.nix
44
flake.nix
|
@ -103,12 +103,39 @@
|
||||||
services.site = {
|
services.site = {
|
||||||
enable = mkEnableOption (mdDoc "site service");
|
enable = mkEnableOption (mdDoc "site service");
|
||||||
|
|
||||||
port = mkOption {
|
certificate = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
example = "/path/to/cert.pem";
|
||||||
|
description = mdDoc ''
|
||||||
|
The path to the SSL certificate the site will use.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
key = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
example = "/path/to/key.pem";
|
||||||
|
description = mdDoc ''
|
||||||
|
The path to the SSL key the site will use.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
httpPort = mkOption {
|
||||||
type = types.port;
|
type = types.port;
|
||||||
default = 8080;
|
default = 8080;
|
||||||
example = 80;
|
example = 80;
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
Specifies on which port the site service listens for connections.
|
Specifies on which port the site service listens for HTTP connections.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
httpsPort = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8443;
|
||||||
|
example = 80;
|
||||||
|
description = mdDoc ''
|
||||||
|
Specifies on which port the site service listens for HTTPS connections.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -138,11 +165,20 @@
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
serviceConfig = let
|
serviceConfig = let
|
||||||
needsPrivilidges = cfg.port < 1024;
|
arguments = [
|
||||||
|
"--http-port" cfg.httpPort
|
||||||
|
"--https-port" cfg.httpsPort
|
||||||
|
"--log-level" cfg.logLevel
|
||||||
|
] ++ (optionals (cfg.certificate != null) [
|
||||||
|
"--certificate" cfg.certificate
|
||||||
|
]) ++ (optionals (cfg.key != null) [
|
||||||
|
"--key" cfg.key
|
||||||
|
]);
|
||||||
|
needsPrivilidges = cfg.httpPort < 1024 || cfg.httpsPort < 1024;
|
||||||
capabilities = [ "" ] ++ optionals needsPrivilidges [ "CAP_NET_BIND_SERVICE" ];
|
capabilities = [ "" ] ++ optionals needsPrivilidges [ "CAP_NET_BIND_SERVICE" ];
|
||||||
rootDirectory = "/run/site";
|
rootDirectory = "/run/site";
|
||||||
in {
|
in {
|
||||||
ExecStart = "${self.packages.${pkgs.system}.site}/bin/site --port ${cfg.port} --log-level ${cfg.logLevel}";
|
ExecStart = "${self.packages.${pkgs.system}.site}/bin/site " + (concatStringsSep " " arguments);
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
DynamicUser = true;
|
DynamicUser = true;
|
||||||
RootDirectory = rootDirectory;
|
RootDirectory = rootDirectory;
|
||||||
|
|
76
src/main.rs
76
src/main.rs
|
@ -1,4 +1,4 @@
|
||||||
#![feature(lazy_cell, let_chains)]
|
#![feature(lazy_cell)]
|
||||||
|
|
||||||
mod asset;
|
mod asset;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
@ -13,17 +13,31 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use axum::Router;
|
use axum::{
|
||||||
|
extract::Host,
|
||||||
|
handler::HandlerWithoutStateExt,
|
||||||
|
http::{
|
||||||
|
uri::Scheme,
|
||||||
|
StatusCode,
|
||||||
|
Uri,
|
||||||
|
},
|
||||||
|
response::Redirect,
|
||||||
|
BoxError,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use axum_server::tls_rustls::RustlsConfig;
|
use axum_server::tls_rustls::RustlsConfig;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser, Clone)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// The port to listen for connections on
|
/// The HTTP port to listen for connections on
|
||||||
#[arg(long, default_value = "8080")]
|
#[arg(long, default_value = "8080")]
|
||||||
port: u16,
|
http_port: u16,
|
||||||
|
/// The HTTPS port to listen for connections on
|
||||||
|
#[arg(long, default_value = "8443")]
|
||||||
|
https_port: u16,
|
||||||
/// The log level to log stuff with
|
/// The log level to log stuff with
|
||||||
#[arg(long, default_value = "info")]
|
#[arg(long, default_value = "info")]
|
||||||
log_level: log::LevelFilter,
|
log_level: log::LevelFilter,
|
||||||
|
@ -36,6 +50,46 @@ struct Cli {
|
||||||
key: Option<PathBuf>,
|
key: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn redirect_http(args: Cli) {
|
||||||
|
let http_port = args.http_port.to_string();
|
||||||
|
let https_port = args.https_port.to_string();
|
||||||
|
|
||||||
|
let make_https = move |host: String, uri: Uri| -> Result<Uri, BoxError> {
|
||||||
|
let mut parts = uri.into_parts();
|
||||||
|
|
||||||
|
parts.scheme = Some(Scheme::HTTPS);
|
||||||
|
|
||||||
|
if parts.path_and_query.is_none() {
|
||||||
|
parts.path_and_query = Some("/".parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let https_host = host.replace(&http_port, &https_port);
|
||||||
|
parts.authority = Some(https_host.parse()?);
|
||||||
|
|
||||||
|
Ok(Uri::from_parts(parts)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
let redirect = move |Host(host): Host, uri: Uri| {
|
||||||
|
async move {
|
||||||
|
match make_https(host, uri) {
|
||||||
|
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("Failed to convert URI to HTTPS: {}", error);
|
||||||
|
Err(StatusCode::BAD_REQUEST)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let address = SocketAddr::from(([0, 0, 0, 0], args.http_port));
|
||||||
|
|
||||||
|
axum_server::bind(address)
|
||||||
|
.serve(redirect.into_make_service())
|
||||||
|
.await
|
||||||
|
.with_context(|| "Failed to run redirect server")
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
|
@ -52,20 +106,22 @@ async fn main() -> anyhow::Result<()> {
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
.into_make_service();
|
.into_make_service();
|
||||||
|
|
||||||
let address = SocketAddr::from(([0, 0, 0, 0], args.port));
|
if let (Some(certificate_path), Some(key_path)) = (&args.certificate, &args.key) {
|
||||||
|
tokio::spawn(redirect_http(args.clone()));
|
||||||
|
|
||||||
if let Some(certificate_path) = args.certificate
|
|
||||||
&& let Some(key_path) = args.key
|
|
||||||
{
|
|
||||||
let config = RustlsConfig::from_pem_file(certificate_path, key_path)
|
let config = RustlsConfig::from_pem_file(certificate_path, key_path)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to create TLS configuration from PEM files")?;
|
.with_context(|| "Failed to create TLS configuration from PEM files")?;
|
||||||
|
|
||||||
|
let address = SocketAddr::from(([0, 0, 0, 0], args.https_port));
|
||||||
|
|
||||||
axum_server::bind_rustls(address, config).serve(app).await
|
axum_server::bind_rustls(address, config).serve(app).await
|
||||||
} else {
|
} else {
|
||||||
|
let address = SocketAddr::from(([0, 0, 0, 0], args.http_port));
|
||||||
|
|
||||||
axum_server::bind(address).serve(app).await
|
axum_server::bind(address).serve(app).await
|
||||||
}
|
}
|
||||||
.with_context(|| "Failed to run server")?;
|
.with_context(|| "Failed to run main server")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue