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:
Aiden McClelland
2025-03-06 20:36:19 -07:00
committed by GitHub
parent ac392dcb96
commit e830fade06
63 changed files with 800 additions and 480 deletions

View File

@@ -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]

View File

@@ -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()

View File

@@ -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;

View File

@@ -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!(""))
}
}

View File

@@ -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>,

View File

@@ -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

View File

@@ -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()
})

View File

@@ -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",

View File

@@ -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)
})

View File

@@ -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()
});
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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()?,
)

View File

@@ -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),
),
);

View File

@@ -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

View File

@@ -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))

View File

@@ -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(());
};

View File

@@ -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,
},
)
}

View File

@@ -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)),

View File

@@ -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

View File

@@ -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);

View File

@@ -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(),
}
}

View File

@@ -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};

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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(())
}
}

View 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(())
}
}