v0.24.9
This commit is contained in:
1 parent
1297ea2920
commit
dc0fcabf51
49 files changed
+2632
-252
No files matched your search
+97
-8
@@ -35,12 +35,13 @@ use tokio::{
|
||||
task::JoinSet,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
name = "rust-proxy",
|
||||
version,
|
||||
about = "RustProxy - independent Rust reverse proxy 0.24.8"
|
||||
about = "RustProxy - independent Rust reverse proxy 0.24.9"
|
||||
)]
|
||||
struct Cli {
|
||||
#[arg(
|
||||
@@ -94,6 +95,11 @@ enum Command {
|
||||
#[command(subcommand)]
|
||||
action: ConfigCommand,
|
||||
},
|
||||
/// Create or restore a password-encrypted full application backup.
|
||||
Backup {
|
||||
#[command(subcommand)]
|
||||
action: BackupCommand,
|
||||
},
|
||||
}
|
||||
#[derive(Subcommand)]
|
||||
enum AdminCommand {
|
||||
@@ -127,6 +133,20 @@ enum ConfigCommand {
|
||||
/// Validate JSON, ACLs, headers and backend policy without changing the database.
|
||||
Validate { file: PathBuf },
|
||||
}
|
||||
#[derive(Subcommand)]
|
||||
enum BackupCommand {
|
||||
/// Create an encrypted full backup. Stop the RustProxy service first.
|
||||
Create {
|
||||
/// Destination .rpb file. Existing files are never overwritten.
|
||||
#[arg(long)]
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
/// Restore an encrypted full backup atomically. Stop the RustProxy service first.
|
||||
Restore {
|
||||
archive: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum TlsCommand {
|
||||
/// Generate a self-signed development certificate. Existing files are never overwritten.
|
||||
@@ -243,13 +263,18 @@ fn resolve_installed_data_dir(configured: PathBuf, explicitly_configured: bool)
|
||||
}
|
||||
|
||||
async fn application() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "rust_proxy=info,warn".into()),
|
||||
let error_log = rust_proxy::logging::ErrorLogController::start();
|
||||
rust_proxy::logging::install_error_log_controller(error_log.clone())?;
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "rust_proxy=info,warn".into());
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_target(false)
|
||||
.with_writer(rust_proxy::logging::RedactingStderr),
|
||||
)
|
||||
.with_target(false)
|
||||
.with_writer(std::io::stderr)
|
||||
.with(rust_proxy::logging::ErrorLogLayer::new(error_log))
|
||||
.init();
|
||||
let matches = Cli::command().get_matches();
|
||||
let explicit = |matches: &clap::ArgMatches, id: &str| {
|
||||
@@ -309,6 +334,13 @@ async fn application() -> Result<()> {
|
||||
)
|
||||
.await
|
||||
}
|
||||
Command::Backup { action } => {
|
||||
backup_command(
|
||||
resolve_installed_data_dir(cli.data_dir, data_dir_explicit),
|
||||
action,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Command::Serve(options) => {
|
||||
let update_check = rust_proxy::update_check::Config::embedded()?.with_overrides(
|
||||
cli.update_repository_url,
|
||||
@@ -328,6 +360,57 @@ async fn application() -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn backup_command(data_dir: PathBuf, action: BackupCommand) -> Result<()> {
|
||||
match action {
|
||||
BackupCommand::Create { output } => {
|
||||
let output = output.unwrap_or_else(|| {
|
||||
PathBuf::from(format!("rustproxy-backup-{}.rpb", rust_proxy::auth::now()))
|
||||
});
|
||||
let mut generated = false;
|
||||
let password = if io::stdin().is_terminal() {
|
||||
let value = Zeroizing::new(
|
||||
rpassword::prompt_password("Backup password (leave empty to generate): ")?
|
||||
);
|
||||
if value.is_empty() {
|
||||
generated = true;
|
||||
Zeroizing::new(rust_proxy::backup::generated_password())
|
||||
} else {
|
||||
let confirmation = Zeroizing::new(rpassword::prompt_password("Confirm backup password: ")?);
|
||||
ensure!(value.as_str() == confirmation.as_str(), "backup passwords do not match");
|
||||
value
|
||||
}
|
||||
} else {
|
||||
generated = true;
|
||||
Zeroizing::new(rust_proxy::backup::generated_password())
|
||||
};
|
||||
let summary = rust_proxy::backup::create(&data_dir, &output, password.as_str()).await?;
|
||||
println!(
|
||||
"Encrypted backup created: {} ({} files, configuration revision {})",
|
||||
summary.path.display(), summary.files, summary.configuration_revision
|
||||
);
|
||||
if generated {
|
||||
println!("Generated backup password (shown once): {}", password.as_str());
|
||||
println!("Store this password securely; restore is impossible without it.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
BackupCommand::Restore { archive } => {
|
||||
ensure!(
|
||||
io::stdin().is_terminal(),
|
||||
"backup restore requires an interactive TTY for the password"
|
||||
);
|
||||
let password = Zeroizing::new(rpassword::prompt_password("Backup password: ")?);
|
||||
ensure!(!password.is_empty(), "backup password cannot be empty");
|
||||
let summary = rust_proxy::backup::restore(&archive, &data_dir, password.as_str()).await?;
|
||||
println!(
|
||||
"Backup restored to {} ({} files, configuration revision {})",
|
||||
data_dir.display(), summary.files, summary.configuration_revision
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_admin_binding(admin_addr: SocketAddr, tls: bool, allow_remote: bool) -> Result<bool> {
|
||||
let remote_admin = !admin_addr.ip().is_loopback();
|
||||
ensure!(
|
||||
@@ -445,6 +528,11 @@ async fn run(
|
||||
let directory = data_dir.join("certs");
|
||||
let initialize = directory.clone();
|
||||
tokio::task::spawn_blocking(move || rust_proxy::tls::initialize_default(&initialize)).await??;
|
||||
rust_proxy::secret_store::initialize(&data_dir)?;
|
||||
let migrated = rust_proxy::secret_store::migrate_acme_credentials(&store, &data_dir, &directory).await?;
|
||||
if migrated > 0 {
|
||||
tracing::info!(migrated, "legacy ACME credentials migrated to encrypted Secret Store");
|
||||
}
|
||||
let cert_dir = Some(directory.clone());
|
||||
let policy = ValidationPolicy {
|
||||
proxy_addr: options.proxy_listen,
|
||||
@@ -459,6 +547,7 @@ async fn run(
|
||||
})
|
||||
.await?
|
||||
.context("stored configuration is invalid; no automatic reset was performed")?;
|
||||
rust_proxy::logging::configure_error_log(snapshot.envelope.config.error_log.clone());
|
||||
let admin_tls_config = {
|
||||
let tls = snapshot.envelope.config.tls.clone();
|
||||
let administration = snapshot.envelope.config.administration.clone();
|
||||
@@ -1426,7 +1515,7 @@ async fn live_logs_command(dir: PathBuf, addr: SocketAddr) -> Result<()> {
|
||||
}
|
||||
});
|
||||
let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
|
||||
println!("RustProxy live logs 0.24.8 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
|
||||
println!("RustProxy live logs 0.24.9 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
|
||||
if interactive { print!("> "); io::stdout().flush()?; }
|
||||
let mut filters = LiveLogFilters::default();
|
||||
let mut history = std::collections::BTreeMap::<u64, serde_json::Value>::new();
|
||||
|
||||
Reference in new issue
Block a user