mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Update/040 types (#2845)
* small type changes and clear todos * handle notifications and metrics * wip * fixes * migration * dedup all urls * better handling of clearnet ips * add rfkill dependency --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -43,6 +43,7 @@ podman
|
||||
postgresql
|
||||
psmisc
|
||||
qemu-guest-agent
|
||||
rfkill
|
||||
rsync
|
||||
samba-common-bin
|
||||
smartmontools
|
||||
|
||||
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -5952,7 +5952,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "start-os"
|
||||
version = "0.3.6-alpha.15"
|
||||
version = "0.4.0-alpha.0"
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"async-acme",
|
||||
|
||||
@@ -14,7 +14,7 @@ keywords = [
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.3.6-alpha.15" # VERSION_BUMP
|
||||
version = "0.4.0-alpha.0" # VERSION_BUMP
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -59,7 +59,13 @@ impl AccountInfo {
|
||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
||||
let password = db.as_private().as_password().de()?;
|
||||
let key_store = db.as_private().as_key_store();
|
||||
let tor_addrs = db.as_public().as_server_info().as_host().as_onions().de()?;
|
||||
let tor_addrs = db
|
||||
.as_public()
|
||||
.as_server_info()
|
||||
.as_network()
|
||||
.as_host()
|
||||
.as_onions()
|
||||
.de()?;
|
||||
let tor_keys = tor_addrs
|
||||
.into_iter()
|
||||
.map(|tor_addr| key_store.as_onion().get_key(&tor_addr))
|
||||
@@ -89,13 +95,17 @@ impl AccountInfo {
|
||||
server_info
|
||||
.as_pubkey_mut()
|
||||
.ser(&self.ssh_key.public_key().to_openssh()?)?;
|
||||
server_info.as_host_mut().as_onions_mut().ser(
|
||||
&self
|
||||
.tor_keys
|
||||
.iter()
|
||||
.map(|tor_key| tor_key.public().get_onion_address())
|
||||
.collect(),
|
||||
)?;
|
||||
server_info
|
||||
.as_network_mut()
|
||||
.as_host_mut()
|
||||
.as_onions_mut()
|
||||
.ser(
|
||||
&self
|
||||
.tor_keys
|
||||
.iter()
|
||||
.map(|tor_key| tor_key.public().get_onion_address())
|
||||
.collect(),
|
||||
)?;
|
||||
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
||||
db.as_private_mut()
|
||||
.as_ssh_privkey_mut()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::cmp::max;
|
||||
use std::ffi::OsString;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -14,7 +13,6 @@ use crate::context::config::ServerConfig;
|
||||
use crate::context::rpc::InitRpcContextPhases;
|
||||
use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
||||
use crate::net::network_interface::SelfContainedNetworkInterfaceListener;
|
||||
use crate::net::utils::ipv6_is_local;
|
||||
use crate::net::web_server::{Acceptor, UpgradableListener, WebServer};
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::launch_metrics_task;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::backtrace;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::future::Future;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
@@ -41,7 +40,7 @@ use crate::service::effects::callbacks::ServiceCallbacks;
|
||||
use crate::service::ServiceMap;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::util::lshw::LshwDevice;
|
||||
use crate::util::sync::SyncMutex;
|
||||
use crate::util::sync::{SyncMutex, Watch};
|
||||
|
||||
pub struct RpcContextSeed {
|
||||
is_closed: AtomicBool,
|
||||
@@ -57,14 +56,14 @@ pub struct RpcContextSeed {
|
||||
pub os_net_service: NetService,
|
||||
pub s9pk_arch: Option<&'static str>,
|
||||
pub services: ServiceMap,
|
||||
pub metrics_cache: RwLock<Option<crate::system::Metrics>>,
|
||||
pub metrics_cache: Watch<Option<crate::system::Metrics>>,
|
||||
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
||||
pub tor_socks: SocketAddr,
|
||||
pub lxc_manager: Arc<LxcManager>,
|
||||
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
||||
pub rpc_continuations: RpcContinuations,
|
||||
pub callbacks: ServiceCallbacks,
|
||||
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
||||
pub wifi_manager: Arc<RwLock<Option<WpaCli>>>,
|
||||
pub current_secret: Arc<Jwk>,
|
||||
pub client: Client,
|
||||
pub start_time: Instant,
|
||||
@@ -174,7 +173,7 @@ impl RpcContext {
|
||||
tracing::info!("Initialized Net Controller");
|
||||
|
||||
let services = ServiceMap::default();
|
||||
let metrics_cache = RwLock::<Option<crate::system::Metrics>>::new(None);
|
||||
let metrics_cache = Watch::<Option<crate::system::Metrics>>::new(None);
|
||||
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
||||
|
||||
let crons = SyncMutex::new(BTreeMap::new());
|
||||
@@ -245,9 +244,7 @@ impl RpcContext {
|
||||
open_authed_continuations: OpenAuthedContinuations::new(),
|
||||
rpc_continuations: RpcContinuations::new(),
|
||||
callbacks: Default::default(),
|
||||
wifi_manager: wifi_interface
|
||||
.clone()
|
||||
.map(|i| Arc::new(RwLock::new(WpaCli::init(i)))),
|
||||
wifi_manager: Arc::new(RwLock::new(wifi_interface.clone().map(|i| WpaCli::init(i)))),
|
||||
current_secret: Arc::new(
|
||||
Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).map_err(|e| {
|
||||
tracing::debug!("{:?}", e);
|
||||
@@ -488,7 +485,7 @@ impl Drop for RpcContext {
|
||||
let count = Arc::strong_count(&self.0) - 1;
|
||||
tracing::info!("RpcContext dropped. {} left.", count);
|
||||
if count > 0 {
|
||||
tracing::debug!("{}", backtrace::Backtrace::force_capture());
|
||||
tracing::debug!("{}", std::backtrace::Backtrace::force_capture());
|
||||
tracing::debug!("{:?}", eyre!(""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,44 +48,50 @@ impl Public {
|
||||
id: account.server_id.clone(),
|
||||
version: Current::default().semver(),
|
||||
hostname: account.hostname.no_dot_host_name(),
|
||||
host: Host {
|
||||
bindings: [(
|
||||
80,
|
||||
BindInfo {
|
||||
enabled: false,
|
||||
options: BindOptions {
|
||||
preferred_external_port: 80,
|
||||
add_ssl: Some(AddSslOptions {
|
||||
preferred_external_port: 443,
|
||||
alpn: Some(AlpnInfo::Specified(vec![
|
||||
MaybeUtf8String("http/1.1".into()),
|
||||
MaybeUtf8String("h2".into()),
|
||||
])),
|
||||
}),
|
||||
secure: None,
|
||||
},
|
||||
net: NetInfo {
|
||||
assigned_port: None,
|
||||
assigned_ssl_port: Some(443),
|
||||
public: false,
|
||||
},
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
onions: account
|
||||
.tor_keys
|
||||
.iter()
|
||||
.map(|k| k.public().get_onion_address())
|
||||
.collect(),
|
||||
domains: BTreeMap::new(),
|
||||
hostname_info: BTreeMap::new(),
|
||||
},
|
||||
last_backup: None,
|
||||
package_version_compat: Current::default().compat().clone(),
|
||||
post_init_migration_todos: BTreeSet::new(),
|
||||
network_interfaces: BTreeMap::new(),
|
||||
acme: BTreeMap::new(),
|
||||
network: NetworkInfo {
|
||||
host: Host {
|
||||
bindings: [(
|
||||
80,
|
||||
BindInfo {
|
||||
enabled: false,
|
||||
options: BindOptions {
|
||||
preferred_external_port: 80,
|
||||
add_ssl: Some(AddSslOptions {
|
||||
preferred_external_port: 443,
|
||||
alpn: Some(AlpnInfo::Specified(vec![
|
||||
MaybeUtf8String("http/1.1".into()),
|
||||
MaybeUtf8String("h2".into()),
|
||||
])),
|
||||
}),
|
||||
secure: None,
|
||||
},
|
||||
net: NetInfo {
|
||||
assigned_port: None,
|
||||
assigned_ssl_port: Some(443),
|
||||
public: false,
|
||||
},
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
onions: account
|
||||
.tor_keys
|
||||
.iter()
|
||||
.map(|k| k.public().get_onion_address())
|
||||
.collect(),
|
||||
domains: BTreeMap::new(),
|
||||
hostname_info: BTreeMap::new(),
|
||||
},
|
||||
wifi: WifiInfo {
|
||||
enabled: true,
|
||||
..Default::default()
|
||||
},
|
||||
network_interfaces: BTreeMap::new(),
|
||||
acme: BTreeMap::new(),
|
||||
},
|
||||
status_info: ServerStatus {
|
||||
backup_progress: None,
|
||||
updated: false,
|
||||
@@ -93,7 +99,6 @@ impl Public {
|
||||
shutting_down: false,
|
||||
restarting: false,
|
||||
},
|
||||
wifi: WifiInfo::default(),
|
||||
unread_notification_count: 0,
|
||||
password_hash: account.password.clone(),
|
||||
pubkey: ssh_key::PublicKey::from(&account.ssh_key)
|
||||
@@ -145,7 +150,6 @@ pub struct ServerInfo {
|
||||
pub id: String,
|
||||
#[ts(type = "string")]
|
||||
pub hostname: InternedString,
|
||||
pub host: Host,
|
||||
#[ts(type = "string")]
|
||||
pub version: Version,
|
||||
#[ts(type = "string")]
|
||||
@@ -154,14 +158,9 @@ pub struct ServerInfo {
|
||||
pub post_init_migration_todos: BTreeSet<Version>,
|
||||
#[ts(type = "string | null")]
|
||||
pub last_backup: Option<DateTime<Utc>>,
|
||||
#[ts(as = "BTreeMap::<String, NetworkInterfaceInfo>")]
|
||||
#[serde(default)]
|
||||
pub network_interfaces: BTreeMap<InternedString, NetworkInterfaceInfo>,
|
||||
#[serde(default)]
|
||||
pub acme: BTreeMap<AcmeProvider, AcmeSettings>,
|
||||
pub network: NetworkInfo,
|
||||
#[serde(default)]
|
||||
pub status_info: ServerStatus,
|
||||
pub wifi: WifiInfo,
|
||||
#[ts(type = "number")]
|
||||
pub unread_notification_count: u64,
|
||||
pub password_hash: String,
|
||||
@@ -178,17 +177,32 @@ pub struct ServerInfo {
|
||||
pub devices: Vec<LshwDevice>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct NetworkInfo {
|
||||
pub wifi: WifiInfo,
|
||||
pub host: Host,
|
||||
#[ts(as = "BTreeMap::<String, NetworkInterfaceInfo>")]
|
||||
#[serde(default)]
|
||||
pub network_interfaces: BTreeMap<InternedString, NetworkInterfaceInfo>,
|
||||
#[serde(default)]
|
||||
pub acme: BTreeMap<AcmeProvider, AcmeSettings>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct NetworkInterfaceInfo {
|
||||
pub public: Option<bool>,
|
||||
pub inbound: Option<bool>,
|
||||
pub outbound: Option<bool>,
|
||||
pub ip_info: Option<IpInfo>,
|
||||
}
|
||||
impl NetworkInterfaceInfo {
|
||||
pub fn public(&self) -> bool {
|
||||
self.public.unwrap_or_else(|| {
|
||||
pub fn inbound(&self) -> bool {
|
||||
self.inbound.unwrap_or_else(|| {
|
||||
!self.ip_info.as_ref().map_or(true, |ip_info| {
|
||||
let ip4s = ip_info
|
||||
.subnets
|
||||
@@ -224,6 +238,8 @@ impl NetworkInterfaceInfo {
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IpInfo {
|
||||
#[ts(type = "string")]
|
||||
pub name: InternedString,
|
||||
pub scope_id: u32,
|
||||
pub device_type: Option<NetworkInterfaceType>,
|
||||
#[ts(type = "string[]")]
|
||||
@@ -276,6 +292,7 @@ pub struct ServerStatus {
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct WifiInfo {
|
||||
pub enabled: bool,
|
||||
pub interface: Option<String>,
|
||||
pub ssids: BTreeSet<String>,
|
||||
pub selected: Option<String>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -267,7 +267,7 @@ where
|
||||
T::Key: FromStr + Ord + Clone,
|
||||
Error: From<<T::Key as FromStr>::Err>,
|
||||
{
|
||||
pub fn keys(&self) -> Result<Vec<T::Key>, Error> {
|
||||
pub fn keys(&self) -> Result<BTreeSet<T::Key>, Error> {
|
||||
use serde::de::Error;
|
||||
match &self.value {
|
||||
Value::Object(o) => o
|
||||
|
||||
@@ -415,7 +415,11 @@ pub async fn init(
|
||||
let wifi_interface = find_wifi_iface().await?;
|
||||
let wifi = db
|
||||
.mutate(|db| {
|
||||
let wifi = db.as_public_mut().as_server_info_mut().as_wifi_mut();
|
||||
let wifi = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_wifi_mut();
|
||||
wifi.as_interface_mut().ser(&wifi_interface)?;
|
||||
wifi.de()
|
||||
})
|
||||
|
||||
@@ -89,7 +89,7 @@ use crate::context::{
|
||||
use crate::disk::fsck::RequiresReboot;
|
||||
use crate::net::net;
|
||||
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||
use crate::util::serde::HandlerExtSerde;
|
||||
use crate::util::serde::{HandlerExtSerde, WithIoFormat};
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -242,10 +242,18 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.subcommand(
|
||||
"metrics",
|
||||
from_fn_async(system::metrics)
|
||||
.with_display_serializable()
|
||||
.with_about("Display information about the server i.e. temperature, RAM, CPU, and disk usage")
|
||||
.with_call_remote::<CliContext>()
|
||||
ParentHandler::<C, WithIoFormat<Empty>>::new()
|
||||
.root_handler(
|
||||
from_fn_async(system::metrics)
|
||||
.with_display_serializable()
|
||||
.with_about("Display information about the server i.e. temperature, RAM, CPU, and disk usage")
|
||||
.with_call_remote::<CliContext>()
|
||||
)
|
||||
.subcommand(
|
||||
"follow",
|
||||
from_fn_async(system::metrics_follow)
|
||||
.no_cli()
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
"shutdown",
|
||||
|
||||
@@ -255,7 +255,7 @@ pub async fn init(
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_server_info_mut().as_network_mut()
|
||||
.as_acme_mut()
|
||||
.insert(&provider, &AcmeSettings { contact })
|
||||
})
|
||||
@@ -276,7 +276,7 @@ pub async fn remove(
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_server_info_mut().as_network_mut()
|
||||
.as_acme_mut()
|
||||
.remove(&provider)
|
||||
})
|
||||
|
||||
@@ -155,7 +155,7 @@ impl LanPortForwardController {
|
||||
let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| {
|
||||
ip_info
|
||||
.iter()
|
||||
.map(|(iface, info)| (iface.clone(), info.public()))
|
||||
.map(|(iface, info)| (iface.clone(), info.inbound()))
|
||||
.collect()
|
||||
});
|
||||
let mut reply: Option<oneshot::Sender<Result<(), Error>>> = None;
|
||||
@@ -175,7 +175,7 @@ impl LanPortForwardController {
|
||||
interfaces = ip_info.peek(|ip_info| {
|
||||
ip_info
|
||||
.iter()
|
||||
.map(|(iface, info)| (iface.clone(), info.public()))
|
||||
.map(|(iface, info)| (iface.clone(), info.inbound()))
|
||||
.collect()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ pub async fn add_domain<Kind: HostApiKind>(
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
if let Some(acme) = &acme {
|
||||
if !db.as_public().as_server_info().as_acme().contains_key(&acme)? {
|
||||
if !db.as_public().as_server_info().as_network().as_acme().contains_key(&acme)? {
|
||||
return Err(Error::new(eyre!("unknown acme provider {}, please run acme.init for this provider first", acme.0), ErrorKind::InvalidRequest));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,11 @@ pub fn host_for<'a>(
|
||||
host_id: &HostId,
|
||||
) -> Result<&'a mut Model<Host>, Error> {
|
||||
let Some(package_id) = package_id else {
|
||||
return Ok(db.as_public_mut().as_server_info_mut().as_host_mut());
|
||||
return Ok(db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_host_mut());
|
||||
};
|
||||
fn host_info<'a>(
|
||||
db: &'a mut DatabaseModel,
|
||||
@@ -122,7 +126,7 @@ pub fn host_for<'a>(
|
||||
}
|
||||
|
||||
pub fn all_hosts(db: &DatabaseModel) -> impl Iterator<Item = Result<&Model<Host>, Error>> {
|
||||
[Ok(db.as_public().as_server_info().as_host())]
|
||||
[Ok(db.as_public().as_server_info().as_network().as_host())]
|
||||
.into_iter()
|
||||
.chain(
|
||||
[db.as_public().as_package_data().as_entries()]
|
||||
@@ -255,7 +259,7 @@ pub async fn list_hosts(
|
||||
ctx: RpcContext,
|
||||
_: Empty,
|
||||
package: PackageId,
|
||||
) -> Result<Vec<HostId>, Error> {
|
||||
) -> Result<BTreeSet<HostId>, Error> {
|
||||
ctx.db
|
||||
.peek()
|
||||
.await
|
||||
|
||||
@@ -188,7 +188,11 @@ impl NetServiceData {
|
||||
let host = ctrl
|
||||
.db
|
||||
.mutate(|db| {
|
||||
let host = db.as_public_mut().as_server_info_mut().as_host_mut();
|
||||
let host = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_host_mut();
|
||||
host.as_bindings_mut().mutate(|b| {
|
||||
for (internal_port, info) in b {
|
||||
if !except.contains(&BindId {
|
||||
@@ -326,7 +330,7 @@ impl NetServiceData {
|
||||
for (interface, public, ip_info) in
|
||||
net_ifaces.iter().filter_map(|(interface, info)| {
|
||||
if let Some(ip_info) = &info.ip_info {
|
||||
Some((interface, info.public(), ip_info))
|
||||
Some((interface, info.inbound(), ip_info))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -612,6 +616,7 @@ impl NetServiceData {
|
||||
.await
|
||||
.as_public()
|
||||
.as_server_info()
|
||||
.as_network()
|
||||
.as_host()
|
||||
.de()?,
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
|
||||
info.ip_info.as_ref()
|
||||
.and_then(|ip_info| ip_info.device_type)
|
||||
.map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")),
|
||||
info.public(),
|
||||
info.inbound(),
|
||||
info.ip_info.as_ref().map_or_else(
|
||||
|| "<DISCONNECTED>".to_owned(),
|
||||
|ip_info| ip_info.subnets
|
||||
@@ -86,18 +86,18 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"set-public",
|
||||
from_fn_async(set_public)
|
||||
"set-inbound",
|
||||
from_fn_async(set_inbound)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Indicate whether this interface is publicly addressable")
|
||||
.with_about("Indicate whether this interface has inbound access from the WAN")
|
||||
.with_call_remote::<CliContext>(),
|
||||
).subcommand(
|
||||
"unset-public",
|
||||
from_fn_async(unset_public)
|
||||
"unset-inbound",
|
||||
from_fn_async(unset_inbound)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Allow this interface to infer whether it is publicly addressable based on its IPv4 address")
|
||||
.with_about("Allow this interface to infer whether it has inbound access from the WAN based on its IPv4 address")
|
||||
.with_call_remote::<CliContext>(),
|
||||
).subcommand("forget",
|
||||
from_fn_async(forget_iface)
|
||||
@@ -116,36 +116,36 @@ async fn list_interfaces(
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
#[ts(export)]
|
||||
struct NetworkInterfaceSetPublicParams {
|
||||
struct NetworkInterfaceSetInboundParams {
|
||||
#[ts(type = "string")]
|
||||
interface: InternedString,
|
||||
public: Option<bool>,
|
||||
inbound: Option<bool>,
|
||||
}
|
||||
|
||||
async fn set_public(
|
||||
async fn set_inbound(
|
||||
ctx: RpcContext,
|
||||
NetworkInterfaceSetPublicParams { interface, public }: NetworkInterfaceSetPublicParams,
|
||||
NetworkInterfaceSetInboundParams { interface, inbound }: NetworkInterfaceSetInboundParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.net_controller
|
||||
.net_iface
|
||||
.set_public(&interface, Some(public.unwrap_or(true)))
|
||||
.set_inbound(&interface, Some(inbound.unwrap_or(true)))
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
#[ts(export)]
|
||||
struct UnsetPublicParams {
|
||||
struct UnsetInboundParams {
|
||||
#[ts(type = "string")]
|
||||
interface: InternedString,
|
||||
}
|
||||
|
||||
async fn unset_public(
|
||||
async fn unset_inbound(
|
||||
ctx: RpcContext,
|
||||
UnsetPublicParams { interface }: UnsetPublicParams,
|
||||
UnsetInboundParams { interface }: UnsetInboundParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.net_controller
|
||||
.net_iface
|
||||
.set_public(&interface, None)
|
||||
.set_inbound(&interface, None)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -193,6 +193,9 @@ mod active_connection {
|
||||
default_service = "org.freedesktop.NetworkManager"
|
||||
)]
|
||||
pub trait ActiveConnection {
|
||||
#[zbus(property)]
|
||||
fn id(&self) -> Result<String, Error>;
|
||||
|
||||
#[zbus(property)]
|
||||
fn state_flags(&self) -> Result<u32, Error>;
|
||||
|
||||
@@ -511,6 +514,8 @@ async fn watch_ip(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let name = InternedString::from(active_connection_proxy.id().await?);
|
||||
|
||||
let dhcp4_config = active_connection_proxy.dhcp4_config().await?;
|
||||
let ip4_proxy =
|
||||
Ip4ConfigProxy::new(&connection, ip4_config.clone()).await?;
|
||||
@@ -568,6 +573,7 @@ async fn watch_ip(
|
||||
}
|
||||
};
|
||||
Some(IpInfo {
|
||||
name: name.clone(),
|
||||
scope_id,
|
||||
device_type,
|
||||
subnets,
|
||||
@@ -579,11 +585,14 @@ async fn watch_ip(
|
||||
};
|
||||
|
||||
write_to.send_if_modified(|m| {
|
||||
let public = m.get(&iface).map_or(None, |i| i.public);
|
||||
let (inbound, outbound) = m
|
||||
.get(&iface)
|
||||
.map_or((None, None), |i| (i.inbound, i.outbound));
|
||||
m.insert(
|
||||
iface.clone(),
|
||||
NetworkInterfaceInfo {
|
||||
public,
|
||||
inbound,
|
||||
outbound,
|
||||
ip_info: ip_info.clone(),
|
||||
},
|
||||
)
|
||||
@@ -663,6 +672,7 @@ impl NetworkInterfaceController {
|
||||
db.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_network_interfaces_mut()
|
||||
.ser(info)
|
||||
})
|
||||
@@ -731,6 +741,7 @@ impl NetworkInterfaceController {
|
||||
.await
|
||||
.as_public()
|
||||
.as_server_info()
|
||||
.as_network()
|
||||
.as_network_interfaces()
|
||||
.de()
|
||||
{
|
||||
@@ -820,7 +831,7 @@ impl NetworkInterfaceController {
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
pub async fn set_public(
|
||||
pub async fn set_inbound(
|
||||
&self,
|
||||
interface: &InternedString,
|
||||
public: Option<bool>,
|
||||
@@ -828,7 +839,7 @@ impl NetworkInterfaceController {
|
||||
let mut sub = self
|
||||
.db
|
||||
.subscribe(
|
||||
"/public/serverInfo/networkInterfaces"
|
||||
"/public/serverInfo/network/networkInterfaces"
|
||||
.parse::<JsonPointer<_, _>>()
|
||||
.with_kind(ErrorKind::Database)?,
|
||||
)
|
||||
@@ -843,7 +854,7 @@ impl NetworkInterfaceController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
.public,
|
||||
.inbound,
|
||||
public,
|
||||
);
|
||||
prev != public
|
||||
@@ -861,7 +872,7 @@ impl NetworkInterfaceController {
|
||||
let mut sub = self
|
||||
.db
|
||||
.subscribe(
|
||||
"/public/serverInfo/networkInterfaces"
|
||||
"/public/serverInfo/network/networkInterfaces"
|
||||
.parse::<JsonPointer<_, _>>()
|
||||
.with_kind(ErrorKind::Database)?,
|
||||
)
|
||||
@@ -955,8 +966,10 @@ impl ListenerMap {
|
||||
) -> Result<(), Error> {
|
||||
let mut keep = BTreeSet::<SocketAddr>::new();
|
||||
for info in ip_info.values().chain([&NetworkInterfaceInfo {
|
||||
public: Some(false),
|
||||
inbound: Some(false),
|
||||
outbound: Some(false),
|
||||
ip_info: Some(IpInfo {
|
||||
name: "lo".into(),
|
||||
scope_id: 1,
|
||||
device_type: None,
|
||||
subnets: [
|
||||
@@ -969,7 +982,7 @@ impl ListenerMap {
|
||||
ntp_servers: Default::default(),
|
||||
}),
|
||||
}]) {
|
||||
if public || !info.public() {
|
||||
if public || !info.inbound() {
|
||||
if let Some(ip_info) = &info.ip_info {
|
||||
for ipnet in &ip_info.subnets {
|
||||
let addr = match ipnet.addr() {
|
||||
@@ -988,7 +1001,7 @@ impl ListenerMap {
|
||||
};
|
||||
keep.insert(addr);
|
||||
if let Some((_, is_public, wan_ip)) = self.listeners.get_mut(&addr) {
|
||||
*is_public = info.public();
|
||||
*is_public = info.inbound();
|
||||
*wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip);
|
||||
continue;
|
||||
}
|
||||
@@ -1006,7 +1019,7 @@ impl ListenerMap {
|
||||
.into(),
|
||||
)
|
||||
.with_kind(ErrorKind::Network)?,
|
||||
info.public(),
|
||||
info.inbound(),
|
||||
info.ip_info.as_ref().and_then(|i| i.wan_ip),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Weak};
|
||||
@@ -179,7 +179,7 @@ pub async fn add_key(
|
||||
Ok(key.public().get_onion_address())
|
||||
}
|
||||
|
||||
pub async fn list_keys(ctx: RpcContext) -> Result<Vec<OnionAddressV3>, Error> {
|
||||
pub async fn list_keys(ctx: RpcContext) -> Result<BTreeSet<OnionAddressV3>, Error> {
|
||||
ctx.db
|
||||
.peek()
|
||||
.await
|
||||
|
||||
@@ -461,6 +461,7 @@ impl VHostServer {
|
||||
target.acme.as_ref().and_then(|a| {
|
||||
peek.as_public()
|
||||
.as_server_info()
|
||||
.as_network()
|
||||
.as_acme()
|
||||
.as_idx(a)
|
||||
.map(|s| (domain, a, s))
|
||||
|
||||
@@ -24,21 +24,28 @@ use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
type WifiManager = Arc<RwLock<WpaCli>>;
|
||||
type WifiManager = Arc<RwLock<Option<WpaCli>>>;
|
||||
|
||||
pub fn wifi_manager(ctx: &RpcContext) -> Result<&WifiManager, Error> {
|
||||
if let Some(wifi_manager) = ctx.wifi_manager.as_ref() {
|
||||
Ok(wifi_manager)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
))
|
||||
}
|
||||
}
|
||||
// pub fn wifi_manager(ctx: &RpcContext) -> Result<&WifiManager, Error> {
|
||||
// if let Some(wifi_manager) = ctx.wifi_manager.as_ref() {
|
||||
// Ok(wifi_manager)
|
||||
// } else {
|
||||
// Err(Error::new(
|
||||
// color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
// ErrorKind::Wifi,
|
||||
// ))
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn wifi<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"set-enabled",
|
||||
from_fn_async(set_enabled)
|
||||
.no_display()
|
||||
.with_about("Enable or disable wifi")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add)
|
||||
@@ -80,6 +87,48 @@ pub fn wifi<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct SetWifiEnabledParams {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
pub async fn set_enabled(
|
||||
ctx: RpcContext,
|
||||
SetWifiEnabledParams { enabled }: SetWifiEnabledParams,
|
||||
) -> Result<(), Error> {
|
||||
if enabled {
|
||||
Command::new("rfkill")
|
||||
.arg("unblock")
|
||||
.arg("all")
|
||||
.invoke(ErrorKind::Wifi)
|
||||
.await?;
|
||||
} else {
|
||||
Command::new("rfkill")
|
||||
.arg("block")
|
||||
.arg("all")
|
||||
.invoke(ErrorKind::Wifi)
|
||||
.await?;
|
||||
}
|
||||
let iface = if let Some(man) = ctx.wifi_manager.read().await.as_ref().filter(|_| enabled) {
|
||||
Some(man.interface.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
d.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_wifi_mut()
|
||||
.as_interface_mut()
|
||||
.ser(&iface)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn available<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new().subcommand(
|
||||
"get",
|
||||
@@ -110,7 +159,7 @@ pub struct AddParams {
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Result<(), Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !ssid.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID may not have special characters"),
|
||||
@@ -130,9 +179,14 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
|
||||
password: &Psk,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!("Adding new WiFi network: '{}'", ssid.0);
|
||||
let mut wpa_supplicant = wifi_manager.write().await;
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
wpa_supplicant.add_network(db, ssid, password).await?;
|
||||
drop(wpa_supplicant);
|
||||
Ok(())
|
||||
}
|
||||
if let Err(err) = add_procedure(
|
||||
@@ -154,6 +208,7 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_wifi_mut()
|
||||
.as_ssids_mut()
|
||||
.mutate(|s| {
|
||||
@@ -173,7 +228,7 @@ pub struct SsidParams {
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<(), Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !ssid.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID may not have special characters"),
|
||||
@@ -185,10 +240,14 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
|
||||
wifi_manager: WifiManager,
|
||||
ssid: &Ssid,
|
||||
) -> Result<(), Error> {
|
||||
let wpa_supplicant = wifi_manager.read().await;
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
let current = wpa_supplicant.get_current_network().await?;
|
||||
drop(wpa_supplicant);
|
||||
let mut wpa_supplicant = wifi_manager.write().await;
|
||||
let connected = wpa_supplicant.select_network(db.clone(), ssid).await?;
|
||||
if connected {
|
||||
tracing::info!("Successfully connected to WiFi: '{}'", ssid.0);
|
||||
@@ -218,7 +277,11 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let wifi = db.as_public_mut().as_server_info_mut().as_wifi_mut();
|
||||
let wifi = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_wifi_mut();
|
||||
wifi.as_ssids_mut().mutate(|s| {
|
||||
s.insert(ssid.clone());
|
||||
Ok(())
|
||||
@@ -231,17 +294,22 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<(), Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !ssid.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID may not have special characters"),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
let wpa_supplicant = wifi_manager.read().await;
|
||||
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
let current = wpa_supplicant.get_current_network().await?;
|
||||
drop(wpa_supplicant);
|
||||
let mut wpa_supplicant = wifi_manager.write().await;
|
||||
let ssid = Ssid(ssid);
|
||||
let is_current_being_removed = matches!(current, Some(current) if current == ssid);
|
||||
let is_current_removed_and_no_hardwire =
|
||||
@@ -254,7 +322,11 @@ pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let wifi = db.as_public_mut().as_server_info_mut().as_wifi_mut();
|
||||
let wifi = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_wifi_mut();
|
||||
wifi.as_ssids_mut().mutate(|s| {
|
||||
s.remove(&ssid.0);
|
||||
Ok(())
|
||||
@@ -379,8 +451,14 @@ fn display_wifi_list(params: WithIoFormat<Empty>, info: Vec<WifiListOut>) {
|
||||
// #[command(display(display_wifi_info))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get(ctx: RpcContext, _: Empty) -> Result<WifiListInfo, Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
let wpa_supplicant = wifi_manager.read().await;
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
let wpa_supplicant = wifi_manager.read_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_ref().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
let (list_networks, current_res, country_res, ethernet_res, signal_strengths) = tokio::join!(
|
||||
wpa_supplicant.list_networks_low(),
|
||||
wpa_supplicant.get_current_network(),
|
||||
@@ -427,8 +505,14 @@ pub async fn get(ctx: RpcContext, _: Empty) -> Result<WifiListInfo, Error> {
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_available(ctx: RpcContext, _: Empty) -> Result<Vec<WifiListOut>, Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
let wpa_supplicant = wifi_manager.read().await;
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
let wpa_supplicant = wifi_manager.read_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_ref().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
let (wifi_list, network_list) = tokio::join!(
|
||||
wpa_supplicant.list_wifi_low(),
|
||||
wpa_supplicant.list_networks_low()
|
||||
@@ -463,14 +547,20 @@ pub async fn set_country(
|
||||
ctx: RpcContext,
|
||||
SetCountryParams { country }: SetCountryParams,
|
||||
) -> Result<(), Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !interface_connected(&ctx.ethernet_interface).await? {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("Won't change country without hardwire connection"),
|
||||
crate::ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
let mut wpa_supplicant = wifi_manager.write().await;
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
wpa_supplicant.set_country_low(country.alpha2()).await?;
|
||||
for (network_id, _wifi_info) in wpa_supplicant.list_networks_low().await? {
|
||||
wpa_supplicant.remove_network_low(network_id).await?;
|
||||
@@ -734,6 +824,7 @@ impl WpaCli {
|
||||
db.mutate(|d| {
|
||||
d.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_wifi_mut()
|
||||
.as_last_region_mut()
|
||||
.ser(&new_country)
|
||||
@@ -909,13 +1000,21 @@ pub async fn synchronize_network_manager<P: AsRef<Path>>(
|
||||
crate::disk::mount::util::bind(&persistent, "/etc/NetworkManager/system-connections", false)
|
||||
.await?;
|
||||
|
||||
if !wifi.enabled {
|
||||
Command::new("rfkill")
|
||||
.arg("block")
|
||||
.arg("all")
|
||||
.invoke(ErrorKind::Wifi)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Command::new("systemctl")
|
||||
.arg("restart")
|
||||
.arg("NetworkManager")
|
||||
.invoke(ErrorKind::Wifi)
|
||||
.await?;
|
||||
|
||||
let Some(wifi_iface) = &wifi.interface else {
|
||||
let Some(wifi_iface) = wifi.interface.as_ref().filter(|_| wifi.enabled) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use chrono::{DateTime, Utc};
|
||||
use clap::builder::ValueParserFactory;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::const_true;
|
||||
use imbl_value::InternedString;
|
||||
use models::{FromStrParser, PackageId};
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
||||
@@ -15,7 +16,7 @@ use ts_rs::TS;
|
||||
|
||||
use crate::backup::BackupReport;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::{Database, DatabaseModel};
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::HandlerExtSerde;
|
||||
|
||||
@@ -33,14 +34,35 @@ pub fn notification<C: Context>() -> ParentHandler<C> {
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_about("Delete notification for a given id")
|
||||
.with_about("Remove notification for given ids")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove-before",
|
||||
from_fn_async(remove_before)
|
||||
.no_display()
|
||||
.with_about("Delete notifications preceding a given id")
|
||||
.with_about("Remove notifications preceding a given id")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mark-seen",
|
||||
from_fn_async(mark_seen)
|
||||
.no_display()
|
||||
.with_about("Mark given notifications as seen")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mark-seen-before",
|
||||
from_fn_async(mark_seen_before)
|
||||
.no_display()
|
||||
.with_about("Mark notifications preceding a given id as seen")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mark-unseen",
|
||||
from_fn_async(mark_unseen)
|
||||
.no_display()
|
||||
.with_about("Mark given notifications as unseen")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -55,7 +77,7 @@ pub fn notification<C: Context>() -> ParentHandler<C> {
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ListParams {
|
||||
pub struct ListNotificationParams {
|
||||
#[ts(type = "number | null")]
|
||||
before: Option<u32>,
|
||||
#[ts(type = "number | null")]
|
||||
@@ -65,7 +87,7 @@ pub struct ListParams {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn list(
|
||||
ctx: RpcContext,
|
||||
ListParams { before, limit }: ListParams,
|
||||
ListNotificationParams { before, limit }: ListNotificationParams,
|
||||
) -> Result<Vec<NotificationWithId>, Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
@@ -121,38 +143,124 @@ pub async fn list(
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct DeleteParams {
|
||||
#[ts(type = "number")]
|
||||
id: u32,
|
||||
pub struct ModifyNotificationParams {
|
||||
#[ts(type = "number[]")]
|
||||
ids: Vec<u32>,
|
||||
}
|
||||
|
||||
pub async fn remove(ctx: RpcContext, DeleteParams { id }: DeleteParams) -> Result<(), Error> {
|
||||
pub async fn remove(
|
||||
ctx: RpcContext,
|
||||
ModifyNotificationParams { ids }: ModifyNotificationParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_private_mut().as_notifications_mut().remove(&id)?;
|
||||
let n = db.as_private_mut().as_notifications_mut();
|
||||
for id in ids {
|
||||
n.remove(&id)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct DeleteBeforeParams {
|
||||
pub struct ModifyNotificationBeforeParams {
|
||||
#[ts(type = "number")]
|
||||
before: u32,
|
||||
}
|
||||
|
||||
pub async fn remove_before(
|
||||
ctx: RpcContext,
|
||||
DeleteBeforeParams { before }: DeleteBeforeParams,
|
||||
ModifyNotificationBeforeParams { before }: ModifyNotificationBeforeParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
for id in db.as_private().as_notifications().keys()? {
|
||||
if id < before {
|
||||
db.as_private_mut().as_notifications_mut().remove(&id)?;
|
||||
let n = db.as_private_mut().as_notifications_mut();
|
||||
for id in n.keys()?.range(..before) {
|
||||
n.remove(&id)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn mark_seen(
|
||||
ctx: RpcContext,
|
||||
ModifyNotificationParams { ids }: ModifyNotificationParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let mut diff = 0;
|
||||
let n = db.as_private_mut().as_notifications_mut();
|
||||
for id in ids {
|
||||
if !n
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(lazy_format!("Notification #{id}"))?
|
||||
.as_seen_mut()
|
||||
.replace(&true)?
|
||||
{
|
||||
diff += 1;
|
||||
}
|
||||
}
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.mutate(|n| Ok(*n -= diff))?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn mark_seen_before(
|
||||
ctx: RpcContext,
|
||||
ModifyNotificationBeforeParams { before }: ModifyNotificationBeforeParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let mut diff = 0;
|
||||
let n = db.as_private_mut().as_notifications_mut();
|
||||
for id in n.keys()?.range(..before) {
|
||||
if !n
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(lazy_format!("Notification #{id}"))?
|
||||
.as_seen_mut()
|
||||
.replace(&true)?
|
||||
{
|
||||
diff += 1;
|
||||
}
|
||||
}
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.mutate(|n| Ok(*n -= diff))?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn mark_unseen(
|
||||
ctx: RpcContext,
|
||||
ModifyNotificationParams { ids }: ModifyNotificationParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let mut diff = 0;
|
||||
let n = db.as_private_mut().as_notifications_mut();
|
||||
for id in ids {
|
||||
if n.as_idx_mut(&id)
|
||||
.or_not_found(lazy_format!("Notification #{id}"))?
|
||||
.as_seen_mut()
|
||||
.replace(&false)?
|
||||
{
|
||||
diff += 1;
|
||||
}
|
||||
}
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.mutate(|n| Ok(*n += diff))?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
@@ -253,8 +361,9 @@ impl Map for Notifications {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, HasModel)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct Notification {
|
||||
pub package_id: Option<PackageId>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
@@ -263,6 +372,8 @@ pub struct Notification {
|
||||
pub title: String,
|
||||
pub message: String,
|
||||
pub data: Value,
|
||||
#[serde(default = "const_true")]
|
||||
pub seen: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -323,6 +434,7 @@ pub fn notify<T: NotificationType>(
|
||||
title,
|
||||
message,
|
||||
data,
|
||||
seen: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ impl PackParams {
|
||||
})
|
||||
.map_err(Error::from)
|
||||
.try_fold(
|
||||
Err(Error::new(eyre!("icon not found"), ErrorKind::NotFound)),
|
||||
Err(Error::new(eyre!("license not found"), ErrorKind::NotFound)),
|
||||
|acc, x| async move {
|
||||
match acc {
|
||||
Ok(_) => Err(Error::new(eyre!("multiple licenses found in working directory, please specify which to use with `--license`"), ErrorKind::InvalidRequest)),
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn mount(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_installed_packages(context: EffectContext) -> Result<Vec<PackageId>, Error> {
|
||||
pub async fn get_installed_packages(context: EffectContext) -> Result<BTreeSet<PackageId>, Error> {
|
||||
context
|
||||
.deref()?
|
||||
.seed
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Utc;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, TryStreamExt};
|
||||
use imbl::vector;
|
||||
use imbl_value::InternedString;
|
||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler};
|
||||
use rustls::RootCertStore;
|
||||
use rustls_pki_types::CertificateDer;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::broadcast::Receiver;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -21,11 +22,13 @@ use crate::context::{CliContext, RpcContext};
|
||||
use crate::disk::util::{get_available, get_used};
|
||||
use crate::logs::{LogSource, LogsParams, SYSTEM_UNIT};
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::RpcContinuations;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::util::cpupower::{get_available_governors, set_governor, Governor};
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
||||
use crate::util::sync::Watch;
|
||||
use crate::util::Invoke;
|
||||
use crate::{MAIN_DATA, PACKAGE_DATA};
|
||||
|
||||
@@ -249,7 +252,7 @@ pub struct MetricLeaf<T> {
|
||||
unit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, TS)]
|
||||
pub struct Celsius(f64);
|
||||
impl fmt::Display for Celsius {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
@@ -277,7 +280,7 @@ impl<'de> Deserialize<'de> for Celsius {
|
||||
Ok(Celsius(s.value.parse().map_err(serde::de::Error::custom)?))
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, TS)]
|
||||
pub struct Percentage(f64);
|
||||
impl Serialize for Percentage {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@@ -303,7 +306,7 @@ impl<'de> Deserialize<'de> for Percentage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, TS)]
|
||||
pub struct MebiBytes(pub f64);
|
||||
impl Serialize for MebiBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@@ -329,7 +332,7 @@ impl<'de> Deserialize<'de> for MebiBytes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, TS)]
|
||||
pub struct GigaBytes(f64);
|
||||
impl Serialize for GigaBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@@ -355,12 +358,13 @@ impl<'de> Deserialize<'de> for GigaBytes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetricsGeneral {
|
||||
pub temperature: Option<Celsius>,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetricsMemory {
|
||||
pub percentage_used: Percentage,
|
||||
@@ -371,7 +375,8 @@ pub struct MetricsMemory {
|
||||
pub zram_available: MebiBytes,
|
||||
pub zram_used: MebiBytes,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetricsCpu {
|
||||
percentage_used: Percentage,
|
||||
@@ -380,7 +385,8 @@ pub struct MetricsCpu {
|
||||
kernel_space: Percentage,
|
||||
wait: Percentage,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetricsDisk {
|
||||
percentage_used: Percentage,
|
||||
@@ -388,8 +394,10 @@ pub struct MetricsDisk {
|
||||
available: GigaBytes,
|
||||
capacity: GigaBytes,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Metrics {
|
||||
general: MetricsGeneral,
|
||||
memory: MetricsMemory,
|
||||
@@ -398,19 +406,74 @@ pub struct Metrics {
|
||||
}
|
||||
|
||||
// #[command(display(display_serializable))]
|
||||
pub async fn metrics(ctx: RpcContext, _: Empty) -> Result<Metrics, Error> {
|
||||
match ctx.metrics_cache.read().await.clone() {
|
||||
None => Err(Error {
|
||||
source: color_eyre::eyre::eyre!("No Metrics Found"),
|
||||
kind: ErrorKind::NotFound,
|
||||
revision: None,
|
||||
}),
|
||||
Some(metrics_val) => Ok(metrics_val),
|
||||
}
|
||||
pub async fn metrics(ctx: RpcContext) -> Result<Metrics, Error> {
|
||||
ctx.metrics_cache.read().or_not_found("No Metrics Found")
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetricsFollowResponse {
|
||||
pub guid: Guid,
|
||||
pub metrics: Metrics,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct MetricsFollowParams {
|
||||
#[ts(skip)]
|
||||
#[serde(rename = "__auth_session")] // from Auth middleware
|
||||
session: Option<InternedString>,
|
||||
}
|
||||
|
||||
pub async fn metrics_follow(
|
||||
ctx: RpcContext,
|
||||
MetricsFollowParams { session }: MetricsFollowParams,
|
||||
) -> Result<MetricsFollowResponse, Error> {
|
||||
let mut local_cache = ctx.metrics_cache.clone();
|
||||
let metrics = local_cache
|
||||
.peek_and_mark_seen(|m| m.clone())
|
||||
.or_not_found("No Metrics Found")?;
|
||||
let guid = Guid::new();
|
||||
ctx.rpc_continuations
|
||||
.add(
|
||||
guid.clone(),
|
||||
RpcContinuation::ws_authed(
|
||||
ctx.clone(),
|
||||
session,
|
||||
|mut ws| async move {
|
||||
let res = async {
|
||||
loop {
|
||||
use axum::extract::ws::Message;
|
||||
tokio::select! {
|
||||
_ = local_cache.changed() => {
|
||||
ws.send(Message::Text(
|
||||
local_cache
|
||||
.peek(|m| serde_json::to_string(&m))
|
||||
.with_kind(ErrorKind::Serialization)?
|
||||
)).await.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
msg = ws.try_next() => {
|
||||
if msg.with_kind(crate::ErrorKind::Network)?.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok::<_, Error>("complete")
|
||||
}
|
||||
.await;
|
||||
ws.close_result(res).await.log_err();
|
||||
},
|
||||
Duration::from_secs(30),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
Ok(MetricsFollowResponse { guid, metrics })
|
||||
}
|
||||
|
||||
pub async fn launch_metrics_task<F: FnMut() -> Receiver<Option<Shutdown>>>(
|
||||
cache: &RwLock<Option<Metrics>>,
|
||||
cache: &Watch<Option<Metrics>>,
|
||||
mut mk_shutdown: F,
|
||||
) {
|
||||
// fetch init temp
|
||||
@@ -475,31 +538,21 @@ pub async fn launch_metrics_task<F: FnMut() -> Receiver<Option<Shutdown>>>(
|
||||
}
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
{
|
||||
// lock for writing
|
||||
let mut guard = cache.write().await;
|
||||
// write
|
||||
*guard = Some(Metrics {
|
||||
general: MetricsGeneral {
|
||||
temperature: init_temp,
|
||||
},
|
||||
memory: init_mem,
|
||||
cpu: init_cpu,
|
||||
disk: init_disk,
|
||||
})
|
||||
}
|
||||
|
||||
let should_launch_temp_task = init_temp.is_some();
|
||||
|
||||
cache.send(Some(Metrics {
|
||||
general: MetricsGeneral {
|
||||
temperature: init_temp,
|
||||
},
|
||||
memory: init_mem,
|
||||
cpu: init_cpu,
|
||||
disk: init_disk,
|
||||
}));
|
||||
|
||||
let mut task_vec = Vec::new();
|
||||
// launch persistent temp task
|
||||
if cache
|
||||
.read()
|
||||
.await
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.general
|
||||
.temperature
|
||||
.is_some()
|
||||
{
|
||||
if should_launch_temp_task {
|
||||
task_vec.push(launch_temp_task(cache, mk_shutdown()).boxed());
|
||||
}
|
||||
// launch persistent cpu task
|
||||
@@ -513,14 +566,15 @@ pub async fn launch_metrics_task<F: FnMut() -> Receiver<Option<Shutdown>>>(
|
||||
}
|
||||
|
||||
async fn launch_temp_task(
|
||||
cache: &RwLock<Option<Metrics>>,
|
||||
cache: &Watch<Option<Metrics>>,
|
||||
mut shutdown: Receiver<Option<Shutdown>>,
|
||||
) {
|
||||
loop {
|
||||
match get_temp().await {
|
||||
Ok(a) => {
|
||||
let mut lock = cache.write().await;
|
||||
(*lock).as_mut().unwrap().general.temperature = Some(a)
|
||||
cache.send_if_modified(|c| {
|
||||
c.as_mut().unwrap().general.temperature.replace(a) != Some(a)
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Could not get new temperature: {}", e);
|
||||
@@ -535,7 +589,7 @@ async fn launch_temp_task(
|
||||
}
|
||||
|
||||
async fn launch_cpu_task(
|
||||
cache: &RwLock<Option<Metrics>>,
|
||||
cache: &Watch<Option<Metrics>>,
|
||||
mut init: ProcStat,
|
||||
mut shutdown: Receiver<Option<Shutdown>>,
|
||||
) {
|
||||
@@ -543,8 +597,7 @@ async fn launch_cpu_task(
|
||||
// read /proc/stat, diff against previous metrics, compute cpu load
|
||||
match get_cpu_info(&mut init).await {
|
||||
Ok(info) => {
|
||||
let mut lock = cache.write().await;
|
||||
(*lock).as_mut().unwrap().cpu = info;
|
||||
cache.send_modify(|c| c.as_mut().unwrap().cpu = info);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Could not get new CPU Metrics: {}", e);
|
||||
@@ -558,16 +611,12 @@ async fn launch_cpu_task(
|
||||
}
|
||||
}
|
||||
|
||||
async fn launch_mem_task(
|
||||
cache: &RwLock<Option<Metrics>>,
|
||||
mut shutdown: Receiver<Option<Shutdown>>,
|
||||
) {
|
||||
async fn launch_mem_task(cache: &Watch<Option<Metrics>>, mut shutdown: Receiver<Option<Shutdown>>) {
|
||||
loop {
|
||||
// read /proc/meminfo
|
||||
match get_mem_info().await {
|
||||
Ok(a) => {
|
||||
let mut lock = cache.write().await;
|
||||
(*lock).as_mut().unwrap().memory = a;
|
||||
cache.send_modify(|c| c.as_mut().unwrap().memory = a);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Could not get new Memory Metrics: {}", e);
|
||||
@@ -581,15 +630,22 @@ async fn launch_mem_task(
|
||||
}
|
||||
}
|
||||
async fn launch_disk_task(
|
||||
cache: &RwLock<Option<Metrics>>,
|
||||
cache: &Watch<Option<Metrics>>,
|
||||
mut shutdown: Receiver<Option<Shutdown>>,
|
||||
) {
|
||||
loop {
|
||||
// run df and capture output
|
||||
match get_disk_info().await {
|
||||
Ok(a) => {
|
||||
let mut lock = cache.write().await;
|
||||
(*lock).as_mut().unwrap().disk = a;
|
||||
cache.send_if_modified(|c| {
|
||||
let c = c.as_mut().unwrap();
|
||||
if c.disk != a {
|
||||
c.disk = a;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Could not get new Disk Metrics: {}", e);
|
||||
|
||||
@@ -36,7 +36,9 @@ mod v0_3_6_alpha_13;
|
||||
mod v0_3_6_alpha_14;
|
||||
mod v0_3_6_alpha_15;
|
||||
|
||||
pub type Current = v0_3_6_alpha_15::Version; // VERSION_BUMP
|
||||
mod v0_4_0_alpha_0;
|
||||
|
||||
pub type Current = v0_4_0_alpha_0::Version; // VERSION_BUMP
|
||||
|
||||
impl Current {
|
||||
#[instrument(skip(self, db))]
|
||||
@@ -133,7 +135,8 @@ enum Version {
|
||||
V0_3_6_alpha_12(Wrapper<v0_3_6_alpha_12::Version>),
|
||||
V0_3_6_alpha_13(Wrapper<v0_3_6_alpha_13::Version>),
|
||||
V0_3_6_alpha_14(Wrapper<v0_3_6_alpha_14::Version>),
|
||||
V0_3_6_alpha_15(Wrapper<v0_3_6_alpha_15::Version>), // VERSION_BUMP
|
||||
V0_3_6_alpha_15(Wrapper<v0_3_6_alpha_15::Version>),
|
||||
V0_4_0_alpha_0(Wrapper<v0_4_0_alpha_0::Version>), // VERSION_BUMP
|
||||
Other(exver::Version),
|
||||
}
|
||||
|
||||
@@ -172,7 +175,8 @@ impl Version {
|
||||
Self::V0_3_6_alpha_12(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_3_6_alpha_13(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_3_6_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_3_6_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::V0_3_6_alpha_15(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_0(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::Other(v) => {
|
||||
return Err(Error::new(
|
||||
eyre!("unknown version {v}"),
|
||||
@@ -203,7 +207,8 @@ impl Version {
|
||||
Version::V0_3_6_alpha_12(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_6_alpha_13(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_6_alpha_14(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_6_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::V0_3_6_alpha_15(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_0(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::Other(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use const_format::formatcp;
|
||||
use ed25519_dalek::SigningKey;
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::{json, InternedString};
|
||||
use models::PackageId;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::x509::X509;
|
||||
use sqlx::postgres::PgConnectOptions;
|
||||
@@ -25,12 +24,12 @@ use crate::disk::mount::util::unmount;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::net::forward::AvailablePorts;
|
||||
use crate::net::keys::KeyStore;
|
||||
use crate::notifications::{Notification, Notifications};
|
||||
use crate::notifications::Notifications;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::ssh::{SshKeys, SshPubKey};
|
||||
use crate::util::crypto::ed25519_expand_key;
|
||||
use crate::util::serde::{Pem, PemEncoding};
|
||||
use crate::util::serde::Pem;
|
||||
use crate::util::Invoke;
|
||||
use crate::{DATA_DIR, PACKAGE_DATA};
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::json;
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{v0_3_6_alpha_12, VersionT};
|
||||
@@ -30,7 +27,7 @@ impl VersionT for Version {
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::json;
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{v0_3_6_alpha_13, VersionT};
|
||||
@@ -30,7 +27,7 @@ impl VersionT for Version {
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::json;
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{v0_3_6_alpha_14, VersionT};
|
||||
@@ -30,7 +27,7 @@ impl VersionT for Version {
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use super::*;
|
||||
|
||||
const V0_4_0: emver::Version = emver::Version::new(0, 4, 0, 0);
|
||||
lazy_static! {
|
||||
pub static ref V0_4_0_COMPAT: VersionRange = VersionRange::Conj(
|
||||
Box::new(VersionRange::Anchor(emver::GTE, V0_4_0)),
|
||||
Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_5::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_4_0
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_4_0_COMPAT
|
||||
}
|
||||
async fn up(&self, _db: &PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down(&self, _db: &PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
58
core/startos/src/version/v0_4_0_alpha_0.rs
Normal file
58
core/startos/src/version/v0_4_0_alpha_0.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::json;
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{v0_3_6_alpha_15, VersionT};
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_0: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 0.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_6_alpha_15::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_0.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
let host = db["public"]["serverInfo"]["host"].clone();
|
||||
let mut wifi = db["public"]["serverInfo"]["wifi"].clone();
|
||||
wifi["enabled"] = Value::Bool(true);
|
||||
let mut network_interfaces = db["public"]["serverInfo"]["networkInterfaces"].clone();
|
||||
for (k, v) in network_interfaces
|
||||
.as_object_mut()
|
||||
.into_iter()
|
||||
.flat_map(|m| m.iter_mut())
|
||||
{
|
||||
v["inbound"] = v["public"].clone();
|
||||
if v["ipInfo"].is_object() {
|
||||
v["ipInfo"]["name"] = Value::String((&**k).to_owned().into());
|
||||
}
|
||||
}
|
||||
let acme = db["public"]["serverInfo"]["acme"].clone();
|
||||
db["public"]["serverInfo"]["network"] = json!({
|
||||
"host": host,
|
||||
"wifi": wifi,
|
||||
"networkInterfaces": network_interfaces,
|
||||
"acme": acme,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type UnsetPublicParams = { interface: string }
|
||||
export type Celsius = number
|
||||
3
sdk/base/lib/osBindings/GigaBytes.ts
Normal file
3
sdk/base/lib/osBindings/GigaBytes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GigaBytes = number
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { NetworkInterfaceType } from "./NetworkInterfaceType"
|
||||
|
||||
export type IpInfo = {
|
||||
name: string
|
||||
scopeId: number
|
||||
deviceType: NetworkInterfaceType | null
|
||||
subnets: string[]
|
||||
|
||||
3
sdk/base/lib/osBindings/MebiBytes.ts
Normal file
3
sdk/base/lib/osBindings/MebiBytes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type MebiBytes = number
|
||||
12
sdk/base/lib/osBindings/Metrics.ts
Normal file
12
sdk/base/lib/osBindings/Metrics.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MetricsCpu } from "./MetricsCpu"
|
||||
import type { MetricsDisk } from "./MetricsDisk"
|
||||
import type { MetricsGeneral } from "./MetricsGeneral"
|
||||
import type { MetricsMemory } from "./MetricsMemory"
|
||||
|
||||
export type Metrics = {
|
||||
general: MetricsGeneral
|
||||
memory: MetricsMemory
|
||||
cpu: MetricsCpu
|
||||
disk: MetricsDisk
|
||||
}
|
||||
10
sdk/base/lib/osBindings/MetricsCpu.ts
Normal file
10
sdk/base/lib/osBindings/MetricsCpu.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Percentage } from "./Percentage"
|
||||
|
||||
export type MetricsCpu = {
|
||||
percentageUsed: Percentage
|
||||
idle: Percentage
|
||||
userSpace: Percentage
|
||||
kernelSpace: Percentage
|
||||
wait: Percentage
|
||||
}
|
||||
10
sdk/base/lib/osBindings/MetricsDisk.ts
Normal file
10
sdk/base/lib/osBindings/MetricsDisk.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { GigaBytes } from "./GigaBytes"
|
||||
import type { Percentage } from "./Percentage"
|
||||
|
||||
export type MetricsDisk = {
|
||||
percentageUsed: Percentage
|
||||
used: GigaBytes
|
||||
available: GigaBytes
|
||||
capacity: GigaBytes
|
||||
}
|
||||
4
sdk/base/lib/osBindings/MetricsGeneral.ts
Normal file
4
sdk/base/lib/osBindings/MetricsGeneral.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Celsius } from "./Celsius"
|
||||
|
||||
export type MetricsGeneral = { temperature: Celsius | null }
|
||||
13
sdk/base/lib/osBindings/MetricsMemory.ts
Normal file
13
sdk/base/lib/osBindings/MetricsMemory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MebiBytes } from "./MebiBytes"
|
||||
import type { Percentage } from "./Percentage"
|
||||
|
||||
export type MetricsMemory = {
|
||||
percentageUsed: Percentage
|
||||
total: MebiBytes
|
||||
available: MebiBytes
|
||||
used: MebiBytes
|
||||
zramTotal: MebiBytes
|
||||
zramAvailable: MebiBytes
|
||||
zramUsed: MebiBytes
|
||||
}
|
||||
13
sdk/base/lib/osBindings/NetworkInfo.ts
Normal file
13
sdk/base/lib/osBindings/NetworkInfo.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AcmeProvider } from "./AcmeProvider"
|
||||
import type { AcmeSettings } from "./AcmeSettings"
|
||||
import type { Host } from "./Host"
|
||||
import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
|
||||
import type { WifiInfo } from "./WifiInfo"
|
||||
|
||||
export type NetworkInfo = {
|
||||
wifi: WifiInfo
|
||||
host: Host
|
||||
networkInterfaces: { [key: string]: NetworkInterfaceInfo }
|
||||
acme: { [key: AcmeProvider]: AcmeSettings }
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { IpInfo } from "./IpInfo"
|
||||
|
||||
export type NetworkInterfaceInfo = {
|
||||
public: boolean | null
|
||||
inbound: boolean | null
|
||||
outbound: boolean | null
|
||||
ipInfo: IpInfo | null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type NetworkInterfaceSetPublicParams = {
|
||||
export type NetworkInterfaceSetInboundParams = {
|
||||
interface: string
|
||||
public: boolean | null
|
||||
inbound: boolean | null
|
||||
}
|
||||
3
sdk/base/lib/osBindings/Percentage.ts
Normal file
3
sdk/base/lib/osBindings/Percentage.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Percentage = number
|
||||
@@ -1,28 +1,21 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AcmeProvider } from "./AcmeProvider"
|
||||
import type { AcmeSettings } from "./AcmeSettings"
|
||||
import type { Governor } from "./Governor"
|
||||
import type { Host } from "./Host"
|
||||
import type { LshwDevice } from "./LshwDevice"
|
||||
import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
|
||||
import type { NetworkInfo } from "./NetworkInfo"
|
||||
import type { ServerStatus } from "./ServerStatus"
|
||||
import type { SmtpValue } from "./SmtpValue"
|
||||
import type { WifiInfo } from "./WifiInfo"
|
||||
|
||||
export type ServerInfo = {
|
||||
arch: string
|
||||
platform: string
|
||||
id: string
|
||||
hostname: string
|
||||
host: Host
|
||||
version: string
|
||||
packageVersionCompat: string
|
||||
postInitMigrationTodos: string[]
|
||||
lastBackup: string | null
|
||||
networkInterfaces: { [key: string]: NetworkInterfaceInfo }
|
||||
acme: { [key: AcmeProvider]: AcmeSettings }
|
||||
network: NetworkInfo
|
||||
statusInfo: ServerStatus
|
||||
wifi: WifiInfo
|
||||
unreadNotificationCount: number
|
||||
passwordHash: string
|
||||
pubkey: string
|
||||
|
||||
3
sdk/base/lib/osBindings/UnsetInboundParams.ts
Normal file
3
sdk/base/lib/osBindings/UnsetInboundParams.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type UnsetInboundParams = { interface: string }
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type WifiInfo = {
|
||||
enabled: boolean
|
||||
interface: string | null
|
||||
ssids: Array<string>
|
||||
selected: string | null
|
||||
|
||||
@@ -46,6 +46,7 @@ export { BlockDev } from "./BlockDev"
|
||||
export { BuildArg } from "./BuildArg"
|
||||
export { CallbackId } from "./CallbackId"
|
||||
export { Category } from "./Category"
|
||||
export { Celsius } from "./Celsius"
|
||||
export { CheckDependenciesParam } from "./CheckDependenciesParam"
|
||||
export { CheckDependenciesResult } from "./CheckDependenciesResult"
|
||||
export { Cifs } from "./Cifs"
|
||||
@@ -93,6 +94,7 @@ export { GetSslKeyParams } from "./GetSslKeyParams"
|
||||
export { GetStatusParams } from "./GetStatusParams"
|
||||
export { GetStoreParams } from "./GetStoreParams"
|
||||
export { GetSystemSmtpParams } from "./GetSystemSmtpParams"
|
||||
export { GigaBytes } from "./GigaBytes"
|
||||
export { GitHash } from "./GitHash"
|
||||
export { Governor } from "./Governor"
|
||||
export { Guid } from "./Guid"
|
||||
@@ -125,14 +127,21 @@ export { LshwProcessor } from "./LshwProcessor"
|
||||
export { MainStatus } from "./MainStatus"
|
||||
export { Manifest } from "./Manifest"
|
||||
export { MaybeUtf8String } from "./MaybeUtf8String"
|
||||
export { MebiBytes } from "./MebiBytes"
|
||||
export { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
|
||||
export { MetricsCpu } from "./MetricsCpu"
|
||||
export { MetricsDisk } from "./MetricsDisk"
|
||||
export { MetricsGeneral } from "./MetricsGeneral"
|
||||
export { MetricsMemory } from "./MetricsMemory"
|
||||
export { Metrics } from "./Metrics"
|
||||
export { MountParams } from "./MountParams"
|
||||
export { MountTarget } from "./MountTarget"
|
||||
export { NamedHealthCheckResult } from "./NamedHealthCheckResult"
|
||||
export { NamedProgress } from "./NamedProgress"
|
||||
export { NetInfo } from "./NetInfo"
|
||||
export { NetworkInfo } from "./NetworkInfo"
|
||||
export { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
|
||||
export { NetworkInterfaceSetPublicParams } from "./NetworkInterfaceSetPublicParams"
|
||||
export { NetworkInterfaceSetInboundParams } from "./NetworkInterfaceSetInboundParams"
|
||||
export { NetworkInterfaceType } from "./NetworkInterfaceType"
|
||||
export { OnionHostname } from "./OnionHostname"
|
||||
export { OsIndex } from "./OsIndex"
|
||||
@@ -149,6 +158,7 @@ export { PackageState } from "./PackageState"
|
||||
export { PackageVersionInfo } from "./PackageVersionInfo"
|
||||
export { PasswordType } from "./PasswordType"
|
||||
export { PathOrUrl } from "./PathOrUrl"
|
||||
export { Percentage } from "./Percentage"
|
||||
export { ProcedureId } from "./ProcedureId"
|
||||
export { Progress } from "./Progress"
|
||||
export { Public } from "./Public"
|
||||
@@ -188,7 +198,7 @@ export { SignerInfo } from "./SignerInfo"
|
||||
export { SmtpValue } from "./SmtpValue"
|
||||
export { StartStop } from "./StartStop"
|
||||
export { TestSmtpParams } from "./TestSmtpParams"
|
||||
export { UnsetPublicParams } from "./UnsetPublicParams"
|
||||
export { UnsetInboundParams } from "./UnsetInboundParams"
|
||||
export { UpdatingState } from "./UpdatingState"
|
||||
export { VerifyCifsParams } from "./VerifyCifsParams"
|
||||
export { VersionSignerParams } from "./VersionSignerParams"
|
||||
|
||||
@@ -73,7 +73,7 @@ import * as actions from "../../base/lib/actions"
|
||||
import { setupInit } from "./inits/setupInit"
|
||||
import * as fs from "node:fs/promises"
|
||||
|
||||
export const OSVersion = testTypeVersion("0.3.6-alpha.15")
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.0")
|
||||
|
||||
// prettier-ignore
|
||||
type AnyNeverCond<T extends any[], Then, Else> =
|
||||
|
||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.3.6-alpha.15",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "startos-ui",
|
||||
"version": "0.3.6-alpha.15",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.3.6-alpha.15",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"author": "Start9 Labs, Inc",
|
||||
"homepage": "https://start9.com/",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -96,7 +96,7 @@ import { map } from 'rxjs'
|
||||
})
|
||||
export class InterfaceComponent {
|
||||
readonly acme$ = inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('serverInfo', 'acme')
|
||||
.watch$('serverInfo', 'network', 'acme')
|
||||
.pipe(map(acme => Object.keys(acme)))
|
||||
|
||||
@Input() packageId?: string
|
||||
|
||||
@@ -19,7 +19,6 @@ export const REMOVE: Partial<TuiDialogOptions<TuiConfirmData>> = {
|
||||
},
|
||||
}
|
||||
|
||||
// @TODO 040 Aiden audit
|
||||
export function getAddresses(
|
||||
serviceInterface: T.ServiceInterface,
|
||||
host: T.Host,
|
||||
@@ -81,11 +80,17 @@ export function getAddresses(
|
||||
} else {
|
||||
const hostnameKind = h.hostname.kind
|
||||
|
||||
if (hostnameKind === 'domain') {
|
||||
if (h.public) {
|
||||
clearnet.push({
|
||||
label: 'Domain',
|
||||
label:
|
||||
hostnameKind == 'domain'
|
||||
? 'Domain'
|
||||
: `${h.networkInterfaceId} (${hostnameKind})`,
|
||||
url,
|
||||
acme: host.domains[h.hostname.domain]?.acme,
|
||||
acme:
|
||||
hostnameKind == 'domain'
|
||||
? host.domains[h.hostname.domain]?.acme
|
||||
: null, // @TODO Matt make sure this is handled correctly - looks like ACME settings aren't built yet anyway, but ACME settings aren't *available* for public IPs
|
||||
})
|
||||
} else {
|
||||
local.push({
|
||||
@@ -101,15 +106,19 @@ export function getAddresses(
|
||||
})
|
||||
|
||||
return {
|
||||
clearnet,
|
||||
local,
|
||||
tor,
|
||||
clearnet: clearnet.filter(
|
||||
(value, index, self) =>
|
||||
index === self.findIndex(t => t.url === value.url),
|
||||
),
|
||||
local: local.filter(
|
||||
(value, index, self) =>
|
||||
index === self.findIndex(t => t.url === value.url),
|
||||
),
|
||||
tor: tor.filter(
|
||||
(value, index, self) =>
|
||||
index === self.findIndex(t => t.url === value.url),
|
||||
),
|
||||
}
|
||||
|
||||
// @TODO Aiden what was going on here in 036?
|
||||
// return mappedAddresses.filter(
|
||||
// (value, index, self) => index === self.findIndex(t => t.url === value.url),
|
||||
// )
|
||||
}
|
||||
|
||||
export type AddressDetails = {
|
||||
|
||||
@@ -95,7 +95,7 @@ export default class NotificationsComponent {
|
||||
this.notifications.set(
|
||||
current.map(c => ({
|
||||
...c,
|
||||
read: toUpdate.some(n => n.id === c.id) || c.read,
|
||||
read: toUpdate.some(n => n.id === c.id) || c.seen,
|
||||
})),
|
||||
)
|
||||
|
||||
@@ -111,7 +111,7 @@ export default class NotificationsComponent {
|
||||
this.notifications.set(
|
||||
current.map(c => ({
|
||||
...c,
|
||||
read: c.read && !toUpdate.some(n => n.id === c.id),
|
||||
read: c.seen && !toUpdate.some(n => n.id === c.id),
|
||||
})),
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import { NotificationItemComponent } from './item.component'
|
||||
@for (notification of notifications; track $index) {
|
||||
<tr
|
||||
[notificationItem]="notification"
|
||||
[style.font-weight]="notification.read ? 'normal' : 'bold'"
|
||||
[style.font-weight]="notification.seen ? 'normal' : 'bold'"
|
||||
>
|
||||
<input
|
||||
tuiCheckbox
|
||||
|
||||
@@ -28,7 +28,7 @@ export class SettingsACMEComponent {
|
||||
|
||||
readonly docsUrl = 'https://docs.start9.com/0.3.6/user-manual/acme'
|
||||
|
||||
acme$ = this.patch.watch$('serverInfo', 'acme').pipe(
|
||||
acme$ = this.patch.watch$('serverInfo', 'network', 'acme').pipe(
|
||||
map(acme => {
|
||||
const providerUrls = Object.keys(acme)
|
||||
return providerUrls.map(url => {
|
||||
|
||||
@@ -771,7 +771,7 @@ export namespace Mock {
|
||||
},
|
||||
},
|
||||
},
|
||||
read: false,
|
||||
seen: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@@ -782,7 +782,7 @@ export namespace Mock {
|
||||
title: 'SSH Key Added',
|
||||
message: 'A new SSH key was added. If you did not do this, shit is bad.',
|
||||
data: null,
|
||||
read: false,
|
||||
seen: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
@@ -793,7 +793,7 @@ export namespace Mock {
|
||||
title: 'SSH Key Removed',
|
||||
message: 'A SSH key was removed.',
|
||||
data: null,
|
||||
read: false,
|
||||
seen: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
@@ -811,7 +811,7 @@ export namespace Mock {
|
||||
)
|
||||
.join(''),
|
||||
data: null,
|
||||
read: false,
|
||||
seen: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
@@ -822,7 +822,7 @@ export namespace Mock {
|
||||
title: 'Welcome to StartOS 0.3.6!',
|
||||
message: 'Click "View Details" to learn all about the new version',
|
||||
data: markdown,
|
||||
read: false,
|
||||
seen: false,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ export namespace RR {
|
||||
guid: string
|
||||
}
|
||||
|
||||
// @TODO 040 implement websocket
|
||||
export type FollowServerMetricsReq = {} // server.metrics.follow
|
||||
export type FollowServerMetricsRes = {
|
||||
guid: string
|
||||
@@ -136,17 +135,16 @@ export namespace RR {
|
||||
} // notification.list
|
||||
export type GetNotificationsRes = ServerNotification<number>[]
|
||||
|
||||
// @TODO 040 all these notification endpoints need updating
|
||||
export type DeleteNotificationReq = { ids: number[] } // notification.delete
|
||||
export type DeleteNotificationRes = null
|
||||
export type DeleteNotificationsReq = { ids: number[] } // notification.remove
|
||||
export type DeleteNotificationsRes = null
|
||||
|
||||
export type MarkSeenNotificationReq = DeleteNotificationReq // notification.mark-seen
|
||||
export type MarkSeenNotificationReq = DeleteNotificationsReq // notification.mark-seen
|
||||
export type MarkSeenNotificationRes = null
|
||||
|
||||
export type MarkSeenAllNotificationsReq = { before: number } // notification.mark-seen-before
|
||||
export type MarkSeenAllNotificationsRes = null
|
||||
|
||||
export type MarkUnseenNotificationReq = DeleteNotificationReq // notification.mark-unseen
|
||||
export type MarkUnseenNotificationReq = DeleteNotificationsReq // notification.mark-unseen
|
||||
export type MarkUnseenNotificationRes = null
|
||||
|
||||
// wifi
|
||||
@@ -175,9 +173,11 @@ export namespace RR {
|
||||
}
|
||||
export type AddWifiRes = null
|
||||
|
||||
// @TODO 040
|
||||
export type EnableWifiReq = { enable: boolean } // wifi.enable
|
||||
export type EnableWifiRes = null
|
||||
export type EnabledWifiReq = { enable: boolean } // wifi.set-enabled
|
||||
export type EnabledWifiRes = null
|
||||
|
||||
export type SetWifiCountryReq = { country: string } // wifi.country.set
|
||||
export type SetWifiCountryRes = null
|
||||
|
||||
export type ConnectWifiReq = { ssid: string } // wifi.connect
|
||||
export type ConnectWifiRes = null
|
||||
@@ -523,8 +523,7 @@ export type ServerNotification<T extends number> = {
|
||||
title: string
|
||||
message: string
|
||||
data: NotificationData<T>
|
||||
// @TODO 040
|
||||
read: boolean
|
||||
seen: boolean
|
||||
}
|
||||
|
||||
export type NotificationLevel = 'success' | 'info' | 'warning' | 'error'
|
||||
@@ -532,10 +531,10 @@ export type NotificationLevel = 'success' | 'info' | 'warning' | 'error'
|
||||
export type NotificationData<T> = T extends 0
|
||||
? null
|
||||
: T extends 1
|
||||
? BackupReport
|
||||
: T extends 2
|
||||
? string
|
||||
: any
|
||||
? BackupReport
|
||||
: T extends 2
|
||||
? string
|
||||
: any
|
||||
|
||||
export type BackupReport = {
|
||||
server: {
|
||||
|
||||
@@ -177,12 +177,12 @@ export abstract class ApiService {
|
||||
): Promise<RR.MarkSeenAllNotificationsRes>
|
||||
|
||||
abstract markUnseenNotifications(
|
||||
params: RR.DeleteNotificationReq,
|
||||
): Promise<RR.DeleteNotificationRes>
|
||||
params: RR.DeleteNotificationsReq,
|
||||
): Promise<RR.DeleteNotificationsRes>
|
||||
|
||||
abstract deleteNotifications(
|
||||
params: RR.DeleteNotificationReq,
|
||||
): Promise<RR.DeleteNotificationRes>
|
||||
params: RR.DeleteNotificationsReq,
|
||||
): Promise<RR.DeleteNotificationsRes>
|
||||
|
||||
// ** proxies **
|
||||
|
||||
@@ -220,7 +220,11 @@ export abstract class ApiService {
|
||||
|
||||
// wifi
|
||||
|
||||
abstract enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes>
|
||||
abstract enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes>
|
||||
|
||||
abstract setWifiCountry(
|
||||
params: RR.SetWifiCountryReq,
|
||||
): Promise<RR.SetWifiCountryRes>
|
||||
|
||||
abstract getWifi(
|
||||
params: RR.GetWifiReq,
|
||||
|
||||
@@ -245,7 +245,6 @@ export class LiveApiService extends ApiService {
|
||||
async followServerMetrics(
|
||||
params: RR.FollowServerMetricsReq,
|
||||
): Promise<RR.FollowServerMetricsRes> {
|
||||
// @TODO 040 implement .follow
|
||||
return this.rpcRequest({ method: 'server.metrics.follow', params })
|
||||
}
|
||||
|
||||
@@ -350,8 +349,8 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
|
||||
async deleteNotifications(
|
||||
params: RR.DeleteNotificationReq,
|
||||
): Promise<RR.DeleteNotificationRes> {
|
||||
params: RR.DeleteNotificationsReq,
|
||||
): Promise<RR.DeleteNotificationsRes> {
|
||||
return this.rpcRequest({ method: 'notification.remove', params })
|
||||
}
|
||||
|
||||
@@ -422,7 +421,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// wifi
|
||||
|
||||
async enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes> {
|
||||
async enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes> {
|
||||
return this.rpcRequest({ method: 'wifi.enable', params })
|
||||
}
|
||||
|
||||
@@ -433,6 +432,12 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'wifi.get', params, timeout })
|
||||
}
|
||||
|
||||
async setWifiCountry(
|
||||
params: RR.SetWifiCountryReq,
|
||||
): Promise<RR.SetWifiCountryRes> {
|
||||
return this.rpcRequest({ method: 'wifi.country.set', params })
|
||||
}
|
||||
|
||||
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
|
||||
return this.rpcRequest({ method: 'wifi.add', params })
|
||||
}
|
||||
|
||||
@@ -519,8 +519,8 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
|
||||
async deleteNotifications(
|
||||
params: RR.DeleteNotificationReq,
|
||||
): Promise<RR.DeleteNotificationRes> {
|
||||
params: RR.DeleteNotificationsReq,
|
||||
): Promise<RR.DeleteNotificationsRes> {
|
||||
await pauseFor(2000)
|
||||
return null
|
||||
}
|
||||
@@ -696,7 +696,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
// wifi
|
||||
|
||||
async enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes> {
|
||||
async enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes> {
|
||||
await pauseFor(2000)
|
||||
const patch = [
|
||||
{
|
||||
@@ -710,6 +710,13 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async setWifiCountry(
|
||||
params: RR.SetWifiCountryReq,
|
||||
): Promise<RR.SetWifiCountryRes> {
|
||||
await pauseFor(2000)
|
||||
return null
|
||||
}
|
||||
|
||||
async getWifi(params: RR.GetWifiReq): Promise<RR.GetWifiRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.Wifi
|
||||
|
||||
@@ -39,6 +39,11 @@ export const mockPatchData: DataModel = {
|
||||
selected: null,
|
||||
lastRegion: null,
|
||||
},
|
||||
acme: {
|
||||
[knownACME[0].url]: {
|
||||
contact: ['mailto:support@start9.com'],
|
||||
},
|
||||
},
|
||||
host: {
|
||||
bindings: {
|
||||
80: {
|
||||
@@ -171,11 +176,6 @@ export const mockPatchData: DataModel = {
|
||||
},
|
||||
},
|
||||
},
|
||||
acme: {
|
||||
[knownACME[0].url]: {
|
||||
contact: ['mailto:support@start9.com'],
|
||||
},
|
||||
},
|
||||
unreadNotificationCount: 4,
|
||||
// password is asdfasdf
|
||||
passwordHash:
|
||||
@@ -191,109 +191,6 @@ export const mockPatchData: DataModel = {
|
||||
backupProgress: {},
|
||||
},
|
||||
hostname: 'random-words',
|
||||
// @ts-ignore
|
||||
host: {
|
||||
bindings: {
|
||||
80: {
|
||||
enabled: true,
|
||||
net: {
|
||||
assignedPort: null,
|
||||
assignedSslPort: 443,
|
||||
public: false,
|
||||
},
|
||||
options: {
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
preferredExternalPort: 443,
|
||||
alpn: { specified: ['http/1.1', 'h2'] },
|
||||
},
|
||||
secure: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
domains: {},
|
||||
onions: ['myveryownspecialtoraddress'],
|
||||
hostnameInfo: {
|
||||
80: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'eth0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'wlan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'eth0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '10.0.0.1',
|
||||
port: null,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'wlan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '10.0.0.2',
|
||||
port: null,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'eth0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: 'fe80::cd00:0000:0cde:1257:0000:211e:72cd',
|
||||
scopeId: 2,
|
||||
port: null,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'wlan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: 'fe80::cd00:0000:0cde:1257:0000:211e:1234',
|
||||
scopeId: 3,
|
||||
port: null,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'myveryownspecialtoraddress.onion',
|
||||
port: 80,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
|
||||
caFingerprint: 'SHA-256: 63 2B 11 99 44 40 17 DF 37 FC C3 DF 0F 3D 15',
|
||||
ntpSynced: false,
|
||||
|
||||
@@ -25,7 +25,7 @@ export class NotificationService {
|
||||
).pipe(shareReplay(1))
|
||||
|
||||
async markSeen(notifications: ServerNotifications) {
|
||||
const ids = notifications.filter(n => !n.read).map(n => n.id)
|
||||
const ids = notifications.filter(n => !n.seen).map(n => n.id)
|
||||
|
||||
this.updateCount(-ids.length)
|
||||
|
||||
@@ -43,7 +43,7 @@ export class NotificationService {
|
||||
}
|
||||
|
||||
async markUnseen(notifications: ServerNotifications) {
|
||||
const ids = notifications.filter(n => n.read).map(n => n.id)
|
||||
const ids = notifications.filter(n => n.seen).map(n => n.id)
|
||||
|
||||
this.updateCount(ids.length)
|
||||
|
||||
@@ -53,7 +53,7 @@ export class NotificationService {
|
||||
}
|
||||
|
||||
async remove(notifications: ServerNotifications): Promise<void> {
|
||||
this.updateCount(-notifications.filter(n => !n.read).length)
|
||||
this.updateCount(-notifications.filter(n => !n.seen).length)
|
||||
|
||||
this.api
|
||||
.deleteNotifications({ ids: notifications.map(n => n.id) })
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
|
||||
export type DataModel = Omit<T.Public, 'serverInfo'> & {
|
||||
ui: UIData
|
||||
// @TODO 040
|
||||
serverInfo: Omit<
|
||||
T.Public['serverInfo'],
|
||||
'wifi' | 'networkInterfaces' | 'host'
|
||||
> & {
|
||||
network: NetworkInfo
|
||||
}
|
||||
packageData: Record<string, PackageDataEntry>
|
||||
}
|
||||
export type DataModel = T.Public & { ui: UIData; packageData: AllPackageData }
|
||||
|
||||
export type UIData = {
|
||||
name: string | null
|
||||
@@ -37,20 +27,7 @@ export type UIStore = {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export type NetworkInfo = {
|
||||
wifi: T.WifiInfo & { enabled: boolean }
|
||||
host: T.Host
|
||||
networkInterfaces: {
|
||||
[id: string]: {
|
||||
inbound: boolean | null
|
||||
outbound: boolean | null
|
||||
ipInfo:
|
||||
| (T.IpInfo & {
|
||||
name: string
|
||||
})
|
||||
| null
|
||||
}
|
||||
}
|
||||
export type NetworkInfo = T.NetworkInfo & {
|
||||
// @TODO 041
|
||||
// start9To: {
|
||||
// subdomain: string
|
||||
|
||||
Reference in New Issue
Block a user