Merge branch 'next/minor' of github.com:Start9Labs/start-os into rebase/feat/domains

This commit is contained in:
Matt Hill
2024-03-30 21:14:53 -06:00
191 changed files with 1789 additions and 1316 deletions

View File

@@ -206,7 +206,7 @@ async fn cli_dump(
from_value::<Dump>(
ctx.call_remote(
"db.dump",
imbl_value::json!({ "include-private":include_private }),
imbl_value::json!({ "includePrivate":include_private }),
)
.await?,
)?

View File

@@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use patch_db::HasModel;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::account::AccountInfo;
use crate::auth::Sessions;

View File

@@ -3,7 +3,7 @@ use std::collections::{BTreeMap, BTreeSet};
use chrono::{DateTime, Utc};
use emver::VersionRange;
use imbl_value::InternedString;
use models::{DataUrl, HealthCheckId, HostId, PackageId};
use models::{ActionId, DataUrl, HealthCheckId, HostId, PackageId, ServiceInterfaceId};
use patch_db::json_ptr::JsonPointer;
use patch_db::HasModel;
use reqwest::Url;
@@ -11,12 +11,15 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::net::host::HostInfo;
use crate::net::service_interface::ServiceInterfaceWithHostInfo;
use crate::prelude::*;
use crate::progress::FullProgress;
use crate::s9pk::manifest::Manifest;
use crate::status::Status;
use crate::util::serde::Pem;
#[derive(Debug, Default, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct AllPackageData(pub BTreeMap<PackageId, PackageDataEntry>);
impl Map for AllPackageData {
type Key = PackageId;
@@ -35,10 +38,11 @@ pub enum ManifestPreference {
New,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "state")]
#[model = "Model<Self>"]
#[ts(export)]
pub enum PackageState {
Installing(InstallingState),
Restoring(InstallingState),
@@ -257,53 +261,81 @@ impl Model<PackageState> {
}
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct InstallingState {
pub installing_info: InstallingInfo,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct UpdatingState {
pub manifest: Manifest,
pub installing_info: InstallingInfo,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct InstalledState {
pub manifest: Manifest,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct InstallingInfo {
pub new_manifest: Manifest,
pub progress: FullProgress,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum AllowedStatuses {
OnlyRunning, // onlyRunning
OnlyStopped,
Any,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct ActionMetadata {
pub name: String,
pub description: String,
pub warning: Option<String>,
#[ts(type = "any")]
pub input: Value,
pub disabled: bool,
pub allowed_statuses: AllowedStatuses,
pub group: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct PackageDataEntry {
pub state_info: PackageState,
pub status: Status,
#[ts(type = "string | null")]
pub marketplace_url: Option<Url>,
#[serde(default)]
#[serde(with = "crate::util::serde::ed25519_pubkey")]
pub developer_key: ed25519_dalek::VerifyingKey,
#[ts(type = "string")]
pub developer_key: Pem<ed25519_dalek::VerifyingKey>,
pub icon: DataUrl<'static>,
#[ts(type = "string | null")]
pub last_backup: Option<DateTime<Utc>>,
pub dependency_info: BTreeMap<PackageId, StaticDependencyInfo>,
pub current_dependencies: CurrentDependencies,
pub interface_addresses: InterfaceAddressMap,
pub actions: BTreeMap<ActionId, ActionMetadata>,
pub service_interfaces: BTreeMap<ServiceInterfaceId, ServiceInterfaceWithHostInfo>,
pub hosts: HostInfo,
pub store_exposed_ui: StoreExposedUI,
#[ts(type = "string[]")]
pub store_exposed_dependents: Vec<JsonPointer>,
}
impl AsRef<PackageDataEntry> for PackageDataEntry {
@@ -312,52 +344,8 @@ impl AsRef<PackageDataEntry> for PackageDataEntry {
}
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[model = "Model<Self>"]
pub struct ExposedDependent {
path: String,
title: String,
description: Option<String>,
masked: Option<bool>,
copyable: Option<bool>,
qr: Option<bool>,
}
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StoreExposedUI(pub BTreeMap<InternedString, ExposedUI>);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
#[derive(Debug, Clone, Default, Deserialize, Serialize, TS)]
#[ts(export)]
pub enum ExposedUI {
Object {
#[ts(type = "{[key: string]: ExposedUI}")]
value: BTreeMap<String, ExposedUI>,
#[serde(default)]
#[ts(type = "string | null")]
description: String,
},
String {
#[ts(type = "string")]
path: JsonPointer,
description: Option<String>,
masked: bool,
copyable: Option<bool>,
qr: Option<bool>,
},
}
impl Default for ExposedUI {
fn default() -> Self {
ExposedUI::Object {
value: BTreeMap::new(),
description: "".to_string(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct CurrentDependencies(pub BTreeMap<PackageId, CurrentDependencyInfo>);
impl CurrentDependencies {
pub fn map(
@@ -381,31 +369,26 @@ impl Map for CurrentDependencies {
}
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct StaticDependencyInfo {
pub struct CurrentDependencyInfo {
#[serde(flatten)]
pub kind: CurrentDependencyKind,
pub title: String,
pub icon: DataUrl<'static>,
#[ts(type = "string")]
pub registry_url: Url,
#[ts(type = "string")]
pub version_spec: VersionRange,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "kind")]
pub enum CurrentDependencyInfo {
#[serde(rename_all = "camelCase")]
Exists {
#[ts(type = "string")]
url: Url,
#[ts(type = "string")]
version_spec: VersionRange,
},
pub enum CurrentDependencyKind {
Exists,
#[serde(rename_all = "camelCase")]
Running {
#[ts(type = "string")]
url: Url,
#[ts(type = "string")]
version_spec: VersionRange,
#[serde(default)]
#[ts(type = "string[]")]
health_checks: BTreeSet<HealthCheckId>,

View File

@@ -13,6 +13,7 @@ use patch_db::{HasModel, Value};
use reqwest::Url;
use serde::{Deserialize, Serialize};
use torut::onion::OnionAddressV3;
use ts_rs::TS;
use crate::account::AccountInfo;
use crate::db::model::package::AllPackageData;
@@ -23,13 +24,14 @@ use crate::util::Version;
use crate::version::{Current, VersionT};
use crate::{ARCH, PLATFORM};
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
// #[macro_debug]
#[ts(export)]
pub struct Public {
pub server_info: ServerInfo,
pub package_data: AllPackageData,
#[ts(type = "any")]
pub ui: Value,
}
impl Public {
@@ -64,10 +66,6 @@ impl Public {
selected: None,
},
unread_notification_count: 0,
connection_addresses: ConnectionAddresses {
tor: Vec::new(),
clearnet: Vec::new(),
},
password_hash: account.password.clone(),
pubkey: ssh_key::PublicKey::from(&account.ssh_key)
.to_openssh()
@@ -101,31 +99,41 @@ fn get_platform() -> InternedString {
(&*PLATFORM).into()
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct ServerInfo {
#[serde(default = "get_arch")]
#[ts(type = "string")]
pub arch: InternedString,
#[serde(default = "get_platform")]
#[ts(type = "string")]
pub platform: InternedString,
pub id: String,
pub hostname: String,
#[ts(type = "string")]
pub version: Version,
#[ts(type = "string | null")]
pub last_backup: Option<DateTime<Utc>>,
/// Used in the wifi to determine the region to set the system to
#[ts(type = "string | null")]
pub last_wifi_region: Option<CountryCode>,
#[ts(type = "string")]
pub eos_version_compat: VersionRange,
#[ts(type = "string")]
pub lan_address: Url,
#[ts(type = "string")]
pub onion_address: OnionAddressV3,
/// for backwards compatibility
#[ts(type = "string")]
pub tor_address: Url,
pub ip_info: BTreeMap<String, IpInfo>,
#[serde(default)]
pub status_info: ServerStatus,
pub wifi: WifiInfo,
#[ts(type = "number")]
pub unread_notification_count: u64,
pub connection_addresses: ConnectionAddresses,
pub password_hash: String,
pub pubkey: String,
pub ca_fingerprint: String,
@@ -136,12 +144,15 @@ pub struct ServerInfo {
pub governor: Option<Governor>,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct IpInfo {
#[ts(type = "string | null")]
pub ipv4_range: Option<Ipv4Net>,
pub ipv4: Option<Ipv4Addr>,
#[ts(type = "string | null")]
pub ipv6_range: Option<Ipv6Net>,
pub ipv6: Option<Ipv6Addr>,
}
@@ -158,15 +169,17 @@ impl IpInfo {
}
}
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[model = "Model<Self>"]
#[ts(export)]
pub struct BackupProgress {
pub complete: bool,
}
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct ServerStatus {
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
pub updated: bool,
@@ -177,34 +190,32 @@ pub struct ServerStatus {
pub restarting: bool,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct UpdateProgress {
#[ts(type = "number | null")]
pub size: Option<u64>,
#[ts(type = "number")]
pub downloaded: u64,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct WifiInfo {
pub ssids: Vec<String>,
pub selected: Option<String>,
pub connected: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct ServerSpecs {
pub cpu: String,
pub disk: String,
pub memory: String,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionAddresses {
pub tor: Vec<String>,
pub clearnet: Vec<String>,
}

View File

@@ -2,18 +2,16 @@ use std::collections::BTreeMap;
use std::time::Duration;
use clap::Parser;
use emver::VersionRange;
use models::{OptionExt, PackageId};
use models::PackageId;
use rpc_toolkit::{command, from_fn_async, Empty, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
use tracing::instrument;
use ts_rs::TS;
use crate::config::{Config, ConfigSpec, ConfigureContext};
use crate::context::RpcContext;
use crate::db::model::package::CurrentDependencies;
use crate::db::model::Database;
use crate::prelude::*;
use crate::s9pk::manifest::Manifest;
use crate::status::DependencyConfigErrors;
use crate::Error;
@@ -21,8 +19,9 @@ pub fn dependency() -> ParentHandler {
ParentHandler::new().subcommand("configure", configure())
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[model = "Model<Self>"]
#[ts(export)]
pub struct Dependencies(pub BTreeMap<PackageId, DepInfo>);
impl Map for Dependencies {
type Key = PackageId;
@@ -35,27 +34,13 @@ impl Map for Dependencies {
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum DependencyRequirement {
OptIn { how: String },
OptOut { how: String },
Required,
}
impl DependencyRequirement {
pub fn required(&self) -> bool {
matches!(self, &DependencyRequirement::Required)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct DepInfo {
pub version: VersionRange,
pub requirement: DependencyRequirement,
pub description: Option<String>,
pub optional: bool,
}
#[derive(Deserialize, Serialize, Parser)]

View File

@@ -1,9 +1,14 @@
use serde::{Deserialize, Serialize};
use torut::onion::OnionAddressV3;
use ts_rs::TS;
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "kind")]
#[ts(export)]
pub enum HostAddress {
Onion { address: OnionAddressV3 },
Onion {
#[ts(type = "string")]
address: OnionAddressV3,
},
}

View File

@@ -1,12 +1,14 @@
use imbl_value::InternedString;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::net::forward::AvailablePorts;
use crate::net::vhost::AlpnInfo;
use crate::prelude::*;
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct BindInfo {
pub options: BindOptions,
pub assigned_lan_port: Option<u16>,
@@ -14,7 +16,7 @@ pub struct BindInfo {
impl BindInfo {
pub fn new(available_ports: &mut AvailablePorts, options: BindOptions) -> Result<Self, Error> {
let mut assigned_lan_port = None;
if options.add_ssl.is_some() || options.secure {
if options.add_ssl.is_some() || options.secure.is_some() {
assigned_lan_port = Some(available_ports.alloc()?);
}
Ok(Self {
@@ -31,7 +33,7 @@ impl BindInfo {
mut assigned_lan_port,
..
} = self;
if options.add_ssl.is_some() || options.secure {
if options.add_ssl.is_some() || options.secure.is_some() {
assigned_lan_port = if let Some(port) = assigned_lan_port.take() {
Some(port)
} else {
@@ -49,20 +51,30 @@ impl BindInfo {
}
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct BindOptions {
pub scheme: InternedString,
pub preferred_external_port: u16,
pub add_ssl: Option<AddSslOptions>,
pub secure: bool,
pub struct Security {
pub ssl: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct BindOptions {
#[ts(type = "string | null")]
pub scheme: Option<InternedString>,
pub preferred_external_port: u16,
pub add_ssl: Option<AddSslOptions>,
pub secure: Option<Security>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct AddSslOptions {
pub scheme: InternedString,
#[ts(type = "string | null")]
pub scheme: Option<InternedString>,
pub preferred_external_port: u16,
// #[serde(default)]
// pub add_x_forwarded_headers: bool, // TODO

View File

@@ -3,6 +3,7 @@ use std::collections::{BTreeMap, BTreeSet};
use imbl_value::InternedString;
use models::HostId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::net::forward::AvailablePorts;
use crate::net::host::address::HostAddress;
@@ -12,9 +13,10 @@ use crate::prelude::*;
pub mod address;
pub mod binding;
#[derive(Debug, Deserialize, Serialize, HasModel)]
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct Host {
pub kind: HostKind,
pub bindings: BTreeMap<u16, BindInfo>,
@@ -37,16 +39,18 @@ impl Host {
}
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub enum HostKind {
Multi,
// Single,
// Static,
}
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[model = "Model<Self>"]
#[ts(export)]
pub struct HostInfo(BTreeMap<HostId, Host>);
impl Map for HostInfo {

View File

@@ -7,6 +7,7 @@ pub mod host;
pub mod keys;
pub mod mdns;
pub mod net_controller;
pub mod service_interface;
pub mod ssl;
pub mod static_server;
pub mod tor;

View File

@@ -247,7 +247,7 @@ impl NetService {
None,
external,
(self.ip, *port).into(),
if bind.options.ssl {
if bind.options.secure.as_ref().map_or(false, |s| s.ssl) {
Ok(())
} else {
Err(ssl.alpn.clone())

View File

@@ -0,0 +1,115 @@
use std::net::{Ipv4Addr, Ipv6Addr};
use models::{HostId, ServiceInterfaceId};
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::net::host::binding::BindOptions;
use crate::net::host::HostKind;
use crate::prelude::*;
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ServiceInterfaceWithHostInfo {
#[serde(flatten)]
pub service_interface: ServiceInterface,
pub host_info: ExportedHostInfo,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExportedHostInfo {
pub id: HostId,
pub kind: HostKind,
pub hostnames: Vec<ExportedHostnameInfo>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[serde(tag = "kind")]
pub enum ExportedHostnameInfo {
Ip {
network_interface_id: String,
public: bool,
hostname: ExportedIpHostname,
},
Onion {
hostname: ExportedOnionHostname,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExportedOnionHostname {
pub value: String,
pub port: Option<u16>,
pub ssl_port: Option<u16>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[serde(tag = "kind")]
pub enum ExportedIpHostname {
Ipv4 {
value: Ipv4Addr,
port: Option<u16>,
ssl_port: Option<u16>,
},
Ipv6 {
value: Ipv6Addr,
port: Option<u16>,
ssl_port: Option<u16>,
},
Local {
value: String,
port: Option<u16>,
ssl_port: Option<u16>,
},
Domain {
domain: String,
subdomain: Option<String>,
port: Option<u16>,
ssl_port: Option<u16>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ServiceInterface {
pub id: ServiceInterfaceId,
pub name: String,
pub description: String,
pub has_primary: bool,
pub disabled: bool,
pub masked: bool,
pub address_info: AddressInfo,
#[serde(rename = "type")]
pub interface_type: ServiceInterfaceType,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum ServiceInterfaceType {
Ui,
P2p,
Api,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct AddressInfo {
pub username: Option<String>,
pub host_id: HostId,
pub bind_options: BindOptions,
pub suffix: String,
}

View File

@@ -14,7 +14,7 @@ use futures::future::ready;
use http::header::ACCEPT_ENCODING;
use http::request::Parts as RequestParts;
use http::{HeaderMap, Method, StatusCode};
use include_dir::{include_dir, Dir};
use include_dir::Dir;
use new_mime_guess::MimeGuess;
use openssl::hash::MessageDigest;
use openssl::x509::X509;
@@ -33,11 +33,15 @@ use crate::middleware::db::SyncDb;
use crate::middleware::diagnostic::DiagnosticMode;
use crate::{diagnostic_api, install_api, main_api, setup_api, Error, ErrorKind, ResultExt};
static NOT_FOUND: &[u8] = b"Not Found";
static METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed";
static NOT_AUTHORIZED: &[u8] = b"Not Authorized";
const NOT_FOUND: &[u8] = b"Not Found";
const METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed";
const NOT_AUTHORIZED: &[u8] = b"Not Authorized";
static EMBEDDED_UIS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
#[cfg(all(feature = "daemon", not(feature = "test")))]
const EMBEDDED_UIS: Dir<'_> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
#[cfg(not(all(feature = "daemon", not(feature = "test"))))]
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];

View File

@@ -17,6 +17,7 @@ use tokio_rustls::rustls::server::Acceptor;
use tokio_rustls::rustls::{RootCertStore, ServerConfig};
use tokio_rustls::{LazyConfigAcceptor, TlsConnector};
use tracing::instrument;
use ts_rs::TS;
use crate::prelude::*;
use crate::util::io::{BackTrackingReader, TimeoutStream};
@@ -80,8 +81,9 @@ struct TargetInfo {
connect_ssl: Result<(), AlpnInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub enum AlpnInfo {
Reflect,
Specified(Vec<MaybeUtf8String>),

View File

@@ -9,6 +9,7 @@ use itertools::Itertools;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncSeek, AsyncWrite};
use tokio::sync::{mpsc, watch};
use ts_rs::TS;
use crate::db::model::DatabaseModel;
use crate::prelude::*;
@@ -19,11 +20,16 @@ lazy_static::lazy_static! {
static ref BYTES: ProgressStyle = ProgressStyle::with_template("{spinner} {wide_msg} [{bytes}/?] [{binary_bytes_per_sec} {elapsed}]").unwrap();
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
#[serde(untagged)]
pub enum Progress {
Complete(bool),
Progress { done: u64, total: Option<u64> },
Progress {
#[ts(type = "number")]
done: u64,
#[ts(type = "number | null")]
total: Option<u64>,
},
}
impl Progress {
pub fn new() -> Self {
@@ -126,13 +132,16 @@ impl std::ops::AddAssign<u64> for Progress {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NamedProgress {
#[ts(type = "string")]
pub name: InternedString,
pub progress: Progress,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct FullProgress {
pub overall: Progress,
pub phases: Vec<NamedProgress>,

View File

@@ -1,13 +1,10 @@
use std::{borrow::Borrow, collections::BTreeMap};
use clap::Parser;
use imbl_value::{json, InOMap, InternedString, Value};
use imbl_value::Value;
use models::PackageId;
use rpc_toolkit::command;
use serde::{Deserialize, Serialize};
use crate::context::RpcContext;
use crate::db::model::package::{ExposedUI, StoreExposedUI};
use crate::prelude::*;
use crate::Error;

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use emver::VersionRange;
use imbl_value::InOMap;
pub use models::PackageId;
use models::VolumeId;
@@ -8,7 +9,6 @@ use serde::{Deserialize, Serialize};
use url::Url;
use super::git_hash::GitHash;
use crate::dependencies::Dependencies;
use crate::prelude::*;
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
use crate::util::Version;
@@ -43,7 +43,7 @@ pub struct Manifest {
pub alerts: Alerts,
pub volumes: BTreeMap<VolumeId, Value>,
#[serde(default)]
pub dependencies: Dependencies,
pub dependencies: BTreeMap<PackageId, DepInfo>,
pub config: Option<InOMap<String, Value>>,
#[serde(default)]
@@ -53,6 +53,29 @@ pub struct Manifest {
pub hardware_requirements: HardwareRequirements,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum DependencyRequirement {
OptIn { how: String },
OptOut { how: String },
Required,
}
impl DependencyRequirement {
pub fn required(&self) -> bool {
matches!(self, &DependencyRequirement::Required)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct DepInfo {
pub version: VersionRange,
pub requirement: DependencyRequirement,
pub description: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Assets {

View File

@@ -7,6 +7,7 @@ use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncSeek, AsyncWriteExt};
use tokio::process::Command;
use crate::dependencies::{DepInfo, Dependencies};
use crate::prelude::*;
use crate::s9pk::manifest::Manifest;
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
@@ -347,7 +348,21 @@ impl From<ManifestV1> for Manifest {
.map(|(id, _)| id.clone())
.collect(),
alerts: value.alerts,
dependencies: value.dependencies,
dependencies: Dependencies(
value
.dependencies
.into_iter()
.map(|(id, value)| {
(
id,
DepInfo {
description: value.description,
optional: !value.requirement.required(),
},
)
})
.collect(),
),
hardware_requirements: value.hardware_requirements,
git_hash: value.git_hash,
os_version: value.eos_version,

View File

@@ -5,6 +5,7 @@ use helpers::const_true;
pub use models::PackageId;
use models::{ImageId, VolumeId};
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use url::Url;
use crate::dependencies::Dependencies;
@@ -18,21 +19,28 @@ fn current_version() -> Version {
Current::new().semver().into()
}
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct Manifest {
pub id: PackageId,
pub title: String,
#[ts(type = "string")]
pub version: Version,
pub release_notes: String,
pub license: String, // type of license
#[serde(default)]
pub replaces: Vec<String>,
#[ts(type = "string")]
pub wrapper_repo: Url,
#[ts(type = "string")]
pub upstream_repo: Url,
#[ts(type = "string")]
pub support_site: Url,
#[ts(type = "string")]
pub marketing_site: Url,
#[ts(type = "string | null")]
pub donation_url: Option<Url>,
pub description: Description,
pub images: Vec<ImageId>,
@@ -45,23 +53,28 @@ pub struct Manifest {
#[serde(default)]
pub hardware_requirements: HardwareRequirements,
#[serde(default)]
#[ts(type = "string | null")]
pub git_hash: Option<GitHash>,
#[serde(default = "current_version")]
#[ts(type = "string")]
pub os_version: Version,
#[serde(default = "const_true")]
pub has_config: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct HardwareRequirements {
#[serde(default)]
#[ts(type = "{ [key: string]: string }")]
device: BTreeMap<String, Regex>,
ram: Option<u64>,
pub arch: Option<Vec<String>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct Description {
pub short: String,
pub long: String,
@@ -84,8 +97,9 @@ impl Description {
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct Alerts {
pub install: Option<String>,
pub uninstall: Option<String>,

View File

@@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize};
use start_stop::StartStop;
use tokio::sync::Notify;
use crate::action::ActionResult;
use crate::config::action::ConfigRes;
use crate::context::{CliContext, RpcContext};
use crate::core::rpc_continuations::RequestGuid;
@@ -30,6 +29,7 @@ use crate::status::health_check::HealthCheckResult;
use crate::status::MainStatus;
use crate::util::actor::{Actor, BackgroundJobs, SimpleActor};
use crate::volume::data_dir;
use crate::{action::ActionResult, util::serde::Pem};
pub mod cli;
mod config;
@@ -280,7 +280,7 @@ impl Service {
entry
.as_state_info_mut()
.ser(&PackageState::Installed(InstalledState { manifest }))?;
entry.as_developer_key_mut().ser(&developer_key)?;
entry.as_developer_key_mut().ser(&Pem::new(developer_key))?;
entry.as_icon_mut().ser(&icon)?;
// TODO: marketplace url
// TODO: dependency info

View File

@@ -116,6 +116,16 @@ impl PersistentContainer {
.await?;
let mut volumes = BTreeMap::new();
for volume in &s9pk.as_manifest().volumes {
let mountpoint = lxc_container
.rootfs_dir()
.join("media/startos/volumes")
.join(volume);
tokio::fs::create_dir_all(&mountpoint).await?;
Command::new("chown")
.arg("100000:100000")
.arg(&mountpoint)
.invoke(crate::ErrorKind::Filesystem)
.await?;
let mount = MountGuard::mount(
&IdMapped::new(
Bind::new(data_dir(&ctx.datadir, &s9pk.as_manifest().id, volume)),
@@ -123,10 +133,7 @@ impl PersistentContainer {
100000,
65536,
),
lxc_container
.rootfs_dir()
.join("media/startos/volumes")
.join(volume),
mountpoint,
MountType::ReadWrite,
)
.await?;
@@ -134,6 +141,16 @@ impl PersistentContainer {
}
let mut assets = BTreeMap::new();
for asset in &s9pk.as_manifest().assets {
let mountpoint = lxc_container
.rootfs_dir()
.join("media/startos/assets")
.join(asset);
tokio::fs::create_dir_all(&mountpoint).await?;
Command::new("chown")
.arg("100000:100000")
.arg(&mountpoint)
.invoke(crate::ErrorKind::Filesystem)
.await?;
assets.insert(
asset.clone(),
MountGuard::mount(
@@ -145,10 +162,7 @@ impl PersistentContainer {
)
.join(asset),
),
lxc_container
.rootfs_dir()
.join("media/startos/assets")
.join(asset),
mountpoint,
MountType::ReadWrite,
)
.await?,

View File

@@ -11,7 +11,7 @@ use clap::Parser;
use emver::VersionRange;
use imbl::OrdMap;
use imbl_value::{json, InternedString};
use models::{ActionId, HealthCheckId, ImageId, PackageId, VolumeId};
use models::{ActionId, HealthCheckId, HostId, ImageId, PackageId, VolumeId};
use patch_db::json_ptr::JsonPointer;
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
@@ -20,16 +20,18 @@ use ts_rs::TS;
use url::Url;
use crate::db::model::package::{
CurrentDependencies, CurrentDependencyInfo, ExposedUI, StoreExposedUI,
ActionMetadata, CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind,
};
use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::net::host::binding::BindOptions;
use crate::net::host::HostKind;
use crate::prelude::*;
use crate::s9pk::rpc::SKIP_ENV;
use crate::service::cli::ContainerCliContext;
use crate::service::ServiceActorSeed;
use crate::status::health_check::{HealthCheckResult, HealthCheckString};
use crate::status::health_check::HealthCheckResult;
use crate::status::MainStatus;
use crate::util::clap::FromStrParser;
use crate::util::{new_guid, Invoke};
@@ -114,7 +116,6 @@ pub fn service_effect_handler() -> ParentHandler {
"exposeForDependents",
from_fn_async(expose_for_dependents).no_cli(),
)
.subcommand("exposeUi", from_fn_async(expose_ui).no_cli())
.subcommand(
"createOverlayedImage",
from_fn_async(create_overlayed_image)
@@ -186,21 +187,7 @@ struct GetServicePortForwardParams {
package_id: Option<PackageId>,
internal_port: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct BindOptionsSecure {
ssl: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct BindOptions {
scheme: Option<String>,
preferred_external_port: u32,
add_ssl: Option<AddSslOptions>,
secure: Option<BindOptionsSecure>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
@@ -255,14 +242,6 @@ struct ListServiceInterfacesParams {
struct RemoveAddressParams {
id: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
enum AllowedStatuses {
OnlyRunning, // onlyRunning
OnlyStopped,
Any,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
@@ -275,21 +254,9 @@ struct ExportActionParams {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ActionMetadata {
name: String,
description: String,
warning: Option<String>,
disabled: bool,
#[ts(type = "{[key: string]: any}")]
input: Value,
allowed_statuses: AllowedStatuses,
group: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct RemoveActionParams {
id: String,
#[ts(type = "string")]
id: ActionId,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
@@ -388,11 +355,48 @@ async fn list_service_interfaces(
async fn remove_address(context: EffectContext, data: RemoveAddressParams) -> Result<Value, Error> {
todo!()
}
async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<Value, Error> {
todo!()
async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
context
.ctx
.db
.mutate(|db| {
let model = db
.as_public_mut()
.as_package_data_mut()
.as_idx_mut(&package_id)
.or_not_found(&package_id)?
.as_actions_mut();
let mut value = model.de()?;
value
.insert(data.id, data.metadata)
.map(|_| ())
.unwrap_or_default();
model.ser(&value)
})
.await?;
Ok(())
}
async fn remove_action(context: EffectContext, data: RemoveActionParams) -> Result<Value, Error> {
todo!()
async fn remove_action(context: EffectContext, data: RemoveActionParams) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
context
.ctx
.db
.mutate(|db| {
let model = db
.as_public_mut()
.as_package_data_mut()
.as_idx_mut(&package_id)
.or_not_found(&package_id)?
.as_actions_mut();
let mut value = model.de()?;
value.remove(&data.id).map(|_| ()).unwrap_or_default();
model.ser(&value)
})
.await?;
Ok(())
}
async fn reverse_proxy(context: EffectContext, data: ReverseProxyParams) -> Result<Value, Error> {
todo!()
@@ -431,35 +435,16 @@ async fn get_host_info(
async fn clear_bindings(context: EffectContext, _: Empty) -> Result<Value, Error> {
todo!()
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
enum BindKind {
Static,
Single,
Multi,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
struct AddSslOptions {
scheme: Option<String>,
preferred_external_port: u32,
add_x_forwarded_headers: Option<bool>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
struct BindParams {
kind: BindKind,
id: String,
internal_port: u32,
scheme: String,
preferred_external_port: u32,
add_ssl: Option<AddSslOptions>,
secure: Option<BindOptionsSecure>,
kind: HostKind,
id: HostId,
internal_port: u16,
#[serde(flatten)]
options: BindOptions,
}
async fn bind(_: AnyContext, BindParams { .. }: BindParams) -> Result<Value, Error> {
todo!()
@@ -699,23 +684,6 @@ async fn expose_for_dependents(
Ok(())
}
async fn expose_ui(context: EffectContext, params: StoreExposedUI) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
context
.ctx
.db
.mutate(|db| {
db.as_public_mut()
.as_package_data_mut()
.as_idx_mut(&package_id)
.or_not_found(&package_id)?
.as_store_exposed_ui_mut()
.ser(&params)
})
.await?;
Ok(())
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
@@ -919,19 +887,14 @@ async fn set_main_status(context: EffectContext, params: SetMainStatus) -> Resul
#[serde(rename_all = "camelCase")]
#[ts(export)]
struct SetHealth {
#[ts(type = "string")]
name: HealthCheckId,
status: HealthCheckString,
message: Option<String>,
id: HealthCheckId,
#[serde(flatten)]
result: HealthCheckResult,
}
async fn set_health(
context: EffectContext,
SetHealth {
name,
status,
message,
}: SetHealth,
SetHealth { id, result }: SetHealth,
) -> Result<Value, Error> {
let context = context.deref()?;
@@ -940,43 +903,22 @@ async fn set_health(
.ctx
.db
.mutate(move |db| {
let mut main = db
.as_public()
.as_package_data()
.as_idx(package_id)
.or_not_found(package_id)?
.as_status()
.as_main()
.de()?;
match &mut main {
&mut MainStatus::Running { ref mut health, .. }
| &mut MainStatus::BackingUp { ref mut health, .. } => {
health.remove(&name);
health.insert(
name,
match status {
HealthCheckString::Disabled => HealthCheckResult::Disabled,
HealthCheckString::Passing => HealthCheckResult::Success,
HealthCheckString::Starting => HealthCheckResult::Starting,
HealthCheckString::Warning => HealthCheckResult::Loading {
message: message.unwrap_or_default(),
},
HealthCheckString::Failure => HealthCheckResult::Failure {
error: message.unwrap_or_default(),
},
},
);
}
_ => return Ok(()),
};
db.as_public_mut()
.as_package_data_mut()
.as_idx_mut(package_id)
.or_not_found(package_id)?
.as_status_mut()
.as_main_mut()
.ser(&main)
.mutate(|main| {
match main {
&mut MainStatus::Running { ref mut health, .. }
| &mut MainStatus::BackingUp { ref mut health, .. } => {
health.insert(id, result);
}
_ => (),
}
Ok(())
})
})
.await?;
Ok(json!(()))
@@ -1101,7 +1043,7 @@ enum DependencyRequirement {
#[ts(type = "string")]
version_spec: VersionRange,
#[ts(type = "string")]
url: Url,
registry_url: Url,
},
#[serde(rename_all = "camelCase")]
Exists {
@@ -1110,7 +1052,7 @@ enum DependencyRequirement {
#[ts(type = "string")]
version_spec: VersionRange,
#[ts(type = "string")]
url: Url,
registry_url: Url,
},
}
// filebrowser:exists,bitcoind:running:foo+bar+baz
@@ -1120,7 +1062,7 @@ impl FromStr for DependencyRequirement {
match s.split_once(':') {
Some((id, "e")) | Some((id, "exists")) => Ok(Self::Exists {
id: id.parse()?,
url: "".parse()?, // TODO
registry_url: "".parse()?, // TODO
version_spec: "*".parse()?, // TODO
}),
Some((id, rest)) => {
@@ -1144,14 +1086,14 @@ impl FromStr for DependencyRequirement {
Ok(Self::Running {
id: id.parse()?,
health_checks,
url: "".parse()?, // TODO
registry_url: "".parse()?, // TODO
version_spec: "*".parse()?, // TODO
})
}
None => Ok(Self::Running {
id: s.parse()?,
health_checks: BTreeSet::new(),
url: "".parse()?, // TODO
registry_url: "".parse()?, // TODO
version_spec: "*".parse()?, // TODO
}),
}
@@ -1187,20 +1129,31 @@ async fn set_dependencies(
.map(|dependency| match dependency {
DependencyRequirement::Exists {
id,
url,
version_spec,
} => (id, CurrentDependencyInfo::Exists { url, version_spec }),
DependencyRequirement::Running {
id,
health_checks,
url,
registry_url,
version_spec,
} => (
id,
CurrentDependencyInfo::Running {
url,
CurrentDependencyInfo {
kind: CurrentDependencyKind::Exists,
registry_url,
version_spec,
health_checks,
icon: todo!(),
title: todo!(),
},
),
DependencyRequirement::Running {
id,
health_checks,
registry_url,
version_spec,
} => (
id,
CurrentDependencyInfo {
kind: CurrentDependencyKind::Running { health_checks },
registry_url,
version_spec,
icon: todo!(),
title: todo!(),
},
),
})

View File

@@ -27,6 +27,7 @@ use crate::s9pk::merkle_archive::source::FileSource;
use crate::s9pk::S9pk;
use crate::service::{LoadDisposition, Service};
use crate::status::{MainStatus, Status};
use crate::util::serde::Pem;
pub type DownloadInstallFuture = BoxFuture<'static, Result<InstallFuture, Error>>;
pub type InstallFuture = BoxFuture<'static, Result<(), Error>>;
@@ -167,14 +168,13 @@ impl ServiceMap {
dependency_config_errors: Default::default(),
},
marketplace_url: None,
developer_key,
developer_key: Pem::new(developer_key),
icon,
last_backup: None,
dependency_info: Default::default(),
current_dependencies: Default::default(),
interface_addresses: Default::default(),
actions: Default::default(),
service_interfaces: Default::default(),
hosts: Default::default(),
store_exposed_ui: Default::default(),
store_exposed_dependents: Default::default(),
},
)?;

View File

@@ -1,35 +1,52 @@
pub use models::HealthCheckId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)]
#[serde(rename_all = "camelCase")]
pub struct HealthCheckResult {
pub name: String,
#[serde(flatten)]
pub kind: HealthCheckResultKind,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "result")]
pub enum HealthCheckResult {
Success,
Disabled,
Starting,
pub enum HealthCheckResultKind {
Success { message: Option<String> },
Disabled { message: Option<String> },
Starting { message: Option<String> },
Loading { message: String },
Failure { error: String },
Failure { message: String },
}
impl std::fmt::Display for HealthCheckResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HealthCheckResult::Success => write!(f, "Succeeded"),
HealthCheckResult::Disabled => write!(f, "Disabled"),
HealthCheckResult::Starting => write!(f, "Starting"),
HealthCheckResult::Loading { message } => write!(f, "Loading ({})", message),
HealthCheckResult::Failure { error } => write!(f, "Failed ({})", error),
let name = &self.name;
match &self.kind {
HealthCheckResultKind::Success { message } => {
if let Some(message) = message {
write!(f, "{name}: Succeeded ({message})")
} else {
write!(f, "{name}: Succeeded")
}
}
HealthCheckResultKind::Disabled { message } => {
if let Some(message) = message {
write!(f, "{name}: Disabled ({message})")
} else {
write!(f, "{name}: Disabled")
}
}
HealthCheckResultKind::Starting { message } => {
if let Some(message) = message {
write!(f, "{name}: Starting ({message})")
} else {
write!(f, "{name}: Starting")
}
}
HealthCheckResultKind::Loading { message } => write!(f, "{name}: Loading ({message})"),
HealthCheckResultKind::Failure { message } => write!(f, "{name}: Failed ({message})"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub enum HealthCheckString {
Passing,
Disabled,
Starting,
Warning,
Failure,
}

View File

@@ -4,15 +4,17 @@ use chrono::{DateTime, Utc};
use imbl::OrdMap;
use models::PackageId;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use self::health_check::HealthCheckId;
use crate::prelude::*;
use crate::status::health_check::HealthCheckResult;
pub mod health_check;
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct Status {
pub configured: bool,
pub main: MainStatus,
@@ -20,9 +22,9 @@ pub struct Status {
pub dependency_config_errors: DependencyConfigErrors,
}
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, Default)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, Default, TS)]
#[model = "Model<Self>"]
#[ts(export)]
pub struct DependencyConfigErrors(pub BTreeMap<PackageId, String>);
impl Map for DependencyConfigErrors {
type Key = PackageId;
@@ -35,22 +37,29 @@ impl Map for DependencyConfigErrors {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, TS)]
#[serde(tag = "status")]
#[serde(rename_all = "camelCase")]
pub enum MainStatus {
Stopped,
Restarting,
#[serde(rename_all = "camelCase")]
Stopping {
timeout: crate::util::serde::Duration,
},
Starting,
#[serde(rename_all = "camelCase")]
Running {
#[ts(type = "string")]
started: DateTime<Utc>,
#[ts(as = "BTreeMap<HealthCheckId, HealthCheckResult>")]
health: OrdMap<HealthCheckId, HealthCheckResult>,
},
#[serde(rename_all = "camelCase")]
BackingUp {
#[ts(type = "string | null")]
started: Option<DateTime<Utc>>,
#[ts(as = "BTreeMap<HealthCheckId, HealthCheckResult>")]
health: OrdMap<HealthCheckId, HealthCheckResult>,
},
}

View File

@@ -3,6 +3,7 @@ use std::collections::BTreeSet;
use imbl::OrdMap;
use tokio::process::Command;
use ts_rs::TS;
use crate::prelude::*;
use crate::util::Invoke;
@@ -13,7 +14,10 @@ pub const GOVERNOR_HEIRARCHY: &[Governor] = &[
Governor(Cow::Borrowed("conservative")),
];
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, TS,
)]
#[ts(export, type = "string")]
pub struct Governor(Cow<'static, str>);
impl std::str::FromStr for Governor {
type Err = std::convert::Infallible;

View File

@@ -15,6 +15,7 @@ use serde::de::DeserializeOwned;
use serde::ser::{SerializeMap, SerializeSeq};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use ts_rs::TS;
use super::IntoDoubleEndedIterator;
use crate::util::clap::FromStrParser;
@@ -633,7 +634,8 @@ where
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, TS)]
#[ts(export, type = "string")]
pub struct Duration(std::time::Duration);
impl Deref for Duration {
type Target = std::time::Duration;
@@ -1128,6 +1130,18 @@ impl PemEncoding for ssh_key::PrivateKey {
}
}
impl PemEncoding for ed25519_dalek::VerifyingKey {
fn from_pem<E: serde::de::Error>(pem: &str) -> Result<Self, E> {
use ed25519_dalek::pkcs8::DecodePublicKey;
ed25519_dalek::VerifyingKey::from_public_key_pem(pem).map_err(E::custom)
}
fn to_pem<E: serde::ser::Error>(&self) -> Result<String, E> {
use ed25519_dalek::pkcs8::EncodePublicKey;
self.to_public_key_pem(pkcs8::LineEnding::LF)
.map_err(E::custom)
}
}
pub mod pem {
use serde::{Deserialize, Deserializer, Serializer};
@@ -1163,7 +1177,8 @@ impl<T: PemEncoding> Pem<T> {
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, TS)]
#[ts(export, type = "string | number[]")]
pub struct MaybeUtf8String(pub Vec<u8>);
impl std::fmt::Debug for MaybeUtf8String {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {