mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
Merge branch 'rebase/integration/refactors' of github.com:Start9Labs/start-os into rebase/feat/domains
This commit is contained in:
2440
backend/Cargo.lock
generated
2440
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
description = "The core of StartOS"
|
||||
documentation = "https://docs.rs/embassy-os"
|
||||
documentation = "https://docs.rs/start-os"
|
||||
edition = "2021"
|
||||
keywords = [
|
||||
"self-hosted",
|
||||
@@ -11,40 +11,28 @@ keywords = [
|
||||
"full-node",
|
||||
"lightning",
|
||||
]
|
||||
name = "embassy-os"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.3.4-rev.3"
|
||||
version = "0.3.4-rev.4"
|
||||
|
||||
[lib]
|
||||
name = "embassy"
|
||||
name = "startos"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassyd"
|
||||
path = "src/bin/embassyd.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-init"
|
||||
path = "src/bin/embassy-init.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-sdk"
|
||||
path = "src/bin/embassy-sdk.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-cli"
|
||||
path = "src/bin/embassy-cli.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "avahi-alias"
|
||||
path = "src/bin/avahi-alias.rs"
|
||||
name = "startbox"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
avahi = ["avahi-sys"]
|
||||
default = ["avahi", "js_engine"]
|
||||
default = ["avahi-alias", "cli", "sdk", "daemon", "js_engine"]
|
||||
dev = []
|
||||
unstable = ["patch-db/unstable"]
|
||||
avahi-alias = ["avahi"]
|
||||
cli = []
|
||||
sdk = []
|
||||
daemon = []
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", features = ["ctr"] }
|
||||
@@ -95,11 +83,14 @@ id-pool = { version = "0.2.2", features = [
|
||||
"serde",
|
||||
], default-features = false }
|
||||
imbl = "2.0.0"
|
||||
include_dir = "0.7.3"
|
||||
indexmap = { version = "1.9.1", features = ["serde"] }
|
||||
ipnet = { version = "2.7.1", features = ["serde"] }
|
||||
iprange = { version = "0.6.7", features = ["serde"] }
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.10.3"
|
||||
jaq-core = "0.10.0"
|
||||
jaq-std = "0.10.0"
|
||||
josekit = "0.8.1"
|
||||
js_engine = { path = '../libs/js_engine', optional = true }
|
||||
jsonpath_lib = "0.3.0"
|
||||
@@ -108,6 +99,7 @@ libc = "0.2.126"
|
||||
log = "0.4.17"
|
||||
mbrman = "0.5.0"
|
||||
models = { version = "*", path = "../libs/models" }
|
||||
new_mime_guess = "4"
|
||||
nix = "0.25.0"
|
||||
nom = "7.1.1"
|
||||
num = "0.4.0"
|
||||
|
||||
@@ -12,18 +12,17 @@
|
||||
|
||||
## Structure
|
||||
|
||||
The StartOS backend is broken up into 4 different binaries:
|
||||
The StartOS backend is packed into a single binary `startbox` that is symlinked under
|
||||
several different names for different behaviour:
|
||||
|
||||
- embassyd: This is the main workhorse of StartOS - any new functionality you
|
||||
- startd: This is the main workhorse of StartOS - any new functionality you
|
||||
want will likely go here
|
||||
- embassy-init: This is the component responsible for allowing you to set up
|
||||
your device, and handles system initialization on startup
|
||||
- embassy-cli: This is a CLI tool that will allow you to issue commands to
|
||||
embassyd and control it similarly to the UI
|
||||
- embassy-sdk: This is a CLI tool that aids in building and packaging services
|
||||
- start-cli: This is a CLI tool that will allow you to issue commands to
|
||||
startd and control it similarly to the UI
|
||||
- start-sdk: This is a CLI tool that aids in building and packaging services
|
||||
you wish to deploy to StartOS
|
||||
|
||||
Finally there is a library `embassy` that supports all four of these tools.
|
||||
Finally there is a library `startos` that supports all of these tools.
|
||||
|
||||
See [here](/backend/Cargo.toml) for details.
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-dev.sh" ]; then
|
||||
>&2 echo "Must be run from backend directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
alias 'rust-arm64-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-arm-cross:aarch64'
|
||||
|
||||
cd ..
|
||||
rust-arm64-builder sh -c "(cd backend && cargo build --locked)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-portable-dev.sh" ]; then
|
||||
>&2 echo "Must be run from backend directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME"/.cargo/registry:/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-musl-cross:x86_64-musl'
|
||||
|
||||
cd ..
|
||||
rust-musl-builder sh -c "(cd backend && cargo +beta build --target=x86_64-unknown-linux-musl --no-default-features --locked)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
@@ -72,5 +72,3 @@ sudo chown -R $USER ../libs/target
|
||||
if [ -n "$fail" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
[Unit]
|
||||
Description=Embassy Init
|
||||
After=network-online.target
|
||||
Requires=network-online.target
|
||||
Wants=avahi-daemon.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=RUST_LOG=embassy_init=debug,embassy=debug,js_engine=debug,patch_db=warn
|
||||
ExecStart=/usr/bin/embassy-init
|
||||
RemainAfterExit=true
|
||||
StandardOutput=append:/var/log/embassy-init.log
|
||||
|
||||
[Install]
|
||||
WantedBy=embassyd.service
|
||||
@@ -1,17 +0,0 @@
|
||||
[Unit]
|
||||
Description=Embassy Daemon
|
||||
After=embassy-init.service
|
||||
Requires=embassy-init.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=RUST_LOG=embassyd=debug,embassy=debug,js_engine=debug,patch_db=warn
|
||||
ExecStart=/usr/bin/embassyd
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ManagedOOMPreference=avoid
|
||||
CPUAccounting=true
|
||||
CPUWeight=1000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -9,7 +9,10 @@ if [ "$0" != "./install-sdk.sh" ]; then
|
||||
fi
|
||||
|
||||
if [ -z "$OS_ARCH" ]; then
|
||||
OS_ARCH=$(uname -m)
|
||||
export OS_ARCH=$(uname -m)
|
||||
fi
|
||||
|
||||
cargo install --bin=embassy-sdk --bin=embassy-cli --path=. --no-default-features --features=js_engine --locked
|
||||
cargo install --path=. --no-default-features --features=js_engine,sdk,cli --locked
|
||||
startbox_loc=$(which startbox)
|
||||
ln -sf $startbox_loc $(dirname $startbox_loc)/start-cli
|
||||
ln -sf $startbox_loc $(dirname $startbox_loc)/start-sdk
|
||||
@@ -334,7 +334,7 @@ async fn perform_backup<Db: DbHandle>(
|
||||
}
|
||||
let luks_folder = Path::new("/media/embassy/config/luks");
|
||||
if tokio::fs::metadata(&luks_folder).await.is_ok() {
|
||||
dir_copy(&luks_folder, &luks_folder_bak).await?;
|
||||
dir_copy(&luks_folder, &luks_folder_bak, None).await?;
|
||||
}
|
||||
|
||||
let timestamp = Some(Utc::now());
|
||||
|
||||
@@ -109,7 +109,7 @@ async fn approximate_progress(
|
||||
if tokio::fs::metadata(&dir).await.is_err() {
|
||||
*size = 0;
|
||||
} else {
|
||||
*size = dir_size(&dir).await?;
|
||||
*size = dir_size(&dir, None).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -285,7 +285,7 @@ async fn restore_packages(
|
||||
progress_info.package_installs.insert(id.clone(), progress);
|
||||
progress_info
|
||||
.src_volume_size
|
||||
.insert(id.clone(), dir_size(backup_dir(&id)).await?);
|
||||
.insert(id.clone(), dir_size(backup_dir(&id), None).await?);
|
||||
progress_info.target_volume_size.insert(id.clone(), 0);
|
||||
let package_id = id.clone();
|
||||
tasks.push(
|
||||
@@ -444,7 +444,6 @@ async fn restore_package<'a>(
|
||||
progress.clone(),
|
||||
async move {
|
||||
download_install_s9pk(&ctx, &manifest, None, progress, file, None).await?;
|
||||
|
||||
guard.unmount().await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -14,7 +14,7 @@ fn log_str_error(action: &str, e: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
let aliases: Vec<_> = std::env::args().skip(1).collect();
|
||||
unsafe {
|
||||
let simple_poll = avahi_sys::avahi_simple_poll_new();
|
||||
9
backend/src/bins/deprecated.rs
Normal file
9
backend/src/bins/deprecated.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub fn renamed(old: &str, new: &str) -> ! {
|
||||
eprintln!("{old} has been renamed to {new}");
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
pub fn removed(name: &str) -> ! {
|
||||
eprintln!("{name} has been removed");
|
||||
std::process::exit(1)
|
||||
}
|
||||
55
backend/src/bins/mod.rs
Normal file
55
backend/src/bins/mod.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "avahi-alias")]
|
||||
pub mod avahi_alias;
|
||||
pub mod deprecated;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod start_cli;
|
||||
#[cfg(feature = "daemon")]
|
||||
pub mod start_init;
|
||||
#[cfg(feature = "sdk")]
|
||||
pub mod start_sdk;
|
||||
#[cfg(feature = "daemon")]
|
||||
pub mod startd;
|
||||
|
||||
fn select_executable(name: &str) -> Option<fn()> {
|
||||
match name {
|
||||
#[cfg(feature = "avahi-alias")]
|
||||
"avahi-alias" => Some(avahi_alias::main),
|
||||
#[cfg(feature = "cli")]
|
||||
"start-cli" => Some(start_cli::main),
|
||||
#[cfg(feature = "sdk")]
|
||||
"start-sdk" => Some(start_sdk::main),
|
||||
#[cfg(feature = "daemon")]
|
||||
"startd" => Some(startd::main),
|
||||
"embassy-cli" => Some(|| deprecated::renamed("embassy-cli", "start-cli")),
|
||||
"embassy-sdk" => Some(|| deprecated::renamed("embassy-sdk", "start-sdk")),
|
||||
"embassyd" => Some(|| deprecated::renamed("embassyd", "startd")),
|
||||
"embassy-init" => Some(|| deprecated::removed("embassy-init")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn startbox() {
|
||||
let args = std::env::args().take(2).collect::<Vec<_>>();
|
||||
if let Some(x) = args
|
||||
.get(0)
|
||||
.and_then(|s| Path::new(&*s).file_name())
|
||||
.and_then(|s| s.to_str())
|
||||
.and_then(|s| select_executable(&s))
|
||||
{
|
||||
x()
|
||||
} else if let Some(x) = args.get(1).and_then(|s| select_executable(&s)) {
|
||||
x()
|
||||
} else {
|
||||
eprintln!(
|
||||
"unknown executable: {}",
|
||||
args.get(0)
|
||||
.filter(|x| &**x != "startbox")
|
||||
.or_else(|| args.get(1))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
use clap::Arg;
|
||||
use embassy::context::CliContext;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::version::{Current, VersionT};
|
||||
use embassy::Error;
|
||||
use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: embassy::main_api,
|
||||
command: crate::main_api,
|
||||
app: app => app
|
||||
.name("Embassy CLI")
|
||||
.name("StartOS CLI")
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
@@ -48,7 +49,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
match inner_main() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
@@ -3,21 +3,22 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use embassy::context::rpc::RpcContextConfig;
|
||||
use embassy::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||
use embassy::disk::fsck::RepairStrategy;
|
||||
use embassy::disk::main::DEFAULT_PASSWORD;
|
||||
use embassy::disk::REPAIR_DISK_PATH;
|
||||
use embassy::init::STANDBY_MODE_PATH;
|
||||
use embassy::net::web_server::WebServer;
|
||||
use embassy::shutdown::Shutdown;
|
||||
use embassy::sound::CHIME;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::util::Invoke;
|
||||
use embassy::{Error, ErrorKind, ResultExt, OS_ARCH};
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||
use crate::disk::fsck::RepairStrategy;
|
||||
use crate::disk::main::DEFAULT_PASSWORD;
|
||||
use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::init::STANDBY_MODE_PATH;
|
||||
use crate::net::web_server::WebServer;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::sound::CHIME;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
|
||||
Command::new("ln")
|
||||
@@ -78,7 +79,7 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
|
||||
server.shutdown().await;
|
||||
|
||||
Command::new("reboot")
|
||||
.invoke(embassy::ErrorKind::Unknown)
|
||||
.invoke(crate::ErrorKind::Unknown)
|
||||
.await?;
|
||||
} else if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
.await
|
||||
@@ -116,7 +117,7 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
|
||||
let guid_string = tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?;
|
||||
let guid = guid_string.trim();
|
||||
let requires_reboot = embassy::disk::main::import(
|
||||
let requires_reboot = crate::disk::main::import(
|
||||
guid,
|
||||
cfg.datadir(),
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
@@ -124,22 +125,26 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
|
||||
} else {
|
||||
RepairStrategy::Preen
|
||||
},
|
||||
DEFAULT_PASSWORD,
|
||||
if guid.ends_with("_UNENC") {
|
||||
None
|
||||
} else {
|
||||
Some(DEFAULT_PASSWORD)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(REPAIR_DISK_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (embassy::ErrorKind::Filesystem, REPAIR_DISK_PATH))?;
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, REPAIR_DISK_PATH))?;
|
||||
}
|
||||
if requires_reboot.0 {
|
||||
embassy::disk::main::export(guid, cfg.datadir()).await?;
|
||||
crate::disk::main::export(guid, cfg.datadir()).await?;
|
||||
Command::new("reboot")
|
||||
.invoke(embassy::ErrorKind::Unknown)
|
||||
.invoke(crate::ErrorKind::Unknown)
|
||||
.await?;
|
||||
}
|
||||
tracing::info!("Loaded Disk");
|
||||
embassy::init::init(&cfg).await?;
|
||||
crate::init::init(&cfg).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -168,11 +173,11 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
if OS_ARCH == "raspberrypi" && tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(STANDBY_MODE_PATH).await?;
|
||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
||||
embassy::sound::SHUTDOWN.play().await?;
|
||||
crate::sound::SHUTDOWN.play().await?;
|
||||
futures::future::pending::<()>().await;
|
||||
}
|
||||
|
||||
embassy::sound::BEP.play().await?;
|
||||
crate::sound::BEP.play().await?;
|
||||
|
||||
run_script_if_exists("/media/embassy/config/preinit.sh").await;
|
||||
|
||||
@@ -180,7 +185,7 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
async move {
|
||||
tracing::error!("{}", e.source);
|
||||
tracing::debug!("{}", e.source);
|
||||
embassy::sound::BEETHOVEN.play().await?;
|
||||
crate::sound::BEETHOVEN.play().await?;
|
||||
|
||||
let ctx = DiagnosticContext::init(
|
||||
cfg_path,
|
||||
@@ -223,8 +228,8 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
res
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = clap::App::new("embassy-init")
|
||||
pub fn main() {
|
||||
let matches = clap::App::new("start-init")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short('c')
|
||||
@@ -233,8 +238,6 @@ fn main() {
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
EmbassyLogger::init();
|
||||
|
||||
let cfg_path = matches.value_of("config").map(|p| Path::new(p).to_owned());
|
||||
let res = {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
@@ -1,20 +1,21 @@
|
||||
use embassy::context::SdkContext;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::version::{Current, VersionT};
|
||||
use embassy::Error;
|
||||
use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::SdkContext;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: embassy::portable_api,
|
||||
command: crate::portable_api,
|
||||
app: app => app
|
||||
.name("Embassy SDK")
|
||||
.name("StartOS SDK")
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
@@ -47,7 +48,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
match inner_main() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
@@ -3,16 +3,17 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use embassy::context::{DiagnosticContext, RpcContext};
|
||||
use embassy::net::web_server::WebServer;
|
||||
use embassy::shutdown::Shutdown;
|
||||
use embassy::system::launch_metrics_task;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::{Error, ErrorKind, ResultExt};
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::{DiagnosticContext, RpcContext};
|
||||
use crate::net::web_server::WebServer;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::launch_metrics_task;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||
let (rpc_ctx, server, shutdown) = {
|
||||
@@ -26,7 +27,7 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
embassy::hostname::sync_hostname(&rpc_ctx.account.read().await.hostname).await?;
|
||||
crate::hostname::sync_hostname(&rpc_ctx.account.read().await.hostname).await?;
|
||||
let server = WebServer::main(
|
||||
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
|
||||
rpc_ctx.clone(),
|
||||
@@ -71,7 +72,7 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
.await
|
||||
});
|
||||
|
||||
embassy::sound::CHIME.play().await?;
|
||||
crate::sound::CHIME.play().await?;
|
||||
|
||||
metrics_task
|
||||
.map_err(|e| {
|
||||
@@ -100,8 +101,15 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
Ok(shutdown)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = clap::App::new("embassyd")
|
||||
pub fn main() {
|
||||
EmbassyLogger::init();
|
||||
|
||||
if !Path::new("/run/embassy/initialized").exists() {
|
||||
super::start_init::main();
|
||||
std::fs::write("/run/embassy/initialized", "").unwrap();
|
||||
}
|
||||
|
||||
let matches = clap::App::new("startd")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short('c')
|
||||
@@ -110,8 +118,6 @@ fn main() {
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
EmbassyLogger::init();
|
||||
|
||||
let cfg_path = matches.value_of("config").map(|p| Path::new(p).to_owned());
|
||||
|
||||
let res = {
|
||||
@@ -126,7 +132,7 @@ fn main() {
|
||||
async {
|
||||
tracing::error!("{}", e.source);
|
||||
tracing::debug!("{:?}", e.source);
|
||||
embassy::sound::BEETHOVEN.play().await?;
|
||||
crate::sound::BEETHOVEN.play().await?;
|
||||
let ctx = DiagnosticContext::init(
|
||||
cfg_path,
|
||||
if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
@@ -269,6 +269,45 @@ impl RpcContext {
|
||||
pub async fn cleanup(&self) -> Result<(), Error> {
|
||||
let mut db = self.db.handle();
|
||||
let receipts = RpcCleanReceipts::new(&mut db).await?;
|
||||
let packages = receipts.packages.get(&mut db).await?.0;
|
||||
let mut current_dependents = packages
|
||||
.keys()
|
||||
.map(|k| (k.clone(), BTreeMap::new()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for (package_id, package) in packages {
|
||||
for (k, v) in package
|
||||
.into_installed()
|
||||
.into_iter()
|
||||
.flat_map(|i| i.current_dependencies.0)
|
||||
{
|
||||
let mut entry: BTreeMap<_, _> = current_dependents.remove(&k).unwrap_or_default();
|
||||
entry.insert(package_id.clone(), v);
|
||||
current_dependents.insert(k, entry);
|
||||
}
|
||||
}
|
||||
for (package_id, current_dependents) in current_dependents {
|
||||
if let Some(deps) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.map::<_, CurrentDependents>(|i| i.current_dependents())
|
||||
.check(&mut db)
|
||||
.await?
|
||||
{
|
||||
deps.put(&mut db, &CurrentDependents(current_dependents))
|
||||
.await?;
|
||||
} else if let Some(deps) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|pde| pde.removing())
|
||||
.map::<_, CurrentDependents>(|i| i.current_dependents())
|
||||
.check(&mut db)
|
||||
.await?
|
||||
{
|
||||
deps.put(&mut db, &CurrentDependents(current_dependents))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
for (package_id, package) in receipts.packages.get(&mut db).await?.0 {
|
||||
if let Err(e) = async {
|
||||
match package {
|
||||
@@ -338,31 +377,6 @@ impl RpcContext {
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
let mut current_dependents = BTreeMap::new();
|
||||
for (package_id, package) in receipts.packages.get(&mut db).await?.0 {
|
||||
for (k, v) in package
|
||||
.into_installed()
|
||||
.into_iter()
|
||||
.flat_map(|i| i.current_dependencies.0)
|
||||
{
|
||||
let mut entry: BTreeMap<_, _> = current_dependents.remove(&k).unwrap_or_default();
|
||||
entry.insert(package_id.clone(), v);
|
||||
current_dependents.insert(k, entry);
|
||||
}
|
||||
}
|
||||
for (package_id, current_dependents) in current_dependents {
|
||||
if let Some(deps) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.map::<_, CurrentDependents>(|i| i.current_dependents())
|
||||
.check(&mut db)
|
||||
.await?
|
||||
{
|
||||
deps.put(&mut db, &CurrentDependents(current_dependents))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ pub struct SetupContextConfig {
|
||||
pub migration_batch_rows: Option<usize>,
|
||||
pub migration_prefetch_rows: Option<usize>,
|
||||
pub datadir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub disable_encryption: bool,
|
||||
}
|
||||
impl SetupContextConfig {
|
||||
#[instrument(skip_all)]
|
||||
@@ -75,6 +77,7 @@ pub struct SetupContextSeed {
|
||||
pub config_path: Option<PathBuf>,
|
||||
pub migration_batch_rows: usize,
|
||||
pub migration_prefetch_rows: usize,
|
||||
pub disable_encryption: bool,
|
||||
pub shutdown: Sender<()>,
|
||||
pub datadir: PathBuf,
|
||||
pub selected_v2_drive: RwLock<Option<PathBuf>>,
|
||||
@@ -102,6 +105,7 @@ impl SetupContext {
|
||||
config_path: path.as_ref().map(|p| p.as_ref().to_owned()),
|
||||
migration_batch_rows: cfg.migration_batch_rows.unwrap_or(25000),
|
||||
migration_prefetch_rows: cfg.migration_prefetch_rows.unwrap_or(100_000),
|
||||
disable_encryption: cfg.disable_encryption,
|
||||
shutdown,
|
||||
datadir,
|
||||
selected_v2_drive: RwLock::new(None),
|
||||
|
||||
@@ -4,9 +4,10 @@ pub mod package;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{Dump, Revision};
|
||||
use patch_db::{DbHandle, Dump, LockType, Revision};
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::hyper::upgrade::Upgraded;
|
||||
use rpc_toolkit::hyper::{Body, Error as HyperError, Request, Response};
|
||||
@@ -24,6 +25,7 @@ use tracing::instrument;
|
||||
pub use self::model::DatabaseModel;
|
||||
use crate::context::RpcContext;
|
||||
use crate::middleware::auth::{HasValidSession, HashSessionToken};
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
@@ -163,7 +165,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request<Body>) -> Result<Response<B
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[command(subcommands(revisions, dump, put))]
|
||||
#[command(subcommands(revisions, dump, put, apply))]
|
||||
pub fn db() -> Result<(), RpcError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -199,6 +201,85 @@ pub async fn dump(
|
||||
Ok(ctx.db.dump().await?)
|
||||
}
|
||||
|
||||
fn apply_expr(input: jaq_core::Val, expr: &str) -> Result<jaq_core::Val, Error> {
|
||||
let (expr, errs) = jaq_core::parse::parse(expr, jaq_core::parse::main());
|
||||
|
||||
let Some(expr) = expr else {
|
||||
return Err(Error::new(
|
||||
eyre!("Failed to parse expression: {:?}", errs),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
|
||||
let mut errs = Vec::new();
|
||||
|
||||
let mut defs = jaq_core::Definitions::core();
|
||||
for def in jaq_std::std() {
|
||||
defs.insert(def, &mut errs);
|
||||
}
|
||||
|
||||
let filter = defs.finish(expr, Vec::new(), &mut errs);
|
||||
|
||||
if !errs.is_empty() {
|
||||
return Err(Error::new(
|
||||
eyre!("Failed to compile expression: {:?}", errs),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
|
||||
let inputs = jaq_core::RcIter::new(std::iter::empty());
|
||||
let mut res_iter = filter.run(jaq_core::Ctx::new([], &inputs), input);
|
||||
|
||||
let Some(res) = res_iter
|
||||
.next()
|
||||
.transpose()
|
||||
.map_err(|e| eyre!("{e}"))
|
||||
.with_kind(crate::ErrorKind::Deserialization)?
|
||||
else {
|
||||
return Err(Error::new(
|
||||
eyre!("expr returned no results"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
|
||||
if res_iter.next().is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("expr returned too many results"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn apply(#[context] ctx: RpcContext, #[arg] expr: String) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
|
||||
DatabaseModel::new().lock(&mut db, LockType::Write).await?;
|
||||
|
||||
let root_ptr = JsonPointer::<String>::default();
|
||||
|
||||
let input = db.get_value(&root_ptr, None).await?;
|
||||
|
||||
let res = (|| {
|
||||
let res = apply_expr(input.into(), &expr)?;
|
||||
|
||||
serde_json::from_value::<model::Database>(res.clone().into()).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Deserialization,
|
||||
"result does not match database model",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok::<serde_json::Value, Error>(res.into())
|
||||
})()?;
|
||||
|
||||
db.put_value(&root_ptr, &res).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(subcommands(ui))]
|
||||
pub fn put() -> Result<(), RpcError> {
|
||||
Ok(())
|
||||
|
||||
@@ -51,7 +51,7 @@ impl Database {
|
||||
last_wifi_region: None,
|
||||
eos_version_compat: Current::new().compat().clone(),
|
||||
lan_address,
|
||||
tor_address: format!("http://{}", account.key.tor_address())
|
||||
tor_address: format!("https://{}", account.key.tor_address())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
ip_info: BTreeMap::new(),
|
||||
@@ -110,6 +110,7 @@ pub struct ServerInfo {
|
||||
pub lan_address: Url,
|
||||
pub tor_address: Url,
|
||||
#[model]
|
||||
#[serde(default)]
|
||||
pub ip_info: BTreeMap<String, IpInfo>,
|
||||
#[model]
|
||||
#[serde(default)]
|
||||
|
||||
@@ -9,11 +9,10 @@ use crate::disk::repair;
|
||||
use crate::init::SYSTEM_REBUILD_PATH;
|
||||
use crate::logs::{fetch_logs, LogResponse, LogSource};
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::SYSTEMD_UNIT;
|
||||
use crate::util::display_none;
|
||||
use crate::Error;
|
||||
|
||||
pub const SYSTEMD_UNIT: &'static str = "embassy-init";
|
||||
|
||||
#[command(subcommands(error, logs, exit, restart, forget_disk, disk, rebuild))]
|
||||
pub fn diagnostic() -> Result<(), Error> {
|
||||
Ok(())
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::disk::mount::util::unmount;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub const PASSWORD_PATH: &'static str = "/etc/embassy/password";
|
||||
pub const PASSWORD_PATH: &'static str = "/run/embassy/password";
|
||||
pub const DEFAULT_PASSWORD: &'static str = "password";
|
||||
pub const MAIN_FS_SIZE: FsSize = FsSize::Gigabytes(8);
|
||||
|
||||
@@ -22,13 +22,13 @@ pub async fn create<I, P>(
|
||||
disks: &I,
|
||||
pvscan: &BTreeMap<PathBuf, Option<String>>,
|
||||
datadir: impl AsRef<Path>,
|
||||
password: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
for<'a> &'a I: IntoIterator<Item = &'a P>,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let guid = create_pool(disks, pvscan).await?;
|
||||
let guid = create_pool(disks, pvscan, password.is_some()).await?;
|
||||
create_all_fs(&guid, &datadir, password).await?;
|
||||
export(&guid, datadir).await?;
|
||||
Ok(guid)
|
||||
@@ -38,6 +38,7 @@ where
|
||||
pub async fn create_pool<I, P>(
|
||||
disks: &I,
|
||||
pvscan: &BTreeMap<PathBuf, Option<String>>,
|
||||
encrypted: bool,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
for<'a> &'a I: IntoIterator<Item = &'a P>,
|
||||
@@ -62,13 +63,16 @@ where
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
}
|
||||
let guid = format!(
|
||||
let mut guid = format!(
|
||||
"EMBASSY_{}",
|
||||
base32::encode(
|
||||
base32::Alphabet::RFC4648 { padding: false },
|
||||
&rand::random::<[u8; 32]>(),
|
||||
)
|
||||
);
|
||||
if !encrypted {
|
||||
guid += "_UNENC";
|
||||
}
|
||||
let mut cmd = Command::new("vgcreate");
|
||||
cmd.arg("-y").arg(&guid);
|
||||
for disk in disks {
|
||||
@@ -90,11 +94,8 @@ pub async fn create_fs<P: AsRef<Path>>(
|
||||
datadir: P,
|
||||
name: &str,
|
||||
size: FsSize,
|
||||
password: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
tokio::fs::write(PASSWORD_PATH, password)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
let mut cmd = Command::new("lvcreate");
|
||||
match size {
|
||||
FsSize::Gigabytes(a) => cmd.arg("-L").arg(format!("{}G", a)),
|
||||
@@ -106,37 +107,41 @@ pub async fn create_fs<P: AsRef<Path>>(
|
||||
.arg(guid)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
let crypt_path = Path::new("/dev").join(guid).join(name);
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksFormat")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&crypt_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksOpen")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&crypt_path)
|
||||
.arg(format!("{}_{}", guid, name))
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
let mut blockdev_path = Path::new("/dev").join(guid).join(name);
|
||||
if let Some(password) = password {
|
||||
if let Some(parent) = Path::new(PASSWORD_PATH).parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
tokio::fs::write(PASSWORD_PATH, password)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksFormat")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&blockdev_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksOpen")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&blockdev_path)
|
||||
.arg(format!("{}_{}", guid, name))
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
tokio::fs::remove_file(PASSWORD_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
blockdev_path = Path::new("/dev/mapper").join(format!("{}_{}", guid, name));
|
||||
}
|
||||
Command::new("mkfs.btrfs")
|
||||
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
|
||||
.arg(&blockdev_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
mount(
|
||||
Path::new("/dev/mapper").join(format!("{}_{}", guid, name)),
|
||||
datadir.as_ref().join(name),
|
||||
ReadWrite,
|
||||
)
|
||||
.await?;
|
||||
tokio::fs::remove_file(PASSWORD_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
mount(&blockdev_path, datadir.as_ref().join(name), ReadWrite).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -144,7 +149,7 @@ pub async fn create_fs<P: AsRef<Path>>(
|
||||
pub async fn create_all_fs<P: AsRef<Path>>(
|
||||
guid: &str,
|
||||
datadir: P,
|
||||
password: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
create_fs(guid, &datadir, "main", MAIN_FS_SIZE, password).await?;
|
||||
create_fs(
|
||||
@@ -161,12 +166,14 @@ pub async fn create_all_fs<P: AsRef<Path>>(
|
||||
#[instrument(skip_all)]
|
||||
pub async fn unmount_fs<P: AsRef<Path>>(guid: &str, datadir: P, name: &str) -> Result<(), Error> {
|
||||
unmount(datadir.as_ref().join(name)).await?;
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksClose")
|
||||
.arg(format!("{}_{}", guid, name))
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
if !guid.ends_with("_UNENC") {
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksClose")
|
||||
.arg(format!("{}_{}", guid, name))
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -203,7 +210,7 @@ pub async fn import<P: AsRef<Path>>(
|
||||
guid: &str,
|
||||
datadir: P,
|
||||
repair: RepairStrategy,
|
||||
password: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<RequiresReboot, Error> {
|
||||
let scan = pvscan().await?;
|
||||
if scan
|
||||
@@ -261,46 +268,56 @@ pub async fn mount_fs<P: AsRef<Path>>(
|
||||
datadir: P,
|
||||
name: &str,
|
||||
repair: RepairStrategy,
|
||||
password: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<RequiresReboot, Error> {
|
||||
tokio::fs::write(PASSWORD_PATH, password)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
let crypt_path = Path::new("/dev").join(guid).join(name);
|
||||
let orig_path = Path::new("/dev").join(guid).join(name);
|
||||
let mut blockdev_path = orig_path.clone();
|
||||
let full_name = format!("{}_{}", guid, name);
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksOpen")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&crypt_path)
|
||||
.arg(&full_name)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
let mapper_path = Path::new("/dev/mapper").join(&full_name);
|
||||
let reboot = repair.fsck(&mapper_path).await?;
|
||||
// Backup LUKS header if e2fsck succeeded
|
||||
let luks_folder = Path::new("/media/embassy/config/luks");
|
||||
tokio::fs::create_dir_all(luks_folder).await?;
|
||||
let tmp_luks_bak = luks_folder.join(format!(".{full_name}.luks.bak.tmp"));
|
||||
if tokio::fs::metadata(&tmp_luks_bak).await.is_ok() {
|
||||
tokio::fs::remove_file(&tmp_luks_bak).await?;
|
||||
if !guid.ends_with("_UNENC") {
|
||||
let password = password.unwrap_or(DEFAULT_PASSWORD);
|
||||
if let Some(parent) = Path::new(PASSWORD_PATH).parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
tokio::fs::write(PASSWORD_PATH, password)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksOpen")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&blockdev_path)
|
||||
.arg(&full_name)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
tokio::fs::remove_file(PASSWORD_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
blockdev_path = Path::new("/dev/mapper").join(&full_name);
|
||||
}
|
||||
let luks_bak = luks_folder.join(format!("{full_name}.luks.bak"));
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksHeaderBackup")
|
||||
.arg("--header-backup-file")
|
||||
.arg(&tmp_luks_bak)
|
||||
.arg(&crypt_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
tokio::fs::rename(&tmp_luks_bak, &luks_bak).await?;
|
||||
mount(&mapper_path, datadir.as_ref().join(name), ReadWrite).await?;
|
||||
let reboot = repair.fsck(&blockdev_path).await?;
|
||||
|
||||
tokio::fs::remove_file(PASSWORD_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
|
||||
if !guid.ends_with("_UNENC") {
|
||||
// Backup LUKS header if e2fsck succeeded
|
||||
let luks_folder = Path::new("/media/embassy/config/luks");
|
||||
tokio::fs::create_dir_all(luks_folder).await?;
|
||||
let tmp_luks_bak = luks_folder.join(format!(".{full_name}.luks.bak.tmp"));
|
||||
if tokio::fs::metadata(&tmp_luks_bak).await.is_ok() {
|
||||
tokio::fs::remove_file(&tmp_luks_bak).await?;
|
||||
}
|
||||
let luks_bak = luks_folder.join(format!("{full_name}.luks.bak"));
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksHeaderBackup")
|
||||
.arg("--header-backup-file")
|
||||
.arg(&tmp_luks_bak)
|
||||
.arg(&orig_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
tokio::fs::rename(&tmp_luks_bak, &luks_bak).await?;
|
||||
}
|
||||
|
||||
mount(&blockdev_path, datadir.as_ref().join(name), ReadWrite).await?;
|
||||
|
||||
Ok(reboot)
|
||||
}
|
||||
@@ -310,7 +327,7 @@ pub async fn mount_all_fs<P: AsRef<Path>>(
|
||||
guid: &str,
|
||||
datadir: P,
|
||||
repair: RepairStrategy,
|
||||
password: &str,
|
||||
password: Option<&str>,
|
||||
) -> Result<RequiresReboot, Error> {
|
||||
let mut reboot = RequiresReboot(false);
|
||||
reboot |= mount_fs(guid, &datadir, "main", repair, password).await?;
|
||||
|
||||
@@ -16,6 +16,9 @@ use crate::util::Invoke;
|
||||
use crate::Error;
|
||||
|
||||
async fn resolve_hostname(hostname: &str) -> Result<IpAddr, Error> {
|
||||
if let Ok(addr) = hostname.parse() {
|
||||
return Ok(addr);
|
||||
}
|
||||
#[cfg(feature = "avahi")]
|
||||
if hostname.ends_with(".local") {
|
||||
return Ok(IpAddr::V4(crate::net::mdns::resolve_mdns(hostname).await?));
|
||||
|
||||
@@ -324,11 +324,13 @@ pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
|
||||
if index.internal {
|
||||
for part in index.parts {
|
||||
let mut disk_info = disk_info(disk.clone()).await;
|
||||
disk_info.logicalname = part;
|
||||
let part_info = part_info(part).await;
|
||||
disk_info.logicalname = part_info.logicalname.clone();
|
||||
disk_info.capacity = part_info.capacity;
|
||||
if let Some(g) = disk_guids.get(&disk_info.logicalname) {
|
||||
disk_info.guid = g.clone();
|
||||
} else {
|
||||
disk_info.partitions = vec![part_info(disk_info.logicalname.clone()).await];
|
||||
disk_info.partitions = vec![part_info];
|
||||
}
|
||||
res.push(disk_info);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use patch_db::{DbHandle, LockType};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::{json, Value};
|
||||
use tokio::fs::{File, OpenOptions};
|
||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt};
|
||||
use tokio::process::Command;
|
||||
@@ -60,7 +61,7 @@ pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
||||
pub const PKG_WASM_DIR: &str = "package-data/wasm";
|
||||
|
||||
#[command(display(display_serializable))]
|
||||
pub async fn list(#[context] ctx: RpcContext) -> Result<Vec<(PackageId, Version)>, Error> {
|
||||
pub async fn list(#[context] ctx: RpcContext) -> Result<Value, Error> {
|
||||
let mut hdl = ctx.db.handle();
|
||||
let package_data = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
@@ -70,11 +71,25 @@ pub async fn list(#[context] ctx: RpcContext) -> Result<Vec<(PackageId, Version)
|
||||
Ok(package_data
|
||||
.0
|
||||
.iter()
|
||||
.filter_map(|(id, pde)| match pde {
|
||||
PackageDataEntry::Installed { installed, .. } => {
|
||||
Some((id.clone(), installed.manifest.version.clone()))
|
||||
}
|
||||
_ => None,
|
||||
.filter_map(|(id, pde)| {
|
||||
serde_json::to_value(match pde {
|
||||
PackageDataEntry::Installed { installed, .. } => {
|
||||
json!({ "status":"installed","id": id.clone(), "version": installed.manifest.version.clone()})
|
||||
}
|
||||
PackageDataEntry::Installing { manifest, install_progress, .. } => {
|
||||
json!({ "status":"installing","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()})
|
||||
}
|
||||
PackageDataEntry::Updating { manifest, installed, install_progress, .. } => {
|
||||
json!({ "status":"updating","id": id.clone(), "version": installed.manifest.version.clone(), "progress": install_progress.clone()})
|
||||
}
|
||||
PackageDataEntry::Restoring { manifest, install_progress, .. } => {
|
||||
json!({ "status":"restoring","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()})
|
||||
}
|
||||
PackageDataEntry::Removing { manifest, .. } => {
|
||||
json!({ "status":"removing", "id": id.clone(), "version": manifest.version.clone()})
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -1231,7 +1246,6 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
||||
current_dependencies: current_dependencies.clone(),
|
||||
interface_addresses,
|
||||
};
|
||||
|
||||
let prev = std::mem::replace(
|
||||
&mut *pde,
|
||||
PackageDataEntry::Installed {
|
||||
|
||||
@@ -17,6 +17,7 @@ pub mod account;
|
||||
pub mod action;
|
||||
pub mod auth;
|
||||
pub mod backup;
|
||||
pub mod bins;
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
pub mod control;
|
||||
|
||||
3
backend/src/main.rs
Normal file
3
backend/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
startos::bins::startbox()
|
||||
}
|
||||
@@ -61,17 +61,16 @@ impl ManageContainer {
|
||||
|
||||
pub fn set_override(&self, override_status: Option<MainStatus>) -> GeneralBoxedGuard {
|
||||
self.override_main_status
|
||||
.send(override_status)
|
||||
.unwrap_or_default();
|
||||
.send_modify(|x| *x = override_status);
|
||||
let override_main_status = self.override_main_status.clone();
|
||||
let guard = GeneralBoxedGuard::new(move || {
|
||||
override_main_status.send(None).unwrap_or_default();
|
||||
override_main_status.send_modify(|x| *x = None);
|
||||
});
|
||||
guard
|
||||
}
|
||||
|
||||
pub fn to_desired(&self, new_state: StartStop) {
|
||||
self.desired_state.send(new_state).unwrap_or_default();
|
||||
self.desired_state.send_modify(|x| *x = new_state);
|
||||
}
|
||||
|
||||
pub async fn wait_for_desired(&self, new_state: StartStop) {
|
||||
@@ -123,7 +122,7 @@ async fn create_service_manager(
|
||||
}
|
||||
}
|
||||
}
|
||||
current_state.send(StartStop::Stop).unwrap_or_default();
|
||||
current_state.send_modify(|x| *x = StartStop::Stop);
|
||||
}
|
||||
(StartStop::Stop, StartStop::Start) => starting_service(
|
||||
current_state.clone(),
|
||||
@@ -201,10 +200,10 @@ fn starting_service(
|
||||
let set_running = {
|
||||
let current_state = current_state.clone();
|
||||
Arc::new(move || {
|
||||
current_state.send(StartStop::Start).unwrap_or_default();
|
||||
current_state.send_modify(|x| *x = StartStop::Start);
|
||||
})
|
||||
};
|
||||
let set_stopped = { move || current_state.send(StartStop::Stop) };
|
||||
let set_stopped = { move || current_state.send_modify(|x| *x = StartStop::Stop) };
|
||||
let running_main_loop = async move {
|
||||
while desired_state.borrow().is_start() {
|
||||
let result = run_main(
|
||||
@@ -213,7 +212,7 @@ fn starting_service(
|
||||
set_running.clone(),
|
||||
)
|
||||
.await;
|
||||
set_stopped().unwrap_or_default();
|
||||
set_stopped();
|
||||
run_main_log_result(result, seed.clone()).await;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ use crate::disk::mount::backup::BackupMountGuard;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::install::cleanup::remove_from_current_dependents_lists;
|
||||
use crate::net::net_controller::NetService;
|
||||
use crate::net::vhost::AlpnInfo;
|
||||
use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning};
|
||||
use crate::procedure::{NoOutput, ProcedureName};
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
@@ -267,7 +268,6 @@ impl Manager {
|
||||
let _ = manage_container
|
||||
.set_override(Some(get_status(&mut tx, &seed.manifest).await.backing_up()));
|
||||
manage_container.wait_for_desired(StartStop::Stop).await;
|
||||
|
||||
let backup_guard = backup_guard.lock().await;
|
||||
let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?;
|
||||
|
||||
@@ -825,8 +825,14 @@ async fn add_network_for_main(
|
||||
let mut tx = secrets.begin().await?;
|
||||
for (id, interface) in &seed.manifest.interfaces.0 {
|
||||
for (external, internal) in interface.lan_config.iter().flatten() {
|
||||
svc.add_lan(&mut tx, id.clone(), external.0, internal.internal, false)
|
||||
.await?;
|
||||
svc.add_lan(
|
||||
&mut tx,
|
||||
id.clone(),
|
||||
external.0,
|
||||
internal.internal,
|
||||
Err(AlpnInfo::Specified(vec![])),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
for (external, internal) in interface.tor_config.iter().flat_map(|t| &t.port_mapping) {
|
||||
svc.add_tor(&mut tx, id.clone(), external.0, internal.0)
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::net::keys::Key;
|
||||
use crate::net::mdns::MdnsController;
|
||||
use crate::net::ssl::{export_cert, export_key, SslManager};
|
||||
use crate::net::tor::TorController;
|
||||
use crate::net::vhost::VHostController;
|
||||
use crate::net::vhost::{AlpnInfo, VHostController};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::volume::cert_dir;
|
||||
use crate::{Error, HOST_IP};
|
||||
@@ -59,6 +59,8 @@ impl NetController {
|
||||
}
|
||||
|
||||
async fn add_os_bindings(&mut self, hostname: &Hostname, key: &Key) -> Result<(), Error> {
|
||||
let alpn = Err(AlpnInfo::Specified(vec!["http/1.1".into(), "h2".into()]));
|
||||
|
||||
// Internal DNS
|
||||
self.vhost
|
||||
.add(
|
||||
@@ -66,7 +68,7 @@ impl NetController {
|
||||
Some("embassy".into()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
false,
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?;
|
||||
self.os_bindings
|
||||
@@ -75,7 +77,13 @@ impl NetController {
|
||||
// LAN IP
|
||||
self.os_bindings.push(
|
||||
self.vhost
|
||||
.add(key.clone(), None, 443, ([127, 0, 0, 1], 80).into(), false)
|
||||
.add(
|
||||
key.clone(),
|
||||
None,
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
@@ -87,7 +95,7 @@ impl NetController {
|
||||
Some("localhost".into()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
false,
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -98,7 +106,7 @@ impl NetController {
|
||||
Some(hostname.no_dot_host_name()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
false,
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -111,7 +119,7 @@ impl NetController {
|
||||
Some(hostname.local_domain_name()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
false,
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -131,7 +139,7 @@ impl NetController {
|
||||
Some(key.tor_address().to_string()),
|
||||
443,
|
||||
([127, 0, 0, 1], 80).into(),
|
||||
false,
|
||||
alpn.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -184,7 +192,7 @@ impl NetController {
|
||||
key: Key,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
connect_ssl: bool,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
) -> Result<Vec<Arc<()>>, Error> {
|
||||
let mut rcs = Vec::with_capacity(2);
|
||||
rcs.push(
|
||||
@@ -278,8 +286,8 @@ impl NetService {
|
||||
id: InterfaceId,
|
||||
external: u16,
|
||||
internal: u16,
|
||||
connect_ssl: bool,
|
||||
) -> Result<String, Error>
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fs::Metadata;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use async_compression::tokio::bufread::{BrotliEncoder, GzipEncoder};
|
||||
use async_compression::tokio::bufread::GzipEncoder;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::Digest;
|
||||
use futures::FutureExt;
|
||||
use http::header::{ACCEPT_ENCODING, CONTENT_ENCODING};
|
||||
use http::header::ACCEPT_ENCODING;
|
||||
use http::request::Parts as RequestParts;
|
||||
use http::response::Builder;
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use new_mime_guess::MimeGuess;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::x509::X509;
|
||||
use rpc_toolkit::rpc_handler;
|
||||
@@ -33,10 +36,7 @@ static NOT_FOUND: &[u8] = b"Not Found";
|
||||
static METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed";
|
||||
static NOT_AUTHORIZED: &[u8] = b"Not Authorized";
|
||||
|
||||
pub const MAIN_UI_WWW_DIR: &str = "/var/www/html/main";
|
||||
pub const SETUP_UI_WWW_DIR: &str = "/var/www/html/setup";
|
||||
pub const DIAG_UI_WWW_DIR: &str = "/var/www/html/diagnostic";
|
||||
pub const INSTALL_UI_WWW_DIR: &str = "/var/www/html/install";
|
||||
static EMBEDDED_UIS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist/static");
|
||||
|
||||
fn status_fn(_: i32) -> StatusCode {
|
||||
StatusCode::OK
|
||||
@@ -50,6 +50,17 @@ pub enum UiMode {
|
||||
Main,
|
||||
}
|
||||
|
||||
impl UiMode {
|
||||
fn path(&self, path: &str) -> PathBuf {
|
||||
match self {
|
||||
Self::Setup => Path::new("setup-wizard").join(path),
|
||||
Self::Diag => Path::new("diagnostic-ui").join(path),
|
||||
Self::Install => Path::new("install-wizard").join(path),
|
||||
Self::Main => Path::new("ui").join(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_ui_file_router(ctx: SetupContext) -> Result<HttpHandler, Error> {
|
||||
let handler: HttpHandler = Arc::new(move |req| {
|
||||
let ctx = ctx.clone();
|
||||
@@ -224,13 +235,6 @@ pub async fn main_ui_server_router(ctx: RpcContext) -> Result<HttpHandler, Error
|
||||
}
|
||||
|
||||
async fn alt_ui(req: Request<Body>, ui_mode: UiMode) -> Result<Response<Body>, Error> {
|
||||
let selected_root_dir = match ui_mode {
|
||||
UiMode::Setup => SETUP_UI_WWW_DIR,
|
||||
UiMode::Diag => DIAG_UI_WWW_DIR,
|
||||
UiMode::Install => INSTALL_UI_WWW_DIR,
|
||||
UiMode::Main => MAIN_UI_WWW_DIR,
|
||||
};
|
||||
|
||||
let (request_parts, _body) = req.into_parts();
|
||||
let accept_encoding = request_parts
|
||||
.headers
|
||||
@@ -243,46 +247,32 @@ async fn alt_ui(req: Request<Body>, ui_mode: UiMode) -> Result<Response<Body>, E
|
||||
.collect::<Vec<_>>();
|
||||
match &request_parts.method {
|
||||
&Method::GET => {
|
||||
let uri_path = request_parts
|
||||
.uri
|
||||
.path()
|
||||
.strip_prefix('/')
|
||||
.unwrap_or(request_parts.uri.path());
|
||||
let uri_path = ui_mode.path(
|
||||
request_parts
|
||||
.uri
|
||||
.path()
|
||||
.strip_prefix('/')
|
||||
.unwrap_or(request_parts.uri.path()),
|
||||
);
|
||||
|
||||
let full_path = Path::new(selected_root_dir).join(uri_path);
|
||||
file_send(
|
||||
&request_parts,
|
||||
if tokio::fs::metadata(&full_path)
|
||||
let file = EMBEDDED_UIS
|
||||
.get_file(&*uri_path)
|
||||
.or_else(|| EMBEDDED_UIS.get_file(&*ui_mode.path("index.html")));
|
||||
|
||||
if let Some(file) = file {
|
||||
FileData::from_embedded(&request_parts, file)
|
||||
.into_response(&request_parts)
|
||||
.await
|
||||
.ok()
|
||||
.map(|f| f.is_file())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
full_path
|
||||
} else {
|
||||
Path::new(selected_root_dir).join("index.html")
|
||||
},
|
||||
&accept_encoding,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Ok(not_found())
|
||||
}
|
||||
}
|
||||
_ => Ok(method_not_allowed()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_embassy_ui(req: Request<Body>, ctx: RpcContext) -> Result<Response<Body>, Error> {
|
||||
let selected_root_dir = MAIN_UI_WWW_DIR;
|
||||
|
||||
let (request_parts, _body) = req.into_parts();
|
||||
let accept_encoding = request_parts
|
||||
.headers
|
||||
.get_all(ACCEPT_ENCODING)
|
||||
.into_iter()
|
||||
.filter_map(|h| h.to_str().ok())
|
||||
.flat_map(|s| s.split(","))
|
||||
.filter_map(|s| s.split(";").next())
|
||||
.map(|s| s.trim())
|
||||
.collect::<Vec<_>>();
|
||||
match (
|
||||
&request_parts.method,
|
||||
request_parts
|
||||
@@ -297,11 +287,12 @@ async fn main_embassy_ui(req: Request<Body>, ctx: RpcContext) -> Result<Response
|
||||
Ok(_) => {
|
||||
let sub_path = Path::new(path);
|
||||
if let Ok(rest) = sub_path.strip_prefix("package-data") {
|
||||
file_send(
|
||||
FileData::from_path(
|
||||
&request_parts,
|
||||
ctx.datadir.join(PKG_PUBLIC_DIR).join(rest),
|
||||
&accept_encoding,
|
||||
&ctx.datadir.join(PKG_PUBLIC_DIR).join(rest),
|
||||
)
|
||||
.await?
|
||||
.into_response(&request_parts)
|
||||
.await
|
||||
} else if let Ok(rest) = sub_path.strip_prefix("eos") {
|
||||
match rest.to_str() {
|
||||
@@ -323,28 +314,25 @@ async fn main_embassy_ui(req: Request<Body>, ctx: RpcContext) -> Result<Response
|
||||
}
|
||||
}
|
||||
(&Method::GET, _) => {
|
||||
let uri_path = request_parts
|
||||
.uri
|
||||
.path()
|
||||
.strip_prefix('/')
|
||||
.unwrap_or(request_parts.uri.path());
|
||||
let uri_path = UiMode::Main.path(
|
||||
request_parts
|
||||
.uri
|
||||
.path()
|
||||
.strip_prefix('/')
|
||||
.unwrap_or(request_parts.uri.path()),
|
||||
);
|
||||
|
||||
let full_path = Path::new(selected_root_dir).join(uri_path);
|
||||
file_send(
|
||||
&request_parts,
|
||||
if tokio::fs::metadata(&full_path)
|
||||
let file = EMBEDDED_UIS
|
||||
.get_file(&*uri_path)
|
||||
.or_else(|| EMBEDDED_UIS.get_file(&*UiMode::Main.path("index.html")));
|
||||
|
||||
if let Some(file) = file {
|
||||
FileData::from_embedded(&request_parts, file)
|
||||
.into_response(&request_parts)
|
||||
.await
|
||||
.ok()
|
||||
.map(|f| f.is_file())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
full_path
|
||||
} else {
|
||||
Path::new(selected_root_dir).join("index.html")
|
||||
},
|
||||
&accept_encoding,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Ok(not_found())
|
||||
}
|
||||
}
|
||||
_ => Ok(method_not_allowed()),
|
||||
}
|
||||
@@ -407,118 +395,163 @@ fn cert_send(cert: &X509) -> Result<Response<Body>, Error> {
|
||||
.with_kind(ErrorKind::Network)
|
||||
}
|
||||
|
||||
async fn file_send(
|
||||
req: &RequestParts,
|
||||
path: impl AsRef<Path>,
|
||||
accept_encoding: &[&str],
|
||||
) -> Result<Response<Body>, Error> {
|
||||
// Serve a file by asynchronously reading it by chunks using tokio-util crate.
|
||||
struct FileData {
|
||||
data: Body,
|
||||
len: Option<u64>,
|
||||
encoding: Option<&'static str>,
|
||||
e_tag: String,
|
||||
mime: Option<String>,
|
||||
}
|
||||
impl FileData {
|
||||
fn from_embedded(req: &RequestParts, file: &'static include_dir::File<'static>) -> Self {
|
||||
let path = file.path();
|
||||
let (encoding, data) = req
|
||||
.headers
|
||||
.get_all(ACCEPT_ENCODING)
|
||||
.into_iter()
|
||||
.filter_map(|h| h.to_str().ok())
|
||||
.flat_map(|s| s.split(","))
|
||||
.filter_map(|s| s.split(";").next())
|
||||
.map(|s| s.trim())
|
||||
.fold((None, file.contents()), |acc, e| {
|
||||
if let Some(file) = (e == "br")
|
||||
.then_some(())
|
||||
.and_then(|_| EMBEDDED_UIS.get_file(format!("{}.br", path.display())))
|
||||
{
|
||||
(Some("br"), file.contents())
|
||||
} else if let Some(file) = (e == "gzip" && acc.0 != Some("br"))
|
||||
.then_some(())
|
||||
.and_then(|_| EMBEDDED_UIS.get_file(format!("{}.gz", path.display())))
|
||||
{
|
||||
(Some("gzip"), file.contents())
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
|
||||
let path = path.as_ref();
|
||||
|
||||
let file = File::open(path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, path.display().to_string()))?;
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, path.display().to_string()))?;
|
||||
|
||||
let e_tag = e_tag(path, &metadata)?;
|
||||
|
||||
let mut builder = Response::builder();
|
||||
builder = with_content_type(path, builder);
|
||||
builder = builder.header(http::header::ETAG, &e_tag);
|
||||
builder = builder.header(
|
||||
http::header::CACHE_CONTROL,
|
||||
"public, max-age=21000000, immutable",
|
||||
);
|
||||
|
||||
if req
|
||||
.headers
|
||||
.get_all(http::header::CONNECTION)
|
||||
.iter()
|
||||
.flat_map(|s| s.to_str().ok())
|
||||
.flat_map(|s| s.split(","))
|
||||
.any(|s| s.trim() == "keep-alive")
|
||||
{
|
||||
builder = builder.header(http::header::CONNECTION, "keep-alive");
|
||||
Self {
|
||||
len: Some(data.len() as u64),
|
||||
encoding,
|
||||
data: data.into(),
|
||||
e_tag: e_tag(path, None),
|
||||
mime: MimeGuess::from_path(path)
|
||||
.first()
|
||||
.map(|m| m.essence_str().to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
if req
|
||||
.headers
|
||||
.get("if-none-match")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
== Some(e_tag.as_str())
|
||||
{
|
||||
builder = builder.status(StatusCode::NOT_MODIFIED);
|
||||
builder.body(Body::empty())
|
||||
} else {
|
||||
let body = if false && accept_encoding.contains(&"br") && metadata.len() > u16::MAX as u64 {
|
||||
builder = builder.header(CONTENT_ENCODING, "br");
|
||||
Body::wrap_stream(ReaderStream::new(BrotliEncoder::new(BufReader::new(file))))
|
||||
} else if accept_encoding.contains(&"gzip") && metadata.len() > u16::MAX as u64 {
|
||||
builder = builder.header(CONTENT_ENCODING, "gzip");
|
||||
Body::wrap_stream(ReaderStream::new(GzipEncoder::new(BufReader::new(file))))
|
||||
async fn from_path(req: &RequestParts, path: &Path) -> Result<Self, Error> {
|
||||
let encoding = req
|
||||
.headers
|
||||
.get_all(ACCEPT_ENCODING)
|
||||
.into_iter()
|
||||
.filter_map(|h| h.to_str().ok())
|
||||
.flat_map(|s| s.split(","))
|
||||
.filter_map(|s| s.split(";").next())
|
||||
.map(|s| s.trim())
|
||||
.any(|e| e == "gzip")
|
||||
.then_some("gzip");
|
||||
|
||||
let file = File::open(path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, path.display().to_string()))?;
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, path.display().to_string()))?;
|
||||
|
||||
let e_tag = e_tag(path, Some(&metadata));
|
||||
|
||||
let (len, data) = if encoding == Some("gzip") {
|
||||
(
|
||||
None,
|
||||
Body::wrap_stream(ReaderStream::new(GzipEncoder::new(BufReader::new(file)))),
|
||||
)
|
||||
} else {
|
||||
builder = with_content_length(&metadata, builder);
|
||||
Body::wrap_stream(ReaderStream::new(file))
|
||||
(
|
||||
Some(metadata.len()),
|
||||
Body::wrap_stream(ReaderStream::new(file)),
|
||||
)
|
||||
};
|
||||
builder.body(body)
|
||||
|
||||
Ok(Self {
|
||||
data,
|
||||
len,
|
||||
encoding,
|
||||
e_tag,
|
||||
mime: MimeGuess::from_path(path)
|
||||
.first()
|
||||
.map(|m| m.essence_str().to_owned()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn into_response(self, req: &RequestParts) -> Result<Response<Body>, Error> {
|
||||
let mut builder = Response::builder();
|
||||
if let Some(mime) = self.mime {
|
||||
builder = builder.header(http::header::CONTENT_TYPE, &*mime);
|
||||
}
|
||||
builder = builder.header(http::header::ETAG, &*self.e_tag);
|
||||
builder = builder.header(
|
||||
http::header::CACHE_CONTROL,
|
||||
"public, max-age=21000000, immutable",
|
||||
);
|
||||
|
||||
if req
|
||||
.headers
|
||||
.get_all(http::header::CONNECTION)
|
||||
.iter()
|
||||
.flat_map(|s| s.to_str().ok())
|
||||
.flat_map(|s| s.split(","))
|
||||
.any(|s| s.trim() == "keep-alive")
|
||||
{
|
||||
builder = builder.header(http::header::CONNECTION, "keep-alive");
|
||||
}
|
||||
|
||||
if req
|
||||
.headers
|
||||
.get("if-none-match")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
== Some(self.e_tag.as_ref())
|
||||
{
|
||||
builder = builder.status(StatusCode::NOT_MODIFIED);
|
||||
builder.body(Body::empty())
|
||||
} else {
|
||||
if let Some(len) = self.len {
|
||||
builder = builder.header(http::header::CONTENT_LENGTH, len);
|
||||
}
|
||||
if let Some(encoding) = self.encoding {
|
||||
builder = builder.header(http::header::CONTENT_ENCODING, encoding);
|
||||
}
|
||||
|
||||
builder.body(self.data)
|
||||
}
|
||||
.with_kind(ErrorKind::Network)
|
||||
}
|
||||
.with_kind(ErrorKind::Network)
|
||||
}
|
||||
|
||||
fn e_tag(path: &Path, metadata: &Metadata) -> Result<String, Error> {
|
||||
let modified = metadata.modified().with_kind(ErrorKind::Filesystem)?;
|
||||
fn e_tag(path: &Path, metadata: Option<&Metadata>) -> String {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(format!("{:?}", path).as_bytes());
|
||||
hasher.update(
|
||||
format!(
|
||||
"{}",
|
||||
modified
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
if let Some(modified) = metadata.and_then(|m| m.modified().ok()) {
|
||||
hasher.update(
|
||||
format!(
|
||||
"{}",
|
||||
modified
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
}
|
||||
let res = hasher.finalize();
|
||||
Ok(format!(
|
||||
format!(
|
||||
"\"{}\"",
|
||||
base32::encode(base32::Alphabet::RFC4648 { padding: false }, res.as_slice()).to_lowercase()
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
///https://en.wikipedia.org/wiki/Media_type
|
||||
fn with_content_type(path: &Path, builder: Builder) -> Builder {
|
||||
let content_type = match path.extension() {
|
||||
Some(os_str) => match os_str.to_str() {
|
||||
Some("apng") => "image/apng",
|
||||
Some("avif") => "image/avif",
|
||||
Some("flif") => "image/flif",
|
||||
Some("gif") => "image/gif",
|
||||
Some("jpg") | Some("jpeg") | Some("jfif") | Some("pjpeg") | Some("pjp") => "image/jpeg",
|
||||
Some("jxl") => "image/jxl",
|
||||
Some("png") => "image/png",
|
||||
Some("svg") => "image/svg+xml",
|
||||
Some("webp") => "image/webp",
|
||||
Some("mng") | Some("x-mng") => "image/x-mng",
|
||||
Some("css") => "text/css",
|
||||
Some("csv") => "text/csv",
|
||||
Some("html") => "text/html",
|
||||
Some("php") => "text/php",
|
||||
Some("plain") | Some("md") | Some("txt") => "text/plain",
|
||||
Some("xml") => "text/xml",
|
||||
Some("js") => "text/javascript",
|
||||
Some("wasm") => "application/wasm",
|
||||
None | Some(_) => "text/plain",
|
||||
},
|
||||
None => "text/plain",
|
||||
};
|
||||
builder.header(http::header::CONTENT_TYPE, content_type)
|
||||
}
|
||||
|
||||
fn with_content_length(metadata: &Metadata, builder: Builder) -> Builder {
|
||||
builder.header(http::header::CONTENT_LENGTH, metadata.len())
|
||||
#[test]
|
||||
fn test_packed_html() {
|
||||
assert!(MainUi::get("index.html").is_some())
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::convert::Infallible;
|
||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
@@ -19,7 +20,7 @@ use tokio_rustls::{LazyConfigAcceptor, TlsConnector};
|
||||
use crate::net::keys::Key;
|
||||
use crate::net::ssl::SslManager;
|
||||
use crate::net::utils::SingleAccept;
|
||||
use crate::util::io::BackTrackingReader;
|
||||
use crate::util::io::{BackTrackingReader, TimeoutStream};
|
||||
use crate::Error;
|
||||
|
||||
// not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353
|
||||
@@ -41,7 +42,7 @@ impl VHostController {
|
||||
hostname: Option<String>,
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
connect_ssl: bool,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
) -> Result<Arc<()>, Error> {
|
||||
let mut writable = self.servers.lock().await;
|
||||
let server = if let Some(server) = writable.remove(&external) {
|
||||
@@ -77,10 +78,16 @@ impl VHostController {
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct TargetInfo {
|
||||
addr: SocketAddr,
|
||||
connect_ssl: bool,
|
||||
connect_ssl: Result<(), AlpnInfo>,
|
||||
key: Key,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum AlpnInfo {
|
||||
Reflect,
|
||||
Specified(Vec<Vec<u8>>),
|
||||
}
|
||||
|
||||
struct VHostServer {
|
||||
mapping: Weak<RwLock<BTreeMap<Option<String>, BTreeMap<TargetInfo, Weak<()>>>>>,
|
||||
_thread: NonDetachingJoinHandle<()>,
|
||||
@@ -98,6 +105,8 @@ impl VHostServer {
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let stream =
|
||||
Box::pin(TimeoutStream::new(stream, Duration::from_secs(300)));
|
||||
let mut stream = BackTrackingReader::new(stream);
|
||||
stream.start_buffering();
|
||||
let mapping = mapping.clone();
|
||||
@@ -178,7 +187,7 @@ impl VHostServer {
|
||||
let cfg = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth();
|
||||
let cfg =
|
||||
let mut cfg =
|
||||
if mid.client_hello().signature_schemes().contains(
|
||||
&tokio_rustls::rustls::SignatureScheme::ED25519,
|
||||
) {
|
||||
@@ -213,49 +222,94 @@ impl VHostServer {
|
||||
.private_key_to_der()?,
|
||||
),
|
||||
)
|
||||
};
|
||||
let mut tls_stream = mid
|
||||
.into_stream(Arc::new(
|
||||
cfg.with_kind(crate::ErrorKind::OpenSsl)?,
|
||||
))
|
||||
.await?;
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
if target.connect_ssl {
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
&mut TlsConnector::from(Arc::new(
|
||||
}
|
||||
.with_kind(crate::ErrorKind::OpenSsl)?;
|
||||
match target.connect_ssl {
|
||||
Ok(()) => {
|
||||
let mut client_cfg =
|
||||
tokio_rustls::rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates({
|
||||
let mut store = RootCertStore::empty();
|
||||
store.add(
|
||||
&tokio_rustls::rustls::Certificate(
|
||||
key.root_ca().to_der()?,
|
||||
),
|
||||
).with_kind(crate::ErrorKind::OpenSsl)?;
|
||||
&tokio_rustls::rustls::Certificate(
|
||||
key.root_ca().to_der()?,
|
||||
),
|
||||
).with_kind(crate::ErrorKind::OpenSsl)?;
|
||||
store
|
||||
})
|
||||
.with_no_client_auth(),
|
||||
))
|
||||
.connect(
|
||||
key.key()
|
||||
.internal_address()
|
||||
.as_str()
|
||||
.try_into()
|
||||
.with_kind(crate::ErrorKind::OpenSsl)?,
|
||||
tcp_stream,
|
||||
.with_no_client_auth();
|
||||
client_cfg.alpn_protocols = mid
|
||||
.client_hello()
|
||||
.alpn()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|x| x.to_vec())
|
||||
.collect();
|
||||
let mut target_stream =
|
||||
TlsConnector::from(Arc::new(client_cfg))
|
||||
.connect_with(
|
||||
key.key()
|
||||
.internal_address()
|
||||
.as_str()
|
||||
.try_into()
|
||||
.with_kind(
|
||||
crate::ErrorKind::OpenSsl,
|
||||
)?,
|
||||
tcp_stream,
|
||||
|conn| {
|
||||
cfg.alpn_protocols.extend(
|
||||
conn.alpn_protocol()
|
||||
.into_iter()
|
||||
.map(|p| p.to_vec()),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::OpenSsl)?;
|
||||
let mut tls_stream =
|
||||
mid.into_stream(Arc::new(cfg)).await?;
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
&mut target_stream,
|
||||
)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::OpenSsl)?,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
&mut tcp_stream,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Err(AlpnInfo::Reflect) => {
|
||||
for proto in
|
||||
mid.client_hello().alpn().into_iter().flatten()
|
||||
{
|
||||
cfg.alpn_protocols.push(proto.into());
|
||||
}
|
||||
let mut tls_stream =
|
||||
mid.into_stream(Arc::new(cfg)).await?;
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
&mut tcp_stream,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(AlpnInfo::Specified(alpn)) => {
|
||||
cfg.alpn_protocols = alpn;
|
||||
let mut tls_stream =
|
||||
mid.into_stream(Arc::new(cfg)).await?;
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
&mut tcp_stream,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
.map_or_else(
|
||||
|e| match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => Ok(()),
|
||||
_ => Err(e),
|
||||
},
|
||||
|_| Ok(()),
|
||||
)?;
|
||||
} else {
|
||||
// 503
|
||||
}
|
||||
|
||||
@@ -149,29 +149,36 @@ pub async fn execute(
|
||||
|
||||
if !overwrite {
|
||||
if let Ok(guard) =
|
||||
TmpMountGuard::mount(&BlockDev::new(part_info.root.clone()), MountType::ReadOnly).await
|
||||
TmpMountGuard::mount(&BlockDev::new(part_info.root.clone()), MountType::ReadWrite).await
|
||||
{
|
||||
if let Err(e) = async {
|
||||
// cp -r ${guard}/config /tmp/config
|
||||
Command::new("cp")
|
||||
.arg("-r")
|
||||
.arg(guard.as_ref().join("config"))
|
||||
.arg("/tmp/config.bak")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
if tokio::fs::metadata(guard.as_ref().join("config/upgrade"))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
tokio::fs::remove_file(guard.as_ref().join("config/upgrade")).await?;
|
||||
}
|
||||
guard.unmount().await
|
||||
if tokio::fs::metadata(guard.as_ref().join("config/disk.guid"))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
tokio::fs::remove_file(guard.as_ref().join("config/disk.guid")).await?;
|
||||
}
|
||||
Command::new("cp")
|
||||
.arg("-r")
|
||||
.arg(guard.as_ref().join("config"))
|
||||
.arg("/tmp/config.bak")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error recovering previous config: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
guard.unmount().await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::StreamExt;
|
||||
use helpers::{Rsync, RsyncOptions};
|
||||
use josekit::jwk::Jwk;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::DbHandle;
|
||||
@@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sqlx::Connection;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::try_join;
|
||||
use torut::onion::OnionAddressV3;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -32,6 +33,7 @@ use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::{init, InitResult};
|
||||
use crate::middleware::encrypt::EncryptedWire;
|
||||
use crate::util::io::{dir_copy, dir_size, Counter};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[command(subcommands(status, disk, attach, execute, cifs, complete, get_pubkey, exit))]
|
||||
@@ -123,7 +125,7 @@ pub async fn attach(
|
||||
} else {
|
||||
RepairStrategy::Preen
|
||||
},
|
||||
DEFAULT_PASSWORD,
|
||||
if guid.ends_with("_UNENC") { None } else { Some(DEFAULT_PASSWORD) },
|
||||
)
|
||||
.await?;
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
@@ -142,7 +144,7 @@ pub async fn attach(
|
||||
}
|
||||
let (hostname, tor_addr, root_ca) = setup_init(&ctx, password).await?;
|
||||
*ctx.setup_result.write().await = Some((guid, SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
tor_address: format!("https://{}", tor_addr),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
}));
|
||||
@@ -279,7 +281,7 @@ pub async fn execute(
|
||||
*ctx.setup_result.write().await = Some((
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
tor_address: format!("https://{}", tor_addr),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(
|
||||
root_ca.to_pem().expect("failed to serialize root ca"),
|
||||
@@ -335,12 +337,17 @@ pub async fn execute_inner(
|
||||
recovery_source: Option<RecoverySource>,
|
||||
recovery_password: Option<String>,
|
||||
) -> Result<(Arc<String>, Hostname, OnionAddressV3, X509), Error> {
|
||||
let encryption_password = if ctx.disable_encryption {
|
||||
None
|
||||
} else {
|
||||
Some(DEFAULT_PASSWORD)
|
||||
};
|
||||
let guid = Arc::new(
|
||||
crate::disk::main::create(
|
||||
&[embassy_logicalname],
|
||||
&pvscan().await?,
|
||||
&ctx.datadir,
|
||||
DEFAULT_PASSWORD,
|
||||
encryption_password,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
@@ -348,7 +355,7 @@ pub async fn execute_inner(
|
||||
&*guid,
|
||||
&ctx.datadir,
|
||||
RepairStrategy::Preen,
|
||||
DEFAULT_PASSWORD,
|
||||
encryption_password,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -416,74 +423,78 @@ async fn migrate(
|
||||
&old_guid,
|
||||
"/media/embassy/migrate",
|
||||
RepairStrategy::Preen,
|
||||
DEFAULT_PASSWORD,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut main_transfer = Rsync::new(
|
||||
"/media/embassy/migrate/main/",
|
||||
"/embassy-data/main/",
|
||||
RsyncOptions {
|
||||
delete: true,
|
||||
force: true,
|
||||
ignore_existing: false,
|
||||
exclude: Vec::new(),
|
||||
no_permissions: false,
|
||||
no_owner: false,
|
||||
if guid.ends_with("_UNENC") {
|
||||
None
|
||||
} else {
|
||||
Some(DEFAULT_PASSWORD)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let mut package_data_transfer = Rsync::new(
|
||||
|
||||
let main_transfer_args = ("/media/embassy/migrate/main/", "/embassy-data/main/");
|
||||
let package_data_transfer_args = (
|
||||
"/media/embassy/migrate/package-data/",
|
||||
"/embassy-data/package-data/",
|
||||
RsyncOptions {
|
||||
delete: true,
|
||||
force: true,
|
||||
ignore_existing: false,
|
||||
exclude: vec!["tmp".to_owned()],
|
||||
no_permissions: false,
|
||||
no_owner: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
);
|
||||
|
||||
let mut main_prog = 0.0;
|
||||
let mut main_complete = false;
|
||||
let mut pkg_prog = 0.0;
|
||||
let mut pkg_complete = false;
|
||||
loop {
|
||||
tokio::select! {
|
||||
p = main_transfer.progress.next() => {
|
||||
if let Some(p) = p {
|
||||
main_prog = p;
|
||||
} else {
|
||||
main_prog = 1.0;
|
||||
main_complete = true;
|
||||
}
|
||||
}
|
||||
p = package_data_transfer.progress.next() => {
|
||||
if let Some(p) = p {
|
||||
pkg_prog = p;
|
||||
} else {
|
||||
pkg_prog = 1.0;
|
||||
pkg_complete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if main_prog > 0.0 && pkg_prog > 0.0 {
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: ((main_prog * 50.0) + (pkg_prog * 950.0)) as u64,
|
||||
total_bytes: Some(1000),
|
||||
complete: false,
|
||||
}));
|
||||
}
|
||||
if main_complete && pkg_complete {
|
||||
break;
|
||||
}
|
||||
let tmpdir = Path::new(package_data_transfer_args.0).join("tmp");
|
||||
if tokio::fs::metadata(&tmpdir).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmpdir).await?;
|
||||
}
|
||||
|
||||
main_transfer.wait().await?;
|
||||
package_data_transfer.wait().await?;
|
||||
let ordering = std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
let main_transfer_size = Counter::new(0, ordering);
|
||||
let package_data_transfer_size = Counter::new(0, ordering);
|
||||
|
||||
let size = tokio::select! {
|
||||
res = async {
|
||||
let (main_size, package_data_size) = try_join!(
|
||||
dir_size(main_transfer_args.0, Some(&main_transfer_size)),
|
||||
dir_size(package_data_transfer_args.0, Some(&package_data_transfer_size))
|
||||
)?;
|
||||
Ok::<_, Error>(main_size + package_data_size)
|
||||
} => { res? },
|
||||
res = async {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: Some(main_transfer_size.load() + package_data_transfer_size.load()),
|
||||
complete: false,
|
||||
}));
|
||||
}
|
||||
} => res,
|
||||
};
|
||||
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: Some(size),
|
||||
complete: false,
|
||||
}));
|
||||
|
||||
let main_transfer_progress = Counter::new(0, ordering);
|
||||
let package_data_transfer_progress = Counter::new(0, ordering);
|
||||
|
||||
tokio::select! {
|
||||
res = async {
|
||||
try_join!(
|
||||
dir_copy(main_transfer_args.0, main_transfer_args.1, Some(&main_transfer_progress)),
|
||||
dir_copy(package_data_transfer_args.0, package_data_transfer_args.1, Some(&package_data_transfer_progress))
|
||||
)?;
|
||||
Ok::<_, Error>(())
|
||||
} => { res? },
|
||||
res = async {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: main_transfer_progress.load() + package_data_transfer_progress.load(),
|
||||
total_bytes: Some(size),
|
||||
complete: false,
|
||||
}));
|
||||
}
|
||||
} => res,
|
||||
}
|
||||
|
||||
let (hostname, tor_addr, root_ca) = setup_init(&ctx, Some(embassy_password)).await?;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::util::{display_none, Invoke};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub const SYSTEMD_UNIT: &'static str = "embassyd";
|
||||
pub const SYSTEMD_UNIT: &'static str = "startd";
|
||||
|
||||
#[command(subcommands(zram))]
|
||||
pub async fn experimental() -> Result<(), Error> {
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::future::Future;
|
||||
use std::io::Cursor;
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::future::{BoxFuture, Fuse};
|
||||
use futures::{AsyncSeek, FutureExt, TryStreamExt};
|
||||
@@ -11,6 +13,8 @@ use nix::unistd::{Gid, Uid};
|
||||
use tokio::io::{
|
||||
duplex, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, DuplexStream, ReadBuf, WriteHalf,
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::{Instant, Sleep};
|
||||
|
||||
use crate::ResultExt;
|
||||
|
||||
@@ -224,6 +228,7 @@ pub async fn copy_and_shutdown<R: AsyncRead + Unpin, W: AsyncWrite + Unpin>(
|
||||
|
||||
pub fn dir_size<'a, P: AsRef<Path> + 'a + Send + Sync>(
|
||||
path: P,
|
||||
ctr: Option<&'a Counter>,
|
||||
) -> BoxFuture<'a, Result<u64, std::io::Error>> {
|
||||
async move {
|
||||
tokio_stream::wrappers::ReadDirStream::new(tokio::fs::read_dir(path.as_ref()).await?)
|
||||
@@ -231,9 +236,12 @@ pub fn dir_size<'a, P: AsRef<Path> + 'a + Send + Sync>(
|
||||
let m = e.metadata().await?;
|
||||
Ok(acc
|
||||
+ if m.is_file() {
|
||||
if let Some(ctr) = ctr {
|
||||
ctr.add(m.len());
|
||||
}
|
||||
m.len()
|
||||
} else if m.is_dir() {
|
||||
dir_size(e.path()).await?
|
||||
dir_size(e.path(), ctr).await?
|
||||
} else {
|
||||
0
|
||||
})
|
||||
@@ -419,9 +427,60 @@ impl<T: AsyncWrite> AsyncWrite for BackTrackingReader<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Counter {
|
||||
atomic: AtomicU64,
|
||||
ordering: std::sync::atomic::Ordering,
|
||||
}
|
||||
impl Counter {
|
||||
pub fn new(init: u64, ordering: std::sync::atomic::Ordering) -> Self {
|
||||
Self {
|
||||
atomic: AtomicU64::new(init),
|
||||
ordering,
|
||||
}
|
||||
}
|
||||
pub fn load(&self) -> u64 {
|
||||
self.atomic.load(self.ordering)
|
||||
}
|
||||
pub fn add(&self, value: u64) {
|
||||
self.atomic.fetch_add(value, self.ordering);
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct CountingReader<'a, R> {
|
||||
ctr: &'a Counter,
|
||||
#[pin]
|
||||
rdr: R,
|
||||
}
|
||||
impl<'a, R> CountingReader<'a, R> {
|
||||
pub fn new(rdr: R, ctr: &'a Counter) -> Self {
|
||||
Self { ctr, rdr }
|
||||
}
|
||||
pub fn into_inner(self) -> R {
|
||||
self.rdr
|
||||
}
|
||||
}
|
||||
impl<'a, R: AsyncRead> AsyncRead for CountingReader<'a, R> {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let this = self.project();
|
||||
let start = buf.filled().len();
|
||||
let res = this.rdr.poll_read(cx, buf);
|
||||
let len = buf.filled().len() - start;
|
||||
if len > 0 {
|
||||
this.ctr.add(len as u64);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + Send + Sync>(
|
||||
src: P0,
|
||||
dst: P1,
|
||||
ctr: Option<&'a Counter>,
|
||||
) -> BoxFuture<'a, Result<(), crate::Error>> {
|
||||
async move {
|
||||
let m = tokio::fs::metadata(&src).await?;
|
||||
@@ -464,23 +523,23 @@ pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + S
|
||||
let dst_path = dst_path.join(e.file_name());
|
||||
if m.is_file() {
|
||||
let len = m.len();
|
||||
let mut dst_file =
|
||||
&mut tokio::fs::File::create(&dst_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
format!("create {}", dst_path.display()),
|
||||
)
|
||||
})?;
|
||||
tokio::io::copy(
|
||||
&mut tokio::fs::File::open(&src_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
format!("open {}", src_path.display()),
|
||||
)
|
||||
})?,
|
||||
&mut dst_file,
|
||||
)
|
||||
.await
|
||||
let mut dst_file = tokio::fs::File::create(&dst_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
format!("create {}", dst_path.display()),
|
||||
)
|
||||
})?;
|
||||
let mut rdr = tokio::fs::File::open(&src_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
format!("open {}", src_path.display()),
|
||||
)
|
||||
})?;
|
||||
if let Some(ctr) = ctr {
|
||||
tokio::io::copy(&mut CountingReader::new(rdr, ctr), &mut dst_file).await
|
||||
} else {
|
||||
tokio::io::copy(&mut rdr, &mut dst_file).await
|
||||
}
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
@@ -508,7 +567,7 @@ pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + S
|
||||
)
|
||||
})?;
|
||||
} else if m.is_dir() {
|
||||
dir_copy(src_path, dst_path).await?;
|
||||
dir_copy(src_path, dst_path, ctr).await?;
|
||||
} else if m.file_type().is_symlink() {
|
||||
tokio::fs::symlink(
|
||||
tokio::fs::read_link(&src_path).await.with_ctx(|_| {
|
||||
@@ -535,3 +594,77 @@ pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + S
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct TimeoutStream<S: AsyncRead + AsyncWrite = TcpStream> {
|
||||
timeout: Duration,
|
||||
#[pin]
|
||||
sleep: Sleep,
|
||||
#[pin]
|
||||
stream: S,
|
||||
}
|
||||
impl<S: AsyncRead + AsyncWrite> TimeoutStream<S> {
|
||||
pub fn new(stream: S, timeout: Duration) -> Self {
|
||||
Self {
|
||||
timeout,
|
||||
sleep: tokio::time::sleep(timeout),
|
||||
stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<S: AsyncRead + AsyncWrite> AsyncRead for TimeoutStream<S> {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
let mut this = self.project();
|
||||
if let std::task::Poll::Ready(_) = this.sleep.as_mut().poll(cx) {
|
||||
return std::task::Poll::Ready(Err(std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
"timed out",
|
||||
)));
|
||||
}
|
||||
let res = this.stream.poll_read(cx, buf);
|
||||
if res.is_ready() {
|
||||
this.sleep.reset(Instant::now() + *this.timeout);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
impl<S: AsyncRead + AsyncWrite> AsyncWrite for TimeoutStream<S> {
|
||||
fn poll_write(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
let mut this = self.project();
|
||||
let res = this.stream.poll_write(cx, buf);
|
||||
if res.is_ready() {
|
||||
this.sleep.reset(Instant::now() + *this.timeout);
|
||||
}
|
||||
res
|
||||
}
|
||||
fn poll_flush(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
let mut this = self.project();
|
||||
let res = this.stream.poll_flush(cx);
|
||||
if res.is_ready() {
|
||||
this.sleep.reset(Instant::now() + *this.timeout);
|
||||
}
|
||||
res
|
||||
}
|
||||
fn poll_shutdown(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
let mut this = self.project();
|
||||
let res = this.stream.poll_shutdown(cx);
|
||||
if res.is_ready() {
|
||||
this.sleep.reset(Instant::now() + *this.timeout);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
41
backend/src/version/v0_3_4_4.rs
Normal file
41
backend/src/version/v0_3_4_4.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
use models::ResultExt;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
|
||||
const V0_3_4_4: emver::Version = emver::Version::new(0, 3, 4, 4);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_4_3::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_4_4
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
let mut tor_addr = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.tor_address()
|
||||
.get_mut(db)
|
||||
.await?;
|
||||
tor_addr
|
||||
.set_scheme("https")
|
||||
.map_err(|_| eyre!("unable to update url scheme to https"))
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?;
|
||||
tor_addr.save(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
19
backend/startd.service
Normal file
19
backend/startd.service
Normal file
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=StartOS Daemon
|
||||
After=network-online.target
|
||||
Requires=network-online.target
|
||||
Wants=avahi-daemon.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=RUST_LOG=startos=debug,js_engine=debug,patch_db=warn
|
||||
ExecStart=/usr/bin/startd
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ManagedOOMPreference=avoid
|
||||
CPUAccounting=true
|
||||
CPUWeight=1000
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1145,7 +1145,14 @@ export const action = {
|
||||
|
||||
async "test-disk-usage"(effects, _input) {
|
||||
const usage = await effects.diskUsage()
|
||||
return usage
|
||||
return {
|
||||
result: {
|
||||
copyable: false,
|
||||
message: `${usage.used} / ${usage.total}`,
|
||||
version: "0",
|
||||
qr: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user