mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
Merge branch 'next/minor' of github.com:Start9Labs/start-os into rebase/feat/domains
This commit is contained in:
@@ -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?,
|
||||
)?
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
115
core/startos/src/net/service_interface.rs
Normal file
115
core/startos/src/net/service_interface.rs
Normal 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,
|
||||
}
|
||||
@@ -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"];
|
||||
|
||||
|
||||
@@ -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>),
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -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(¶ms)
|
||||
})
|
||||
.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!(),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user