mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
merge from master and fix typescript errors
This commit is contained in:
14
backend/.sqlx/query-b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d.json
generated
Normal file
14
backend/.sqlx/query-b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM network_keys WHERE package = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d"
|
||||
}
|
||||
24
backend/Cargo.lock
generated
24
backend/Cargo.lock
generated
@@ -1906,28 +1906,6 @@ version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||
|
||||
[[package]]
|
||||
name = "git-version"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b0decc02f4636b9ccad390dcbe77b722a77efedfa393caf8379a51d5c61899"
|
||||
dependencies = [
|
||||
"git-version-macro",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git-version-macro"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
@@ -4970,12 +4948,12 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
"divrem",
|
||||
"ed25519 2.2.3",
|
||||
"ed25519-dalek 1.0.1",
|
||||
"ed25519-dalek 2.0.0",
|
||||
"embassy_container_init",
|
||||
"emver",
|
||||
"fd-lock-rs",
|
||||
"futures",
|
||||
"git-version",
|
||||
"gpt",
|
||||
"helpers",
|
||||
"hex",
|
||||
|
||||
@@ -15,6 +15,7 @@ name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.3.5"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
name = "startos"
|
||||
@@ -31,7 +32,7 @@ cli = []
|
||||
daemon = []
|
||||
default = ["cli", "sdk", "daemon", "js_engine"]
|
||||
dev = []
|
||||
podman = []
|
||||
docker = []
|
||||
sdk = []
|
||||
unstable = ["console-subscriber", "tokio/tracing"]
|
||||
|
||||
@@ -66,18 +67,17 @@ divrem = "1.0.0"
|
||||
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
||||
ed25519-dalek = { version = "2.0.0", features = [
|
||||
"serde",
|
||||
"hazmat",
|
||||
"zeroize",
|
||||
"rand_core",
|
||||
"digest",
|
||||
] }
|
||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||
embassy_container_init = { path = "../libs/embassy_container_init" }
|
||||
emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
fd-lock-rs = "0.1.4"
|
||||
futures = "0.3.28"
|
||||
git-version = "0.3.5"
|
||||
gpt = "3.1.0"
|
||||
helpers = { path = "../libs/helpers" }
|
||||
hex = "0.4.3"
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ -z "$OS_ARCH" ]; then
|
||||
>&2 echo '$OS_ARCH is required'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$ARCH" ]; then
|
||||
ARCH=$(uname -m)
|
||||
fi
|
||||
@@ -23,27 +18,17 @@ if tty -s; then
|
||||
fi
|
||||
|
||||
cd ..
|
||||
FLAGS=""
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
if [[ "$ENVIRONMENT" =~ (^|-)podman($|-) ]]; then
|
||||
FLAGS="podman,$FLAGS"
|
||||
fi
|
||||
if [[ "$ENVIRONMENT" =~ (^|-)unstable($|-) ]]; then
|
||||
FLAGS="unstable,$FLAGS"
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable"
|
||||
fi
|
||||
if [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
||||
FLAGS="dev,$FLAGS"
|
||||
fi
|
||||
|
||||
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-arm-cross:aarch64'
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
||||
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-arm-cross:aarch64'
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
||||
|
||||
set +e
|
||||
fail=
|
||||
echo "FLAGS=\"$FLAGS\""
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-gnu-builder sh -c "(cd backend && cargo build --release --features avahi-alias,$FLAGS --locked --target=$ARCH-unknown-linux-gnu)"
|
||||
rust-gnu-builder sh -c "(cd backend && cargo build --release --features avahi-alias,$FEATURES --locked --target=$ARCH-unknown-linux-gnu)"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
|
||||
@@ -11,8 +11,8 @@ fi
|
||||
frontend="../frontend/dist/static"
|
||||
[ -d "$frontend" ] || mkdir -p "$frontend"
|
||||
|
||||
if [ -z "$OS_ARCH" ]; then
|
||||
export OS_ARCH=$(uname -m)
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
export PLATFORM=$(uname -m)
|
||||
fi
|
||||
|
||||
cargo install --path=. --no-default-features --features=js_engine,sdk,cli --locked
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use digest::Digest;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use ed25519_dalek::SecretKey;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
@@ -14,7 +15,7 @@ fn hash_password(password: &str) -> Result<String, Error> {
|
||||
argon2::hash_encoded(
|
||||
password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)
|
||||
}
|
||||
@@ -29,11 +30,11 @@ pub struct AccountInfo {
|
||||
pub root_ca_cert: X509,
|
||||
}
|
||||
impl AccountInfo {
|
||||
pub fn new(password: &str) -> Result<Self, Error> {
|
||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let server_id = generate_id();
|
||||
let hostname = generate_hostname();
|
||||
let root_ca_key = generate_key()?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname)?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||
Ok(Self {
|
||||
server_id,
|
||||
hostname,
|
||||
|
||||
@@ -84,7 +84,7 @@ fn gen_pwd() {
|
||||
argon2::hash_encoded(
|
||||
b"testing1234",
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default()
|
||||
&argon2::Config::rfc9106_low_mem()
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
|
||||
@@ -189,7 +189,7 @@ pub async fn recover_full_embassy(
|
||||
os_backup.account.password = argon2::hash_encoded(
|
||||
embassy_password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use clap::Arg;
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{command, run_cli, Context};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::procedure::js_scripts::ExecuteArgs;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable};
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
@@ -16,6 +12,9 @@ lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
struct DenoContext;
|
||||
impl Context for DenoContext {}
|
||||
|
||||
#[command(subcommands(execute, sandbox))]
|
||||
fn deno_api() -> Result<(), Error> {
|
||||
Ok(())
|
||||
@@ -70,13 +69,11 @@ impl PackageLogger {
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
let filter_layer = EnvFilter::builder()
|
||||
.with_default_directive(
|
||||
format!("{}=info", std::module_path!().split("::").next().unwrap())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.from_env_lossy();
|
||||
let filter_layer = EnvFilter::default().add_directive(
|
||||
format!("{}=warn", std::module_path!().split("::").next().unwrap())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let fmt_layer = fmt::layer().with_writer(std::io::stderr).with_target(true);
|
||||
let journald_layer = tracing_journald::layer()
|
||||
.unwrap()
|
||||
@@ -103,16 +100,8 @@ fn inner_main() -> Result<(), Error> {
|
||||
command: deno_api,
|
||||
app: app => app
|
||||
.name("StartOS Deno Executor")
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
),
|
||||
context: matches => {
|
||||
CliContext::init(matches)?
|
||||
},
|
||||
.version(&**VERSION_STRING),
|
||||
context: _m => DenoContext,
|
||||
exit: |e: RpcError| {
|
||||
match e.data {
|
||||
Some(Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::net::web_server::WebServer;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::sound::CHIME;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
||||
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||
@@ -30,19 +30,19 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Er
|
||||
|
||||
Command::new("ln")
|
||||
.arg("-sf")
|
||||
.arg("/usr/lib/embassy/scripts/fake-apt")
|
||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||
.arg("/usr/local/bin/apt")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
Command::new("ln")
|
||||
.arg("-sf")
|
||||
.arg("/usr/lib/embassy/scripts/fake-apt")
|
||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||
.arg("/usr/local/bin/apt-get")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
Command::new("ln")
|
||||
.arg("-sf")
|
||||
.arg("/usr/lib/embassy/scripts/fake-apt")
|
||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||
.arg("/usr/local/bin/aptitude")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
@@ -177,7 +177,7 @@ async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
|
||||
|
||||
#[instrument(skip_all)]
|
||||
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() {
|
||||
if &*PLATFORM == "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?;
|
||||
crate::sound::SHUTDOWN.play().await?;
|
||||
|
||||
@@ -14,9 +14,8 @@ use rpc_toolkit::command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{PackageId};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||
use crate::Error;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
@@ -105,7 +105,7 @@ where
|
||||
rng: &mut R,
|
||||
timeout: &Option<Duration>,
|
||||
) -> Result<Value, Self::Error> {
|
||||
self.gen_with(self.default_spec().borrow(), rng, timeout)
|
||||
self.gen_with(self.default_spec(), rng, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use serde::Deserialize;
|
||||
use sqlx::postgres::PgConnectOptions;
|
||||
use sqlx::PgPool;
|
||||
use tokio::sync::{broadcast, oneshot, Mutex, RwLock};
|
||||
use tokio::time::Instant;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
@@ -29,7 +30,7 @@ use crate::install::cleanup::{cleanup_failed, uninstall};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::middleware::auth::HashSessionToken;
|
||||
use crate::net::net_controller::NetController;
|
||||
use crate::net::ssl::SslManager;
|
||||
use crate::net::ssl::{root_ca_start_time, SslManager};
|
||||
use crate::net::wifi::WpaCli;
|
||||
use crate::notifications::NotificationManager;
|
||||
use crate::shutdown::Shutdown;
|
||||
@@ -123,6 +124,7 @@ pub struct RpcContextSeed {
|
||||
pub current_secret: Arc<Jwk>,
|
||||
pub client: Client,
|
||||
pub hardware: Hardware,
|
||||
pub start_time: Instant,
|
||||
}
|
||||
|
||||
pub struct Hardware {
|
||||
@@ -158,7 +160,7 @@ impl RpcContext {
|
||||
base.dns_bind
|
||||
.as_deref()
|
||||
.unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]),
|
||||
SslManager::new(&account)?,
|
||||
SslManager::new(&account, root_ca_start_time().await?)?,
|
||||
&account.hostname,
|
||||
&account.key,
|
||||
)
|
||||
@@ -214,6 +216,7 @@ impl RpcContext {
|
||||
.build()
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||
hardware: Hardware { devices, ram },
|
||||
start_time: Instant::now(),
|
||||
});
|
||||
|
||||
let res = Self(seed.clone());
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use emver::VersionRange;
|
||||
use imbl_value::InternedString;
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use isocountry::CountryCode;
|
||||
use itertools::Itertools;
|
||||
@@ -24,6 +25,7 @@ use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::Status;
|
||||
use crate::util::Version;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::{ARCH, PLATFORM};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -40,6 +42,8 @@ impl Database {
|
||||
let lan_address = account.hostname.lan_address().parse().unwrap();
|
||||
Database {
|
||||
server_info: ServerInfo {
|
||||
arch: get_arch(),
|
||||
platform: get_platform(),
|
||||
id: account.server_id.clone(),
|
||||
version: Current::new().semver().into(),
|
||||
hostname: account.hostname.no_dot_host_name(),
|
||||
@@ -55,6 +59,8 @@ impl Database {
|
||||
backup_progress: None,
|
||||
updated: false,
|
||||
update_progress: None,
|
||||
shutting_down: false,
|
||||
restarting: false,
|
||||
},
|
||||
wifi: WifiInfo {
|
||||
ssids: Vec::new(),
|
||||
@@ -77,8 +83,8 @@ impl Database {
|
||||
.iter()
|
||||
.map(|x| format!("{x:X}"))
|
||||
.join(":"),
|
||||
system_start_time: Utc::now().to_rfc3339(),
|
||||
zram: false,
|
||||
ntp_synced: false,
|
||||
zram: true,
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
lan_port_forwards: LanPortForwards::new(),
|
||||
@@ -90,10 +96,22 @@ impl Database {
|
||||
|
||||
pub type DatabaseModel = Model<Database>;
|
||||
|
||||
fn get_arch() -> InternedString {
|
||||
(*ARCH).into()
|
||||
}
|
||||
|
||||
fn get_platform() -> InternedString {
|
||||
(&*PLATFORM).into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct ServerInfo {
|
||||
#[serde(default = "get_arch")]
|
||||
pub arch: InternedString,
|
||||
#[serde(default = "get_platform")]
|
||||
pub platform: InternedString,
|
||||
pub id: String,
|
||||
pub hostname: String,
|
||||
pub version: Version,
|
||||
@@ -112,7 +130,8 @@ pub struct ServerInfo {
|
||||
pub password_hash: String,
|
||||
pub pubkey: String,
|
||||
pub ca_fingerprint: String,
|
||||
pub system_start_time: String,
|
||||
#[serde(default)]
|
||||
pub ntp_synced: bool,
|
||||
#[serde(default)]
|
||||
pub zram: bool,
|
||||
}
|
||||
@@ -152,6 +171,10 @@ pub struct ServerStatus {
|
||||
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
||||
pub updated: bool,
|
||||
pub update_progress: Option<UpdateProgress>,
|
||||
#[serde(default)]
|
||||
pub shutting_down: bool,
|
||||
#[serde(default)]
|
||||
pub restarting: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
|
||||
@@ -23,6 +23,7 @@ pub async fn btrfs_check_repair(logicalname: impl AsRef<Path>) -> Result<Require
|
||||
Command::new("btrfs")
|
||||
.arg("check")
|
||||
.arg("--repair")
|
||||
.arg("--force")
|
||||
.arg(logicalname.as_ref())
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
@@ -84,7 +84,7 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
|
||||
argon2::hash_encoded(
|
||||
password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?,
|
||||
);
|
||||
@@ -134,7 +134,7 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
|
||||
argon2::hash_encoded(
|
||||
new_password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?,
|
||||
);
|
||||
|
||||
@@ -2,13 +2,12 @@ use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use sha2::Sha256;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
use super::{FileSystem, MountType};
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||
@@ -17,7 +16,7 @@ pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||
key: &str,
|
||||
) -> Result<(), Error> {
|
||||
tokio::fs::create_dir_all(dst.as_ref()).await?;
|
||||
let mut ecryptfs = tokio::process::Command::new("mount")
|
||||
tokio::process::Command::new("mount")
|
||||
.arg("-t")
|
||||
.arg("ecryptfs")
|
||||
.arg(src.as_ref())
|
||||
@@ -25,22 +24,9 @@ pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||
.arg("-o")
|
||||
// for more information `man ecryptfs`
|
||||
.arg(format!("key=passphrase:passphrase_passwd={},ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=n,ecryptfs_enable_filename_crypto=y,no_sig_cache", key))
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()?;
|
||||
let mut stdin = ecryptfs.stdin.take().unwrap();
|
||||
let mut stderr = ecryptfs.stderr.take().unwrap();
|
||||
stdin.write_all(b"\n").await?;
|
||||
stdin.flush().await?;
|
||||
stdin.shutdown().await?;
|
||||
drop(stdin);
|
||||
let mut err = String::new();
|
||||
stderr.read_to_string(&mut err).await?;
|
||||
if !ecryptfs.wait().await?.success() {
|
||||
Err(Error::new(eyre!("{}", err), crate::ErrorKind::Filesystem))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
.input(Some(&mut std::io::Cursor::new(b"\n")))
|
||||
.invoke(crate::ErrorKind::Filesystem).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct EcryptFS<EncryptedDir: AsRef<Path>, Key: AsRef<str>> {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
|
||||
use async_compression::tokio::bufread::GzipDecoder;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncWriteExt, BufReader};
|
||||
use tokio::io::{AsyncRead, BufReader};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::disk::fsck::RequiresReboot;
|
||||
@@ -23,7 +22,7 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
||||
if product_name.is_empty() {
|
||||
return Ok(RequiresReboot(false));
|
||||
}
|
||||
let firmware_dir = Path::new("/usr/lib/embassy/firmware").join(&product_name);
|
||||
let firmware_dir = Path::new("/usr/lib/startos/firmware").join(&product_name);
|
||||
if tokio::fs::metadata(&firmware_dir).await.is_ok() {
|
||||
let current_firmware = String::from_utf8(
|
||||
Command::new("dmidecode")
|
||||
@@ -44,36 +43,25 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
||||
let mut firmware_read_dir = tokio::fs::read_dir(&firmware_dir).await?;
|
||||
while let Some(entry) = firmware_read_dir.next_entry().await? {
|
||||
let filename = entry.file_name().to_string_lossy().into_owned();
|
||||
let rdr: Option<Box<dyn AsyncRead + Unpin>> = if filename.ends_with(".rom.gz") {
|
||||
Some(Box::new(GzipDecoder::new(BufReader::new(
|
||||
File::open(entry.path()).await?,
|
||||
))))
|
||||
} else if filename.ends_with(".rom") {
|
||||
Some(Box::new(File::open(entry.path()).await?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let rdr: Option<Box<dyn AsyncRead + Unpin + Send>> =
|
||||
if filename.ends_with(".rom.gz") {
|
||||
Some(Box::new(GzipDecoder::new(BufReader::new(
|
||||
File::open(entry.path()).await?,
|
||||
))))
|
||||
} else if filename.ends_with(".rom") {
|
||||
Some(Box::new(File::open(entry.path()).await?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(mut rdr) = rdr {
|
||||
let mut flashrom = Command::new("flashrom")
|
||||
Command::new("flashrom")
|
||||
.arg("-p")
|
||||
.arg("internal")
|
||||
.arg("-w-")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?;
|
||||
let mut rom_dest = flashrom.stdin.take().or_not_found("stdin")?;
|
||||
tokio::io::copy(&mut rdr, &mut rom_dest).await?;
|
||||
rom_dest.flush().await?;
|
||||
rom_dest.shutdown().await?;
|
||||
drop(rom_dest);
|
||||
let o = flashrom.wait_with_output().await?;
|
||||
if !o.status.success() {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", std::str::from_utf8(&o.stderr)?),
|
||||
ErrorKind::Firmware,
|
||||
));
|
||||
} else {
|
||||
return Ok(RequiresReboot(true));
|
||||
}
|
||||
.input(Some(&mut rdr))
|
||||
.invoke(ErrorKind::Firmware)
|
||||
.await?;
|
||||
return Ok(RequiresReboot(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
@@ -19,7 +19,6 @@ use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::prelude::*;
|
||||
use crate::sound::BEP;
|
||||
use crate::system::time;
|
||||
use crate::util::cpupower::{
|
||||
current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE,
|
||||
};
|
||||
@@ -255,6 +254,17 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
}
|
||||
}
|
||||
crate::disk::mount::util::bind(&log_dir, "/var/log/journal", false).await?;
|
||||
match Command::new("chattr")
|
||||
.arg("-R")
|
||||
.arg("+C")
|
||||
.arg("/var/log/journal")
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) if e.source.to_string().contains("Operation not supported") => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
Command::new("systemctl")
|
||||
.arg("restart")
|
||||
.arg("systemd-journald")
|
||||
@@ -263,6 +273,9 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
tracing::info!("Mounted Logs");
|
||||
|
||||
let tmp_dir = cfg.datadir().join("package-data/tmp");
|
||||
if should_rebuild && tokio::fs::metadata(&tmp_dir).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmp_dir).await?;
|
||||
}
|
||||
if tokio::fs::metadata(&tmp_dir).await.is_err() {
|
||||
tokio::fs::create_dir_all(&tmp_dir).await?;
|
||||
}
|
||||
@@ -275,9 +288,6 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
.datadir()
|
||||
.join(format!("package-data/tmp/{CONTAINER_TOOL}"));
|
||||
let tmp_docker_exists = tokio::fs::metadata(&tmp_docker).await.is_ok();
|
||||
if should_rebuild && tmp_docker_exists {
|
||||
tokio::fs::remove_dir_all(&tmp_docker).await?;
|
||||
}
|
||||
if CONTAINER_TOOL == "docker" {
|
||||
Command::new("systemctl")
|
||||
.arg("stop")
|
||||
@@ -309,7 +319,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
}
|
||||
|
||||
tracing::info!("Loading System Docker Images");
|
||||
crate::install::load_images("/usr/lib/embassy/system-images").await?;
|
||||
crate::install::load_images("/usr/lib/startos/system-images").await?;
|
||||
tracing::info!("Loaded System Docker Images");
|
||||
|
||||
tracing::info!("Loading Package Docker Images");
|
||||
@@ -361,15 +371,28 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut warn_time_not_synced = true;
|
||||
for _ in 0..60 {
|
||||
let mut time_not_synced = true;
|
||||
let mut not_made_progress = 0u32;
|
||||
for _ in 0..1800 {
|
||||
if check_time_is_synchronized().await? {
|
||||
warn_time_not_synced = false;
|
||||
time_not_synced = false;
|
||||
break;
|
||||
}
|
||||
let t = SystemTime::now();
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
if t.elapsed()
|
||||
.map(|t| t > Duration::from_secs_f64(1.1))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
not_made_progress = 0;
|
||||
} else {
|
||||
not_made_progress += 1;
|
||||
}
|
||||
if not_made_progress > 30 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if warn_time_not_synced {
|
||||
if time_not_synced {
|
||||
tracing::warn!("Timed out waiting for system time to synchronize");
|
||||
} else {
|
||||
tracing::info!("Syncronized system clock");
|
||||
@@ -383,9 +406,24 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
updated: false,
|
||||
update_progress: None,
|
||||
backup_progress: None,
|
||||
shutting_down: false,
|
||||
restarting: false,
|
||||
};
|
||||
|
||||
server_info.system_start_time = time().await?;
|
||||
server_info.ntp_synced = if time_not_synced {
|
||||
let db = db.clone();
|
||||
tokio::spawn(async move {
|
||||
while !check_time_is_synchronized().await.unwrap() {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
}
|
||||
db.mutate(|v| v.as_server_info_mut().as_ntp_synced_mut().ser(&true))
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
db.mutate(|v| {
|
||||
v.as_server_info_mut().ser(&server_info)?;
|
||||
|
||||
@@ -173,7 +173,7 @@ where
|
||||
);
|
||||
cleanup(ctx, id, &version).await?;
|
||||
cleanup_folder(volume_dir, Arc::new(dependents_paths)).await;
|
||||
remove_tor_keys(secrets, id).await?;
|
||||
remove_network_keys(secrets, id).await?;
|
||||
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
@@ -188,12 +188,15 @@ where
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn remove_tor_keys<Ex>(secrets: &mut Ex, id: &PackageId) -> Result<(), Error>
|
||||
pub async fn remove_network_keys<Ex>(secrets: &mut Ex, id: &PackageId) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||
{
|
||||
sqlx::query!("DELETE FROM network_keys WHERE package = $1", &*id)
|
||||
.execute(&mut *secrets)
|
||||
.await?;
|
||||
sqlx::query!("DELETE FROM tor WHERE package = $1", &*id)
|
||||
.execute(secrets)
|
||||
.execute(&mut *secrets)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
|
||||
use std::io::SeekFrom;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -49,9 +48,9 @@ use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::reader::S9pkReader;
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::util::docker::CONTAINER_TOOL;
|
||||
use crate::util::io::{copy_and_shutdown, response_to_reader};
|
||||
use crate::util::io::response_to_reader;
|
||||
use crate::util::serde::{display_serializable, Port};
|
||||
use crate::util::{display_none, AsyncFileExt, Version};
|
||||
use crate::util::{display_none, AsyncFileExt, Invoke, Version};
|
||||
use crate::volume::{asset_dir, script_dir};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
@@ -838,15 +837,15 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
||||
None
|
||||
};
|
||||
|
||||
let icon_path = if let Some(marketplace_url) = &marketplace_url {
|
||||
if let Some(manifest) = &manifest {
|
||||
let dir = ctx
|
||||
.datadir
|
||||
.join(PKG_PUBLIC_DIR)
|
||||
.join(&manifest.id)
|
||||
.join(manifest.version.as_str());
|
||||
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type()));
|
||||
if tokio::fs::metadata(&icon_path).await.is_err() {
|
||||
let icon_path = if let Some(manifest) = &manifest {
|
||||
let dir = ctx
|
||||
.datadir
|
||||
.join(PKG_PUBLIC_DIR)
|
||||
.join(&manifest.id)
|
||||
.join(manifest.version.as_str());
|
||||
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type()));
|
||||
if tokio::fs::metadata(&icon_path).await.is_err() {
|
||||
if let Some(marketplace_url) = &marketplace_url {
|
||||
tokio::fs::create_dir_all(&dir).await?;
|
||||
let icon = ctx
|
||||
.client
|
||||
@@ -864,10 +863,12 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
||||
let mut dst = File::create(&icon_path).await?;
|
||||
tokio::io::copy(&mut response_to_reader(icon), &mut dst).await?;
|
||||
dst.sync_all().await?;
|
||||
Some(icon_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(icon_path)
|
||||
} else {
|
||||
None
|
||||
Some(icon_path)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -951,32 +952,11 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
||||
tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version);
|
||||
progress
|
||||
.track_read_during(ctx.db.clone(), pkg_id, || async {
|
||||
let mut load = Command::new(CONTAINER_TOOL)
|
||||
Command::new(CONTAINER_TOOL)
|
||||
.arg("load")
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
let load_in = load.stdin.take().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Could not write to stdin of docker load"),
|
||||
crate::ErrorKind::Docker,
|
||||
)
|
||||
})?;
|
||||
let mut docker_rdr = rdr.docker_images().await?;
|
||||
copy_and_shutdown(&mut docker_rdr, load_in).await?;
|
||||
let res = load.wait_with_output().await?;
|
||||
if !res.status.success() {
|
||||
Err(Error::new(
|
||||
eyre!(
|
||||
"{}",
|
||||
String::from_utf8(res.stderr)
|
||||
.unwrap_or_else(|e| format!("Could not parse stderr: {}", e))
|
||||
),
|
||||
crate::ErrorKind::Docker,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
.input(Some(&mut rdr.docker_images().await?))
|
||||
.invoke(ErrorKind::Docker)
|
||||
.await
|
||||
})
|
||||
.await?;
|
||||
tracing::info!("Install {}@{}: Unpacked Docker Images", pkg_id, version,);
|
||||
@@ -1273,57 +1253,36 @@ pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
|
||||
let path = entry.path();
|
||||
let ext = path.extension().and_then(|ext| ext.to_str());
|
||||
if ext == Some("tar") || ext == Some("s9pk") {
|
||||
let mut load = Command::new(CONTAINER_TOOL)
|
||||
.arg("load")
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
let load_in = load.stdin.take().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Could not write to stdin of docker load"),
|
||||
crate::ErrorKind::Docker,
|
||||
)
|
||||
})?;
|
||||
match ext {
|
||||
Some("tar") => {
|
||||
copy_and_shutdown(&mut File::open(&path).await?, load_in)
|
||||
.await?
|
||||
}
|
||||
Some("s9pk") => match async {
|
||||
let mut reader = S9pkReader::open(&path, true).await?;
|
||||
copy_and_shutdown(&mut reader.docker_images().await?, load_in)
|
||||
.await?;
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Error loading docker images from s9pk: {e}"
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
return Ok(());
|
||||
if let Err(e) = async {
|
||||
match ext {
|
||||
Some("tar") => {
|
||||
Command::new(CONTAINER_TOOL)
|
||||
.arg("load")
|
||||
.input(Some(&mut File::open(&path).await?))
|
||||
.invoke(ErrorKind::Docker)
|
||||
.await
|
||||
}
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let res = load.wait_with_output().await?;
|
||||
if !res.status.success() {
|
||||
Err(Error::new(
|
||||
eyre!(
|
||||
"{}",
|
||||
String::from_utf8(res.stderr).unwrap_or_else(|e| format!(
|
||||
"Could not parse stderr: {}",
|
||||
e
|
||||
))
|
||||
),
|
||||
crate::ErrorKind::Docker,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
Some("s9pk") => {
|
||||
Command::new(CONTAINER_TOOL)
|
||||
.arg("load")
|
||||
.input(Some(
|
||||
&mut S9pkReader::open(&path, true)
|
||||
.await?
|
||||
.docker_images()
|
||||
.await?,
|
||||
))
|
||||
.invoke(ErrorKind::Docker)
|
||||
.await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error loading docker images from s9pk: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,12 +5,21 @@ pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
|
||||
pub const BUFFER_SIZE: usize = 1024;
|
||||
pub const HOST_IP: [u8; 4] = [172, 18, 0, 1];
|
||||
pub const TARGET: &str = current_platform::CURRENT_PLATFORM;
|
||||
pub const OS_ARCH: &str = env!("OS_ARCH");
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref ARCH: &'static str = {
|
||||
let (arch, _) = TARGET.split_once("-").unwrap();
|
||||
arch
|
||||
};
|
||||
pub static ref PLATFORM: String = {
|
||||
if let Ok(platform) = std::fs::read_to_string("/usr/lib/startos/PLATFORM.txt") {
|
||||
platform
|
||||
} else {
|
||||
ARCH.to_string()
|
||||
}
|
||||
};
|
||||
pub static ref SOURCE_DATE: SystemTime = {
|
||||
std::fs::metadata(std::env::current_exe().unwrap()).unwrap().modified().unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
pub mod account;
|
||||
@@ -56,6 +65,8 @@ pub mod util;
|
||||
pub mod version;
|
||||
pub mod volume;
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub use config::Config;
|
||||
pub use error::{Error, ErrorKind, ResultExt};
|
||||
use rpc_toolkit::command;
|
||||
|
||||
@@ -136,7 +136,13 @@ pub struct LogEntry {
|
||||
}
|
||||
impl std::fmt::Display for LogEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.timestamp, self.message)
|
||||
write!(
|
||||
f,
|
||||
"{} {}",
|
||||
self.timestamp
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
|
||||
self.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +151,7 @@ pub struct JournalctlEntry {
|
||||
#[serde(rename = "__REALTIME_TIMESTAMP")]
|
||||
pub timestamp: String,
|
||||
#[serde(rename = "MESSAGE")]
|
||||
#[serde(deserialize_with = "deserialize_string_or_utf8_array")]
|
||||
#[serde(deserialize_with = "deserialize_log_message")]
|
||||
pub message: String,
|
||||
#[serde(rename = "__CURSOR")]
|
||||
pub cursor: String,
|
||||
@@ -164,7 +170,7 @@ impl JournalctlEntry {
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_string_or_utf8_array<'de, D: serde::de::Deserializer<'de>>(
|
||||
fn deserialize_log_message<'de, D: serde::de::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<String, D::Error> {
|
||||
struct Visitor;
|
||||
@@ -177,13 +183,7 @@ fn deserialize_string_or_utf8_array<'de, D: serde::de::Deserializer<'de>>(
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v.to_owned())
|
||||
}
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v)
|
||||
Ok(v.trim().to_owned())
|
||||
}
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
@@ -201,6 +201,7 @@ fn deserialize_string_or_utf8_array<'de, D: serde::de::Deserializer<'de>>(
|
||||
.flatten()
|
||||
.collect::<Result<Vec<u8>, _>>()?,
|
||||
)
|
||||
.map(|s| s.trim().to_owned())
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
@@ -374,12 +375,12 @@ pub async fn journalctl(
|
||||
cmd.arg(format!("_COMM={}", SYSTEM_UNIT));
|
||||
}
|
||||
LogSource::Container(id) => {
|
||||
#[cfg(feature = "podman")]
|
||||
#[cfg(not(feature = "docker"))]
|
||||
cmd.arg(format!(
|
||||
"SYSLOG_IDENTIFIER={}",
|
||||
DockerProcedure::container_name(&id, None)
|
||||
));
|
||||
#[cfg(not(feature = "podman"))]
|
||||
#[cfg(feature = "docker")]
|
||||
cmd.arg(format!(
|
||||
"CONTAINER_NAME={}",
|
||||
DockerProcedure::container_name(&id, None)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use models::ErrorKind;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::procedure::docker::DockerProcedure;
|
||||
use crate::procedure::PackageProcedure;
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
use crate::util::docker::stop_container;
|
||||
use crate::Error;
|
||||
@@ -16,11 +18,13 @@ impl ManagerSeed {
|
||||
pub async fn stop_container(&self) -> Result<(), Error> {
|
||||
match stop_container(
|
||||
&self.container_name,
|
||||
self.manifest
|
||||
.containers
|
||||
.as_ref()
|
||||
.and_then(|c| c.main.sigterm_timeout)
|
||||
.map(|d| *d),
|
||||
match &self.manifest.main {
|
||||
PackageProcedure::Docker(DockerProcedure {
|
||||
sigterm_timeout: Some(sigterm_timeout),
|
||||
..
|
||||
}) => Some(**sigterm_timeout),
|
||||
_ => None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -13,7 +13,8 @@ pub fn pbkdf2(password: impl AsRef<[u8]>, salt: impl AsRef<[u8]>) -> CipherKey<A
|
||||
salt.as_ref(),
|
||||
1000,
|
||||
aeskey.as_mut_slice(),
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
aeskey
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use futures::FutureExt;
|
||||
use libc::time_t;
|
||||
use openssl::asn1::{Asn1Integer, Asn1Time};
|
||||
use openssl::bn::{BigNum, MsbOption};
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
@@ -20,12 +21,20 @@ use tracing::instrument;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::context::RpcContext;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::check_time_is_synchronized;
|
||||
use crate::net::dhcp::ips;
|
||||
use crate::net::keys::{Key, KeyInfo};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
use crate::{Error, ErrorKind, ResultExt, SOURCE_DATE};
|
||||
|
||||
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
||||
|
||||
fn unix_time(time: SystemTime) -> time_t {
|
||||
time.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as time_t)
|
||||
.or_else(|_| UNIX_EPOCH.elapsed().map(|d| -(d.as_secs() as time_t)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CertPair {
|
||||
pub ed25519: X509,
|
||||
@@ -55,9 +64,13 @@ impl CertPair {
|
||||
}),
|
||||
);
|
||||
if cert
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
.not_before()
|
||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
||||
== Ordering::Less
|
||||
&& cert
|
||||
.not_after()
|
||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||
== Ordering::Greater
|
||||
&& ips.is_superset(&ip)
|
||||
{
|
||||
return Ok(cert.clone());
|
||||
@@ -80,6 +93,14 @@ impl CertPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
|
||||
Ok(if check_time_is_synchronized().await? {
|
||||
SystemTime::now()
|
||||
} else {
|
||||
*SOURCE_DATE
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SslManager {
|
||||
hostname: Hostname,
|
||||
@@ -89,9 +110,13 @@ pub struct SslManager {
|
||||
cert_cache: RwLock<BTreeMap<Key, CertPair>>,
|
||||
}
|
||||
impl SslManager {
|
||||
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
||||
pub fn new(account: &AccountInfo, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let int_key = generate_key()?;
|
||||
let int_cert = make_int_cert((&account.root_ca_key, &account.root_ca_cert), &int_key)?;
|
||||
let int_cert = make_int_cert(
|
||||
(&account.root_ca_key, &account.root_ca_cert),
|
||||
&int_key,
|
||||
start_time,
|
||||
)?;
|
||||
Ok(Self {
|
||||
hostname: account.hostname.clone(),
|
||||
root_cert: account.root_ca_cert.clone(),
|
||||
@@ -160,14 +185,20 @@ pub fn generate_key() -> Result<PKey<Private>, Error> {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X509, Error> {
|
||||
pub fn make_root_cert(
|
||||
root_key: &PKey<Private>,
|
||||
hostname: &Hostname,
|
||||
start_time: SystemTime,
|
||||
) -> Result<X509, Error> {
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let embargo = Asn1Time::days_from_now(0)?;
|
||||
let unix_start_time = unix_time(start_time);
|
||||
|
||||
let embargo = Asn1Time::from_unix(unix_start_time - 86400)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
let expiration = Asn1Time::days_from_now(3650)?;
|
||||
let expiration = Asn1Time::from_unix(unix_start_time + (10 * 364 * 86400))?;
|
||||
builder.set_not_after(&expiration)?;
|
||||
|
||||
builder.set_serial_number(&*rand_serial()?)?;
|
||||
@@ -214,14 +245,17 @@ pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X
|
||||
pub fn make_int_cert(
|
||||
signer: (&PKey<Private>, &X509),
|
||||
applicant: &PKey<Private>,
|
||||
start_time: SystemTime,
|
||||
) -> Result<X509, Error> {
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let embargo = Asn1Time::days_from_now(0)?;
|
||||
let unix_start_time = unix_time(start_time);
|
||||
|
||||
let embargo = Asn1Time::from_unix(unix_start_time - 86400)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
let expiration = Asn1Time::days_from_now(3650)?;
|
||||
let expiration = Asn1Time::from_unix(unix_start_time + (10 * 364 * 86400))?;
|
||||
builder.set_not_after(&expiration)?;
|
||||
|
||||
builder.set_serial_number(&*rand_serial()?)?;
|
||||
@@ -344,17 +378,10 @@ pub fn make_leaf_cert(
|
||||
let mut builder = X509Builder::new()?;
|
||||
builder.set_version(CERTIFICATE_VERSION)?;
|
||||
|
||||
let embargo = Asn1Time::from_unix(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.or_else(|_| UNIX_EPOCH.elapsed().map(|d| -(d.as_secs() as i64)))
|
||||
.unwrap_or_default()
|
||||
- 86400,
|
||||
)?;
|
||||
let embargo = Asn1Time::from_unix(unix_time(SystemTime::now()) - 86400)?;
|
||||
builder.set_not_before(&embargo)?;
|
||||
|
||||
// Google Apple and Mozilla reject certificate horizons longer than 397 days
|
||||
// Google Apple and Mozilla reject certificate horizons longer than 398 days
|
||||
// https://techbeacon.com/security/google-apple-mozilla-enforce-1-year-max-security-certifications
|
||||
let expiration = Asn1Time::days_from_now(397)?;
|
||||
builder.set_not_after(&expiration)?;
|
||||
|
||||
@@ -272,7 +272,14 @@ impl VHostServer {
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::OpenSsl)?;
|
||||
let mut tls_stream =
|
||||
mid.into_stream(Arc::new(cfg)).await?;
|
||||
match mid.into_stream(Arc::new(cfg)).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::trace!( "VHostController: failed to accept TLS connection on port {port}: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
@@ -287,7 +294,14 @@ impl VHostServer {
|
||||
cfg.alpn_protocols.push(proto.into());
|
||||
}
|
||||
let mut tls_stream =
|
||||
mid.into_stream(Arc::new(cfg)).await?;
|
||||
match mid.into_stream(Arc::new(cfg)).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::trace!( "VHostController: failed to accept TLS connection on port {port}: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
@@ -298,7 +312,14 @@ impl VHostServer {
|
||||
Err(AlpnInfo::Specified(alpn)) => {
|
||||
cfg.alpn_protocols = alpn;
|
||||
let mut tls_stream =
|
||||
mid.into_stream(Arc::new(cfg)).await?;
|
||||
match mid.into_stream(Arc::new(cfg)).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::trace!( "VHostController: failed to accept TLS connection on port {port}: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
tls_stream.get_mut().0.stop_buffering();
|
||||
tokio::io::copy_bidirectional(
|
||||
&mut tls_stream,
|
||||
@@ -308,10 +329,12 @@ impl VHostServer {
|
||||
}
|
||||
}
|
||||
.map_or_else(
|
||||
|e| match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => Ok(()),
|
||||
|e| {
|
||||
use std::io::ErrorKind as E;
|
||||
match e.kind() {
|
||||
E::UnexpectedEof | E::BrokenPipe | E::ConnectionAborted | E::ConnectionReset | E::ConnectionRefused | E::TimedOut | E::Interrupted | E::NotConnected => Ok(()),
|
||||
_ => Err(e),
|
||||
},
|
||||
}},
|
||||
|_| Ok(()),
|
||||
)?;
|
||||
} else {
|
||||
@@ -327,8 +350,10 @@ impl VHostServer {
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error in VHostController on port {port}: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
tracing::trace!(
|
||||
"VHostController: failed to accept connection on port {port}: {e}"
|
||||
);
|
||||
tracing::trace!("{e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ use std::os::unix::prelude::FileTypeExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use async_stream::stream;
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::Report;
|
||||
use futures::future::{BoxFuture, Either as EitherFuture};
|
||||
use futures::{FutureExt, TryStreamExt};
|
||||
use helpers::{NonDetachingJoinHandle, UnixRpcClient};
|
||||
@@ -396,7 +394,7 @@ impl DockerProcedure {
|
||||
|
||||
cmd.arg("exec");
|
||||
|
||||
cmd.args(self.docker_args_inject(pkg_id).await?);
|
||||
cmd.args(self.docker_args_inject(pkg_id));
|
||||
let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) {
|
||||
cmd.stdin(std::process::Stdio::piped());
|
||||
Some(format.to_vec(input)?)
|
||||
@@ -756,7 +754,7 @@ impl DockerProcedure {
|
||||
+ self.args.len(), // [ARG...]
|
||||
)
|
||||
}
|
||||
async fn docker_args_inject(&self, pkg_id: &PackageId) -> Result<Vec<Cow<'_, OsStr>>, Error> {
|
||||
fn docker_args_inject(&self, pkg_id: &PackageId) -> Vec<Cow<'_, OsStr>> {
|
||||
let mut res = self.new_docker_args();
|
||||
if let Some(shm_size_mb) = self.shm_size_mb {
|
||||
res.push(OsStr::new("--shm-size").into());
|
||||
@@ -769,7 +767,7 @@ impl DockerProcedure {
|
||||
|
||||
res.extend(self.args.iter().map(|s| OsStr::new(s).into()));
|
||||
|
||||
Ok(res)
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,7 +811,7 @@ impl LongRunning {
|
||||
socket_path: &Path,
|
||||
) -> Result<tokio::process::Command, Error> {
|
||||
const INIT_EXEC: &str = "/start9/bin/embassy_container_init";
|
||||
const BIND_LOCATION: &str = "/usr/lib/embassy/container/";
|
||||
const BIND_LOCATION: &str = "/usr/lib/startos/container/";
|
||||
tracing::trace!("setup_long_running_docker_cmd");
|
||||
|
||||
remove_container(container_name, true).await?;
|
||||
@@ -892,23 +890,12 @@ async fn buf_reader_to_lines(
|
||||
reader: impl AsyncBufRead + Unpin,
|
||||
limit: impl Into<Option<usize>>,
|
||||
) -> Result<Vec<String>, Error> {
|
||||
let lines = stream! {
|
||||
let mut lines = reader.lines();
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
yield Ok::<_, Report>(line);
|
||||
}
|
||||
};
|
||||
let output: RingVec<String> = lines
|
||||
.try_fold(
|
||||
RingVec::new(limit.into().unwrap_or(1000)),
|
||||
|mut acc, line| async move {
|
||||
acc.push(line);
|
||||
Ok(acc)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Unknown)?;
|
||||
let output: Vec<String> = output.value.into_iter().collect();
|
||||
let mut lines = reader.lines();
|
||||
let mut answer = RingVec::new(limit.into().unwrap_or(1000));
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
answer.push(line);
|
||||
}
|
||||
let output: Vec<String> = answer.value.into_iter().collect();
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
@@ -973,4 +960,11 @@ mod tests {
|
||||
assert_eq!(CAPACITY_IN, ring.value.capacity());
|
||||
assert_eq!(CAPACITY_IN, ring.value.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tests_buf_reader_to_lines() {
|
||||
let mut reader = BufReader::new("hello\nworld\n".as_bytes());
|
||||
let lines = futures::executor::block_on(buf_reader_to_lines(&mut reader, None)).unwrap();
|
||||
assert_eq!(lines, vec!["hello", "world"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use embassy_container_init::ProcessGroupId;
|
||||
use helpers::UnixRpcClient;
|
||||
pub use js_engine::JsError;
|
||||
@@ -19,8 +15,8 @@ use tracing::instrument;
|
||||
use super::ProcedureName;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::io::to_json_async_writer;
|
||||
use crate::util::Version;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{Invoke, Version};
|
||||
use crate::volume::Volumes;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -85,45 +81,23 @@ impl JsProcedure {
|
||||
_gid: ProcessGroupId,
|
||||
_rpc_client: Option<Arc<UnixRpcClient>>,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
let runner_argument = ExecuteArgs {
|
||||
procedure: self.clone(),
|
||||
directory: directory.clone(),
|
||||
pkg_id: pkg_id.clone(),
|
||||
pkg_version: pkg_version.clone(),
|
||||
name,
|
||||
volumes: volumes.clone(),
|
||||
input: input.and_then(|x| serde_json::to_value(x).ok()),
|
||||
};
|
||||
let mut runner = Command::new("start-deno")
|
||||
Command::new("start-deno")
|
||||
.arg("execute")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
to_json_async_writer(
|
||||
&mut runner.stdin.take().or_not_found("stdin")?,
|
||||
&runner_argument,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let res = if let Some(timeout) = timeout {
|
||||
tokio::time::timeout(timeout, runner.wait_with_output())
|
||||
.await
|
||||
.with_kind(ErrorKind::Timeout)??
|
||||
} else {
|
||||
runner.wait_with_output().await?
|
||||
};
|
||||
|
||||
if res.status.success() {
|
||||
serde_json::from_str::<Result<O, (i32, String)>>(std::str::from_utf8(&res.stdout)?)
|
||||
.with_kind(ErrorKind::Deserialization)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{}", String::from_utf8(res.stderr)?),
|
||||
ErrorKind::Javascript,
|
||||
))
|
||||
}
|
||||
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
|
||||
&ExecuteArgs {
|
||||
procedure: self.clone(),
|
||||
directory: directory.clone(),
|
||||
pkg_id: pkg_id.clone(),
|
||||
pkg_version: pkg_version.clone(),
|
||||
name,
|
||||
volumes: volumes.clone(),
|
||||
input: input.and_then(|x| serde_json::to_value(x).ok()),
|
||||
},
|
||||
)?)))
|
||||
.timeout(timeout)
|
||||
.invoke(ErrorKind::Javascript)
|
||||
.await
|
||||
.and_then(|res| IoFormat::Json.from_slice(&res))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@@ -137,45 +111,23 @@ impl JsProcedure {
|
||||
timeout: Option<Duration>,
|
||||
name: ProcedureName,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
let runner_argument = ExecuteArgs {
|
||||
procedure: self.clone(),
|
||||
directory: directory.clone(),
|
||||
pkg_id: pkg_id.clone(),
|
||||
pkg_version: pkg_version.clone(),
|
||||
name,
|
||||
volumes: volumes.clone(),
|
||||
input: input.and_then(|x| serde_json::to_value(x).ok()),
|
||||
};
|
||||
let mut runner = Command::new("start-deno")
|
||||
Command::new("start-deno")
|
||||
.arg("sandbox")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
to_json_async_writer(
|
||||
&mut runner.stdin.take().or_not_found("stdin")?,
|
||||
&runner_argument,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let res = if let Some(timeout) = timeout {
|
||||
tokio::time::timeout(timeout, runner.wait_with_output())
|
||||
.await
|
||||
.with_kind(ErrorKind::Timeout)??
|
||||
} else {
|
||||
runner.wait_with_output().await?
|
||||
};
|
||||
|
||||
if res.status.success() {
|
||||
serde_json::from_str::<Result<O, (i32, String)>>(std::str::from_utf8(&res.stdout)?)
|
||||
.with_kind(ErrorKind::Deserialization)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{}", String::from_utf8(res.stderr)?),
|
||||
ErrorKind::Javascript,
|
||||
))
|
||||
}
|
||||
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
|
||||
&ExecuteArgs {
|
||||
procedure: self.clone(),
|
||||
directory: directory.clone(),
|
||||
pkg_id: pkg_id.clone(),
|
||||
pkg_version: pkg_version.clone(),
|
||||
name,
|
||||
volumes: volumes.clone(),
|
||||
input: input.and_then(|x| serde_json::to_value(x).ok()),
|
||||
},
|
||||
)?)))
|
||||
.timeout(timeout)
|
||||
.invoke(ErrorKind::Javascript)
|
||||
.await
|
||||
.and_then(|res| IoFormat::Json.from_slice(&res))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
|
||||
@@ -172,7 +172,13 @@ impl<'de> Deserialize<'de> for NoOutput {
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let _ = Value::deserialize(deserializer)?;
|
||||
let _ = Value::deserialize(deserializer);
|
||||
Ok(NoOutput)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deser_no_output() {
|
||||
serde_json::from_str::<NoOutput>("").unwrap();
|
||||
serde_json::from_str::<Result<NoOutput, NoOutput>>("{\"Ok\": null}").unwrap();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub fn with_query_params(ctx: RpcContext, mut url: Url) -> Url {
|
||||
"os.compat",
|
||||
&crate::version::Current::new().compat().to_string(),
|
||||
)
|
||||
.append_pair("os.arch", crate::OS_ARCH)
|
||||
.append_pair("os.arch", &*crate::PLATFORM)
|
||||
.append_pair("hardware.arch", &*crate::ARCH)
|
||||
.append_pair("hardware.ram", &ctx.hardware.ram.to_string());
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::{init, InitResult};
|
||||
use crate::middleware::encrypt::EncryptedWire;
|
||||
use crate::net::ssl::root_ca_start_time;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::{dir_copy, dir_size, Counter};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
@@ -378,7 +379,7 @@ async fn fresh_setup(
|
||||
ctx: &SetupContext,
|
||||
embassy_password: &str,
|
||||
) -> Result<(Hostname, OnionAddressV3, X509), Error> {
|
||||
let account = AccountInfo::new(embassy_password)?;
|
||||
let account = AccountInfo::new(embassy_password, root_ca_start_time().await?)?;
|
||||
let sqlite_pool = ctx.secret_store().await?;
|
||||
account.save(&sqlite_pool).await?;
|
||||
sqlite_pool.close().await;
|
||||
|
||||
@@ -6,10 +6,11 @@ use rpc_toolkit::command;
|
||||
use crate::context::RpcContext;
|
||||
use crate::disk::main::export;
|
||||
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
|
||||
use crate::prelude::*;
|
||||
use crate::sound::SHUTDOWN;
|
||||
use crate::util::docker::CONTAINER_TOOL;
|
||||
use crate::util::{display_none, Invoke};
|
||||
use crate::{Error, OS_ARCH};
|
||||
use crate::PLATFORM;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Shutdown {
|
||||
@@ -60,7 +61,7 @@ impl Shutdown {
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
if OS_ARCH != "raspberrypi" || self.restart {
|
||||
if &*PLATFORM != "raspberrypi" || self.restart {
|
||||
if let Err(e) = SHUTDOWN.play().await {
|
||||
tracing::error!("Error Playing Shutdown Song: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
@@ -68,7 +69,7 @@ impl Shutdown {
|
||||
}
|
||||
});
|
||||
drop(rt);
|
||||
if OS_ARCH == "raspberrypi" {
|
||||
if &*PLATFORM == "raspberrypi" {
|
||||
if !self.restart {
|
||||
std::fs::write(STANDBY_MODE_PATH, "").unwrap();
|
||||
Command::new("sync").spawn().unwrap().wait().unwrap();
|
||||
@@ -90,6 +91,14 @@ impl Shutdown {
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_shutting_down_mut()
|
||||
.ser(&true)
|
||||
})
|
||||
.await?;
|
||||
ctx.shutdown
|
||||
.send(Some(Shutdown {
|
||||
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
|
||||
@@ -102,6 +111,14 @@ pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_restarting_mut()
|
||||
.ser(&true)
|
||||
})
|
||||
.await?;
|
||||
ctx.shutdown
|
||||
.send(Some(Shutdown {
|
||||
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use chrono::Utc;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::command;
|
||||
@@ -84,9 +85,65 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn time() -> Result<String, Error> {
|
||||
Ok(Utc::now().to_rfc3339())
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TimeInfo {
|
||||
now: String,
|
||||
uptime: u64,
|
||||
}
|
||||
|
||||
fn display_time(arg: TimeInfo, matches: &ArgMatches) {
|
||||
use std::fmt::Write;
|
||||
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
return display_serializable(arg, matches);
|
||||
}
|
||||
|
||||
let days = arg.uptime / (24 * 60 * 60);
|
||||
let days_s = arg.uptime % (24 * 60 * 60);
|
||||
let hours = days_s / (60 * 60);
|
||||
let hours_s = arg.uptime % (60 * 60);
|
||||
let minutes = hours_s / 60;
|
||||
let seconds = arg.uptime % 60;
|
||||
let mut uptime_string = String::new();
|
||||
if days > 0 {
|
||||
write!(&mut uptime_string, "{days} days").unwrap();
|
||||
}
|
||||
if hours > 0 {
|
||||
if !uptime_string.is_empty() {
|
||||
uptime_string += ", ";
|
||||
}
|
||||
write!(&mut uptime_string, "{hours} hours").unwrap();
|
||||
}
|
||||
if minutes > 0 {
|
||||
if !uptime_string.is_empty() {
|
||||
uptime_string += ", ";
|
||||
}
|
||||
write!(&mut uptime_string, "{minutes} minutes").unwrap();
|
||||
}
|
||||
if !uptime_string.is_empty() {
|
||||
uptime_string += ", ";
|
||||
}
|
||||
write!(&mut uptime_string, "{seconds} seconds").unwrap();
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc -> "NOW", &arg.now]);
|
||||
table.add_row(row![bc -> "UPTIME", &uptime_string]);
|
||||
table.print_tty(false).unwrap();
|
||||
}
|
||||
|
||||
#[command(display(display_time))]
|
||||
pub async fn time(
|
||||
#[context] ctx: RpcContext,
|
||||
#[allow(unused_variables)]
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<TimeInfo, Error> {
|
||||
Ok(TimeInfo {
|
||||
now: Utc::now().to_rfc3339(),
|
||||
uptime: ctx.start_time.elapsed().as_secs(),
|
||||
})
|
||||
}
|
||||
|
||||
#[command(
|
||||
@@ -303,60 +360,44 @@ impl<'de> Deserialize<'de> for GigaBytes {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct MetricsGeneral {
|
||||
#[serde(rename = "Temperature")]
|
||||
temperature: Option<Celsius>,
|
||||
pub temperature: Option<Celsius>,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct MetricsMemory {
|
||||
#[serde(rename = "Percentage Used")]
|
||||
pub percentage_used: Percentage,
|
||||
#[serde(rename = "Total")]
|
||||
pub total: MebiBytes,
|
||||
#[serde(rename = "Available")]
|
||||
pub available: MebiBytes,
|
||||
#[serde(rename = "Used")]
|
||||
pub used: MebiBytes,
|
||||
#[serde(rename = "Swap Total")]
|
||||
pub swap_total: MebiBytes,
|
||||
#[serde(rename = "Swap Free")]
|
||||
pub swap_free: MebiBytes,
|
||||
#[serde(rename = "Swap Used")]
|
||||
pub swap_used: MebiBytes,
|
||||
pub zram_total: MebiBytes,
|
||||
pub zram_available: MebiBytes,
|
||||
pub zram_used: MebiBytes,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct MetricsCpu {
|
||||
#[serde(rename = "User Space")]
|
||||
user_space: Percentage,
|
||||
#[serde(rename = "Kernel Space")]
|
||||
kernel_space: Percentage,
|
||||
#[serde(rename = "I/O Wait")]
|
||||
wait: Percentage,
|
||||
#[serde(rename = "Idle")]
|
||||
percentage_used: Percentage,
|
||||
idle: Percentage,
|
||||
#[serde(rename = "Usage")]
|
||||
usage: Percentage,
|
||||
user_space: Percentage,
|
||||
kernel_space: Percentage,
|
||||
wait: Percentage,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct MetricsDisk {
|
||||
#[serde(rename = "Size")]
|
||||
size: GigaBytes,
|
||||
#[serde(rename = "Used")]
|
||||
percentage_used: Percentage,
|
||||
used: GigaBytes,
|
||||
#[serde(rename = "Available")]
|
||||
available: GigaBytes,
|
||||
#[serde(rename = "Percentage Used")]
|
||||
used_percentage: Percentage,
|
||||
capacity: GigaBytes,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Metrics {
|
||||
#[serde(rename = "General")]
|
||||
general: MetricsGeneral,
|
||||
#[serde(rename = "Memory")]
|
||||
memory: MetricsMemory,
|
||||
#[serde(rename = "CPU")]
|
||||
cpu: MetricsCpu,
|
||||
#[serde(rename = "Disk")]
|
||||
disk: MetricsDisk,
|
||||
}
|
||||
|
||||
@@ -682,7 +723,7 @@ async fn get_cpu_info(last: &mut ProcStat) -> Result<MetricsCpu, Error> {
|
||||
kernel_space: Percentage((new.system() - last.system()) as f64 * 100.0 / total_diff as f64),
|
||||
idle: Percentage((new.idle - last.idle) as f64 * 100.0 / total_diff as f64),
|
||||
wait: Percentage((new.iowait - last.iowait) as f64 * 100.0 / total_diff as f64),
|
||||
usage: Percentage((new.used() - last.used()) as f64 * 100.0 / total_diff as f64),
|
||||
percentage_used: Percentage((new.used() - last.used()) as f64 * 100.0 / total_diff as f64),
|
||||
};
|
||||
*last = new;
|
||||
Ok(res)
|
||||
@@ -695,8 +736,8 @@ pub struct MemInfo {
|
||||
buffers: Option<u64>,
|
||||
cached: Option<u64>,
|
||||
slab: Option<u64>,
|
||||
swap_total: Option<u64>,
|
||||
swap_free: Option<u64>,
|
||||
zram_total: Option<u64>,
|
||||
zram_free: Option<u64>,
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
|
||||
@@ -708,8 +749,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
|
||||
buffers: None,
|
||||
cached: None,
|
||||
slab: None,
|
||||
swap_total: None,
|
||||
swap_free: None,
|
||||
zram_total: None,
|
||||
zram_free: None,
|
||||
};
|
||||
fn get_num_kb(l: &str) -> Result<u64, Error> {
|
||||
let e = Error::new(
|
||||
@@ -734,8 +775,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
|
||||
_ if entry.starts_with("Buffers") => mem_info.buffers = Some(get_num_kb(entry)?),
|
||||
_ if entry.starts_with("Cached") => mem_info.cached = Some(get_num_kb(entry)?),
|
||||
_ if entry.starts_with("Slab") => mem_info.slab = Some(get_num_kb(entry)?),
|
||||
_ if entry.starts_with("SwapTotal") => mem_info.swap_total = Some(get_num_kb(entry)?),
|
||||
_ if entry.starts_with("SwapFree") => mem_info.swap_free = Some(get_num_kb(entry)?),
|
||||
_ if entry.starts_with("SwapTotal") => mem_info.zram_total = Some(get_num_kb(entry)?),
|
||||
_ if entry.starts_with("SwapFree") => mem_info.zram_free = Some(get_num_kb(entry)?),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -751,24 +792,24 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
|
||||
let buffers = ensure_present(mem_info.buffers, "Buffers")?;
|
||||
let cached = ensure_present(mem_info.cached, "Cached")?;
|
||||
let slab = ensure_present(mem_info.slab, "Slab")?;
|
||||
let swap_total_k = ensure_present(mem_info.swap_total, "SwapTotal")?;
|
||||
let swap_free_k = ensure_present(mem_info.swap_free, "SwapFree")?;
|
||||
let zram_total_k = ensure_present(mem_info.zram_total, "SwapTotal")?;
|
||||
let zram_free_k = ensure_present(mem_info.zram_free, "SwapFree")?;
|
||||
|
||||
let total = MebiBytes(mem_total as f64 / 1024.0);
|
||||
let available = MebiBytes(mem_available as f64 / 1024.0);
|
||||
let used = MebiBytes((mem_total - mem_free - buffers - cached - slab) as f64 / 1024.0);
|
||||
let swap_total = MebiBytes(swap_total_k as f64 / 1024.0);
|
||||
let swap_free = MebiBytes(swap_free_k as f64 / 1024.0);
|
||||
let swap_used = MebiBytes((swap_total_k - swap_free_k) as f64 / 1024.0);
|
||||
let zram_total = MebiBytes(zram_total_k as f64 / 1024.0);
|
||||
let zram_available = MebiBytes(zram_free_k as f64 / 1024.0);
|
||||
let zram_used = MebiBytes((zram_total_k - zram_free_k) as f64 / 1024.0);
|
||||
let percentage_used = Percentage((total.0 - available.0) / total.0 * 100.0);
|
||||
Ok(MetricsMemory {
|
||||
percentage_used,
|
||||
total,
|
||||
available,
|
||||
used,
|
||||
swap_total,
|
||||
swap_free,
|
||||
swap_used,
|
||||
zram_total,
|
||||
zram_available,
|
||||
zram_used,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -792,10 +833,10 @@ async fn get_disk_info() -> Result<MetricsDisk, Error> {
|
||||
let total_percentage = total_used as f64 / total_size as f64 * 100.0f64;
|
||||
|
||||
Ok(MetricsDisk {
|
||||
size: GigaBytes(total_size as f64 / 1_000_000_000.0),
|
||||
capacity: GigaBytes(total_size as f64 / 1_000_000_000.0),
|
||||
used: GigaBytes(total_used as f64 / 1_000_000_000.0),
|
||||
available: GigaBytes(total_available as f64 / 1_000_000_000.0),
|
||||
used_percentage: Percentage(total_percentage as f64),
|
||||
percentage_used: Percentage(total_percentage as f64),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::sound::{
|
||||
};
|
||||
use crate::update::latest_information::LatestInformation;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
||||
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
|
||||
|
||||
mod latest_information;
|
||||
|
||||
@@ -231,7 +231,7 @@ impl EosUrl {
|
||||
.host_str()
|
||||
.ok_or_else(|| Error::new(eyre!("Could not get host of base"), ErrorKind::ParseUrl))?;
|
||||
let version: &Version = &self.version;
|
||||
Ok(format!("{host}::{version}/{OS_ARCH}/")
|
||||
Ok(format!("{host}::{version}/{}/", &*PLATFORM)
|
||||
.parse()
|
||||
.map_err(|_| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?)
|
||||
}
|
||||
@@ -297,7 +297,7 @@ async fn sync_boot() -> Result<(), Error> {
|
||||
.await?
|
||||
.wait()
|
||||
.await?;
|
||||
if OS_ARCH != "raspberrypi" {
|
||||
if &*PLATFORM != "raspberrypi" {
|
||||
let dev_mnt =
|
||||
MountGuard::mount(&Bind::new("/dev"), "/media/embassy/next/dev", ReadWrite).await?;
|
||||
let sys_mnt =
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use ed25519_dalek::hazmat::ExpandedSecretKey;
|
||||
use ed25519_dalek::{SecretKey, EXPANDED_SECRET_KEY_LENGTH};
|
||||
|
||||
#[inline]
|
||||
pub fn ed25519_expand_key(key: &SecretKey) -> [u8; EXPANDED_SECRET_KEY_LENGTH] {
|
||||
let key = ExpandedSecretKey::from(key);
|
||||
|
||||
let mut bytes: [u8; 64] = [0u8; 64];
|
||||
|
||||
bytes[..32].copy_from_slice(key.scalar.as_bytes());
|
||||
bytes[32..].copy_from_slice(&key.hash_prefix[..]);
|
||||
bytes
|
||||
ed25519_dalek_v1::ExpandedSecretKey::from(
|
||||
&ed25519_dalek_v1::SecretKey::from_bytes(key).unwrap(),
|
||||
)
|
||||
.to_bytes()
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ use tokio::process::Command;
|
||||
|
||||
use crate::util::Invoke;
|
||||
|
||||
#[cfg(not(feature = "podman"))]
|
||||
#[cfg(feature = "docker")]
|
||||
pub const CONTAINER_TOOL: &str = "docker";
|
||||
#[cfg(feature = "podman")]
|
||||
#[cfg(not(feature = "docker"))]
|
||||
pub const CONTAINER_TOOL: &str = "podman";
|
||||
|
||||
#[cfg(not(feature = "podman"))]
|
||||
#[cfg(feature = "docker")]
|
||||
pub const CONTAINER_DATADIR: &str = "/var/lib/docker";
|
||||
#[cfg(feature = "podman")]
|
||||
#[cfg(not(feature = "docker"))]
|
||||
pub const CONTAINER_DATADIR: &str = "/var/lib/containers";
|
||||
|
||||
pub struct DockerImageSha(String);
|
||||
|
||||
@@ -26,13 +26,13 @@ use crate::shutdown::Shutdown;
|
||||
use crate::{Error, ErrorKind, ResultExt as _};
|
||||
pub mod config;
|
||||
pub mod cpupower;
|
||||
pub mod crypto;
|
||||
pub mod docker;
|
||||
pub mod http_reader;
|
||||
pub mod io;
|
||||
pub mod logger;
|
||||
pub mod lshw;
|
||||
pub mod serde;
|
||||
pub mod crypto;
|
||||
|
||||
#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)]
|
||||
pub enum Never {}
|
||||
@@ -50,30 +50,113 @@ impl std::fmt::Display for Never {
|
||||
impl std::error::Error for Never {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Invoke {
|
||||
pub trait Invoke<'a> {
|
||||
type Extended<'ext>
|
||||
where
|
||||
Self: 'ext,
|
||||
'ext: 'a;
|
||||
fn timeout<'ext: 'a>(&'ext mut self, timeout: Option<Duration>) -> Self::Extended<'ext>;
|
||||
fn input<'ext: 'a, Input: tokio::io::AsyncRead + Unpin + Send>(
|
||||
&'ext mut self,
|
||||
input: Option<&'ext mut Input>,
|
||||
) -> Self::Extended<'ext>;
|
||||
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error>;
|
||||
async fn invoke_timeout(
|
||||
&mut self,
|
||||
error_kind: crate::ErrorKind,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Vec<u8>, Error>;
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl Invoke for tokio::process::Command {
|
||||
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
|
||||
self.invoke_timeout(error_kind, None).await
|
||||
|
||||
pub struct ExtendedCommand<'a> {
|
||||
cmd: &'a mut tokio::process::Command,
|
||||
timeout: Option<Duration>,
|
||||
input: Option<&'a mut (dyn tokio::io::AsyncRead + Unpin + Send)>,
|
||||
}
|
||||
impl<'a> std::ops::Deref for ExtendedCommand<'a> {
|
||||
type Target = tokio::process::Command;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.cmd
|
||||
}
|
||||
async fn invoke_timeout(
|
||||
&mut self,
|
||||
error_kind: crate::ErrorKind,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
self.kill_on_drop(true);
|
||||
self.stdout(Stdio::piped());
|
||||
self.stderr(Stdio::piped());
|
||||
let res = match timeout {
|
||||
None => self.output().await?,
|
||||
Some(t) => tokio::time::timeout(t, self.output())
|
||||
}
|
||||
impl<'a> std::ops::DerefMut for ExtendedCommand<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.cmd
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Invoke<'a> for tokio::process::Command {
|
||||
type Extended<'ext> = ExtendedCommand<'ext>
|
||||
where
|
||||
Self: 'ext,
|
||||
'ext: 'a;
|
||||
fn timeout<'ext: 'a>(&'ext mut self, timeout: Option<Duration>) -> Self::Extended<'ext> {
|
||||
ExtendedCommand {
|
||||
cmd: self,
|
||||
timeout,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
fn input<'ext: 'a, Input: tokio::io::AsyncRead + Unpin + Send>(
|
||||
&'ext mut self,
|
||||
input: Option<&'ext mut Input>,
|
||||
) -> Self::Extended<'ext> {
|
||||
ExtendedCommand {
|
||||
cmd: self,
|
||||
timeout: None,
|
||||
input: if let Some(input) = input {
|
||||
Some(&mut *input)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
|
||||
ExtendedCommand {
|
||||
cmd: self,
|
||||
timeout: None,
|
||||
input: None,
|
||||
}
|
||||
.invoke(error_kind)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Invoke<'a> for ExtendedCommand<'a> {
|
||||
type Extended<'ext> = &'ext mut ExtendedCommand<'ext>
|
||||
where
|
||||
Self: 'ext,
|
||||
'ext: 'a;
|
||||
fn timeout<'ext: 'a>(&'ext mut self, timeout: Option<Duration>) -> Self::Extended<'ext> {
|
||||
self.timeout = timeout;
|
||||
self
|
||||
}
|
||||
fn input<'ext: 'a, Input: tokio::io::AsyncRead + Unpin + Send>(
|
||||
&'ext mut self,
|
||||
input: Option<&'ext mut Input>,
|
||||
) -> Self::Extended<'ext> {
|
||||
self.input = if let Some(input) = input {
|
||||
Some(&mut *input)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self
|
||||
}
|
||||
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
|
||||
self.cmd.kill_on_drop(true);
|
||||
if self.input.is_some() {
|
||||
self.cmd.stdin(Stdio::piped());
|
||||
}
|
||||
self.cmd.stdout(Stdio::piped());
|
||||
self.cmd.stderr(Stdio::piped());
|
||||
let mut child = self.cmd.spawn()?;
|
||||
if let (Some(mut stdin), Some(input)) = (child.stdin.take(), self.input.take()) {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
tokio::io::copy(input, &mut stdin).await?;
|
||||
stdin.flush().await?;
|
||||
stdin.shutdown().await?;
|
||||
drop(stdin);
|
||||
}
|
||||
let res = match self.timeout {
|
||||
None => child.wait_with_output().await?,
|
||||
Some(t) => tokio::time::timeout(t, child.wait_with_output())
|
||||
.await
|
||||
.with_kind(ErrorKind::Timeout)??,
|
||||
};
|
||||
|
||||
@@ -200,8 +200,7 @@ pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const COMMIT_HASH: &str =
|
||||
git_version::git_version!(args = ["--always", "--abbrev=40", "--dirty=-modified"]);
|
||||
pub const COMMIT_HASH: &str = include_str!("../../../GIT_HASH.txt");
|
||||
|
||||
#[command(rename = "git-info", local, metadata(authenticated = false))]
|
||||
pub fn git_info() -> Result<&'static str, Error> {
|
||||
|
||||
@@ -27,6 +27,12 @@ impl Volumes {
|
||||
volume
|
||||
.validate(interfaces)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, format!("Volume {}", id)))?;
|
||||
if let Volume::Backup { .. } = volume {
|
||||
return Err(Error::new(
|
||||
eyre!("Invalid volume type \"backup\""),
|
||||
ErrorKind::ParseS9pk,
|
||||
)); // Volume::Backup is for internal use and shouldn't be declared in manifest
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -131,7 +137,6 @@ pub enum Volume {
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Certificate { interface_id: InterfaceId },
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(skip)]
|
||||
Backup { readonly: bool },
|
||||
}
|
||||
impl Volume {
|
||||
|
||||
@@ -855,7 +855,7 @@ export const action = {
|
||||
},
|
||||
/**
|
||||
* Created this test because of issue
|
||||
* https://github.com/Start9Labs/embassy-os/issues/1737
|
||||
* https://github.com/Start9Labs/start-os/issues/1737
|
||||
* which that we couldn't create a dir that was deeply nested, and the parents where
|
||||
* not created yet. Found this out during the migrations, where the parent would die.
|
||||
* @param {*} effects
|
||||
@@ -931,7 +931,7 @@ export const action = {
|
||||
},
|
||||
/**
|
||||
* Created this test because of issue
|
||||
* https://github.com/Start9Labs/embassy-os/issues/2121
|
||||
* https://github.com/Start9Labs/start-os/issues/2121
|
||||
* That the empty in the create dies
|
||||
* @param {*} effects
|
||||
* @param {*} _input
|
||||
|
||||
@@ -15,7 +15,7 @@ docker run -d --rm --name=tmp_postgres -e POSTGRES_PASSWORD=password -v $TMP_DIR
|
||||
PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tmp_postgres)
|
||||
|
||||
DATABASE_URL=postgres://postgres:password@$PG_IP/postgres cargo sqlx migrate run
|
||||
DATABASE_URL=postgres://postgres:password@$PG_IP/postgres OS_ARCH=$(uname -m) cargo sqlx prepare -- --lib --profile=test
|
||||
DATABASE_URL=postgres://postgres:password@$PG_IP/postgres PLATFORM=$(uname -m) cargo sqlx prepare -- --lib --profile=test
|
||||
)
|
||||
|
||||
docker stop tmp_postgres
|
||||
|
||||
Reference in New Issue
Block a user