More svc effect handlers (#2610)

* complete get_primary_url fn

* complete clear_network_interfaces fn

* formatting

* complete remove_address fn

* get_system_smtp wip

* complete get_system_smtp and set_system_smtp

* add SetSystemSmtpParams struct

* add set_system_smtp subcommand

* Remove 'Copy' implementation from `HostAddress`

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>

* Refactor `get_host_primary` fn and clone  resulting `HostAddress`

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>

* misc fixes and debug info

* seed hosts with a tor address

---------

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Dominion5254
2024-05-10 13:20:24 -06:00
committed by GitHub
parent 30aabe255b
commit 800b0763e4
31 changed files with 259 additions and 97 deletions

View File

@@ -76,6 +76,7 @@ impl Public {
ntp_synced: false,
zram: true,
governor: None,
smtp: None,
},
package_data: AllPackageData::default(),
ui: serde_json::from_str(include_str!(concat!(
@@ -135,6 +136,7 @@ pub struct ServerInfo {
#[serde(default)]
pub zram: bool,
pub governor: Option<Governor>,
pub smtp: Option<String>
}
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]

View File

@@ -220,7 +220,7 @@ where
}
pub fn upsert<F>(&mut self, key: &T::Key, value: F) -> Result<&mut Model<T::Value>, Error>
where
F: FnOnce() -> T::Value,
F: FnOnce() -> Result<T::Value, Error>,
{
use serde::ser::Error;
match &mut self.value {
@@ -233,7 +233,7 @@ where
s.as_ref().index_or_insert(v)
});
if !exists {
res.ser(&value())?;
res.ser(&value()?)?;
}
Ok(res)
}

View File

@@ -348,6 +348,12 @@ pub async fn init(cfg: &ServerConfig) -> Result<InitResult, Error> {
})
.await?;
Command::new("systemctl")
.arg("start")
.arg("lxc-net.service")
.invoke(ErrorKind::Lxc)
.await?;
crate::version::init(&db).await?;
db.mutate(|d| {

View File

@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use torut::onion::OnionAddressV3;
use ts_rs::TS;
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "kind")]
#[ts(export)]

View File

@@ -1,10 +1,12 @@
use std::collections::{BTreeMap, BTreeSet};
use imbl_value::InternedString;
use models::HostId;
use models::{HostId, PackageId};
use serde::{Deserialize, Serialize};
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
use ts_rs::TS;
use crate::db::model::DatabaseModel;
use crate::net::forward::AvailablePorts;
use crate::net::host::address::HostAddress;
use crate::net::host::binding::{BindInfo, BindOptions};
@@ -64,25 +66,72 @@ impl Map for HostInfo {
}
}
impl Model<HostInfo> {
pub fn host_for<'a>(
db: &'a mut DatabaseModel,
package_id: &PackageId,
host_id: &HostId,
host_kind: HostKind,
) -> Result<&'a mut Model<Host>, Error> {
fn host_info<'a>(
db: &'a mut DatabaseModel,
package_id: &PackageId,
) -> Result<&'a mut Model<HostInfo>, Error> {
Ok::<_, Error>(
db.as_public_mut()
.as_package_data_mut()
.as_idx_mut(package_id)
.or_not_found(package_id)?
.as_hosts_mut(),
)
}
let tor_key = if host_info(db, package_id)?.as_idx(host_id).is_none() {
Some(
db.as_private_mut()
.as_key_store_mut()
.as_onion_mut()
.new_key()?,
)
} else {
None
};
host_info(db, package_id)?.upsert(host_id, || {
let mut h = Host::new(host_kind);
h.addresses.insert(HostAddress::Onion {
address: tor_key
.or_not_found("generated tor key")?
.public()
.get_onion_address(),
});
Ok(h)
})
}
impl Model<Host> {
pub fn set_kind(&mut self, kind: HostKind) -> Result<(), Error> {
match (self.as_kind().de()?, kind) {
(HostKind::Multi, HostKind::Multi) => Ok(()),
}
}
pub fn add_binding(
&mut self,
available_ports: &mut AvailablePorts,
kind: HostKind,
id: &HostId,
internal_port: u16,
options: BindOptions,
) -> Result<(), Error> {
self.upsert(id, || Host::new(kind))?
.as_bindings_mut()
.mutate(|b| {
let info = if let Some(info) = b.remove(&internal_port) {
info.update(available_ports, options)?
} else {
BindInfo::new(available_ports, options)?
};
b.insert(internal_port, info);
Ok(())
}) // TODO: handle host kind change
self.as_bindings_mut().mutate(|b| {
let info = if let Some(info) = b.remove(&internal_port) {
info.update(available_ports, options)?
} else {
BindInfo::new(available_ports, options)?
};
b.insert(internal_port, info);
Ok(())
})
}
}
impl HostInfo {
pub fn get_host_primary(&self, host_id: &HostId) -> Option<HostAddress> {
self.0.get(&host_id).and_then(|h| h.primary.clone())
}
}

View File

@@ -16,7 +16,7 @@ use crate::net::dns::DnsController;
use crate::net::forward::LanPortForwardController;
use crate::net::host::address::HostAddress;
use crate::net::host::binding::{AddSslOptions, BindOptions};
use crate::net::host::{Host, HostKind};
use crate::net::host::{host_for, Host, HostKind};
use crate::net::tor::TorController;
use crate::net::vhost::{AlpnInfo, VHostController};
use crate::prelude::*;
@@ -162,7 +162,7 @@ impl NetController {
}
}
#[derive(Default)]
#[derive(Default, Debug)]
struct HostBinds {
lan: BTreeMap<u16, (u16, Option<AddSslOptions>, Arc<()>)>,
tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
@@ -193,25 +193,17 @@ impl NetService {
internal_port: u16,
options: BindOptions,
) -> Result<(), Error> {
let id_ref = &id;
dbg!("bind", &kind, &id, internal_port, &options);
let pkg_id = &self.id;
let host = self
.net_controller()?
.db
.mutate(|d| {
let mut ports = d.as_private().as_available_ports().de()?;
let hosts = d
.as_public_mut()
.as_package_data_mut()
.as_idx_mut(pkg_id)
.or_not_found(pkg_id)?
.as_hosts_mut();
hosts.add_binding(&mut ports, kind, &id, internal_port, options)?;
let host = hosts
.as_idx(&id)
.or_not_found(lazy_format!("Host {id_ref} for {pkg_id}"))?
.de()?;
d.as_private_mut().as_available_ports_mut().ser(&ports)?;
.mutate(|db| {
let mut ports = db.as_private().as_available_ports().de()?;
let host = host_for(db, pkg_id, &id, kind)?;
host.add_binding(&mut ports, internal_port, options)?;
let host = host.de()?;
db.as_private_mut().as_available_ports_mut().ser(&ports)?;
Ok(host)
})
.await?;
@@ -219,6 +211,8 @@ impl NetService {
}
async fn update(&mut self, id: HostId, host: Host) -> Result<(), Error> {
dbg!(&host);
dbg!(&self.binds);
let ctrl = self.net_controller()?;
let binds = {
if !self.binds.contains_key(&id) {

View File

@@ -9,7 +9,7 @@ use futures::{FutureExt, TryStreamExt};
use helpers::NonDetachingJoinHandle;
use imbl_value::InternedString;
use itertools::Itertools;
use rpc_toolkit::{from_fn_async, CallRemote, Context, HandlerArgs, HandlerExt, ParentHandler};
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha512};
use ts_rs::TS;
@@ -106,9 +106,11 @@ async fn add_asset(
.as_idx_mut(&version)
.or_not_found(&version)?,
)
.upsert(&platform, || RegistryAsset {
url,
signature_info: SignatureInfo::new(SIG_CONTEXT),
.upsert(&platform, || {
Ok(RegistryAsset {
url,
signature_info: SignatureInfo::new(SIG_CONTEXT),
})
})?
.as_signature_info_mut()
.mutate(|s| s.add_sig(&signature))?;

View File

@@ -81,7 +81,7 @@ pub async fn add_version(
db.as_index_mut()
.as_os_mut()
.as_versions_mut()
.upsert(&version, || OsVersionInfo::default())?
.upsert(&version, || Ok(OsVersionInfo::default()))?
.mutate(|i| {
i.headline = headline;
i.release_notes = release_notes;

View File

@@ -275,9 +275,7 @@ impl<S: FileSource> DirectoryContents<S> {
_ => std::cmp::Ordering::Equal,
}) {
varint::serialize_varstring(&**name, w).await?;
if let Some(pos) = entry.serialize_header(queue.add(entry).await?, w).await? {
eprintln!("DEBUG ====> {name} @ {pos}");
}
entry.serialize_header(queue.add(entry).await?, w).await?;
}
Ok(())

View File

@@ -29,7 +29,7 @@ impl ContainerCliContext {
Self(Arc::new(ContainerCliSeed {
socket: cfg
.socket
.unwrap_or_else(|| Path::new("/").join(HOST_RPC_SERVER_SOCKET)),
.unwrap_or_else(|| Path::new("/media/startos/rpc").join(HOST_RPC_SERVER_SOCKET)),
runtime: OnceCell::new(),
}))
}

View File

@@ -13,7 +13,7 @@ use imbl::OrdMap;
use imbl_value::{json, InternedString};
use itertools::Itertools;
use models::{
ActionId, DataUrl, HealthCheckId, HostId, Id, ImageId, PackageId, ServiceInterfaceId, VolumeId,
ActionId, DataUrl, HealthCheckId, HostId, ImageId, PackageId, ServiceInterfaceId, VolumeId,
};
use patch_db::json_ptr::JsonPointer;
use rpc_toolkit::{from_fn, from_fn_async, Context, Empty, HandlerExt, ParentHandler};
@@ -29,6 +29,7 @@ use crate::db::model::package::{
use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::net::host::address::HostAddress;
use crate::net::host::binding::BindOptions;
use crate::net::host::{self, HostKind};
use crate::net::service_interface::{
@@ -169,6 +170,7 @@ pub fn service_effect_handler<C: Context>() -> ParentHandler<C> {
.no_display()
.with_call_remote::<ContainerCliContext>(),
)
.subcommand("setSystemSmtp", from_fn_async(set_system_smtp).no_cli())
.subcommand("getSystemSmtp", from_fn_async(get_system_smtp).no_cli())
.subcommand("getContainerIp", from_fn_async(get_container_ip).no_cli())
.subcommand(
@@ -206,6 +208,12 @@ struct GetSystemSmtpParams {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct SetSystemSmtpParams {
smtp: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct GetServicePortForwardParams {
#[ts(type = "string | null")]
package_id: Option<PackageId>,
@@ -236,6 +244,7 @@ struct GetPrimaryUrlParams {
package_id: Option<PackageId>,
service_interface_id: String,
callback: Callback,
host_id: HostId,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
@@ -249,7 +258,7 @@ struct ListServiceInterfacesParams {
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct RemoveAddressParams {
id: String,
id: ServiceInterfaceId,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
@@ -316,11 +325,39 @@ struct MountParams {
location: String,
target: MountTarget,
}
async fn set_system_smtp(context: EffectContext, data: SetSystemSmtpParams) -> Result<(), Error> {
let context = context.deref()?;
context
.ctx
.db
.mutate(|db| {
let model = db.as_public_mut().as_server_info_mut().as_smtp_mut();
model.ser(&mut Some(data.smtp))
})
.await
}
async fn get_system_smtp(
context: EffectContext,
data: GetSystemSmtpParams,
) -> Result<Value, Error> {
todo!()
) -> Result<String, Error> {
let context = context.deref()?;
let res = context
.ctx
.db
.peek()
.await
.into_public()
.into_server_info()
.into_smtp()
.de()?;
match res {
Some(smtp) => Ok(smtp),
None => Err(Error::new(
eyre!("SMTP not found"),
crate::ErrorKind::NotFound,
)),
}
}
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr, Error> {
let context = context.deref()?;
@@ -337,8 +374,24 @@ async fn get_service_port_forward(
let net_service = context.persistent_container.net_service.lock().await;
net_service.get_ext_port(data.host_id, internal_port)
}
async fn clear_network_interfaces(context: EffectContext, _: Empty) -> Result<Value, Error> {
todo!()
async fn clear_network_interfaces(context: EffectContext, _: Empty) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
context
.ctx
.db
.mutate(|db| {
let model = db
.as_public_mut()
.as_package_data_mut()
.as_idx_mut(&package_id)
.or_not_found(&package_id)?
.as_service_interfaces_mut();
let mut new_map = BTreeMap::new();
model.ser(&mut new_map)
})
.await
}
async fn export_service_interface(
context: EffectContext,
@@ -397,8 +450,27 @@ async fn export_service_interface(
async fn get_primary_url(
context: EffectContext,
data: GetPrimaryUrlParams,
) -> Result<Value, Error> {
todo!()
) -> Result<HostAddress, Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let db_model = context.ctx.db.peek().await;
let pkg_data_model = db_model
.as_public()
.as_package_data()
.as_idx(&package_id)
.or_not_found(&package_id)?;
let host = pkg_data_model.de()?.hosts.get_host_primary(&data.host_id);
match host {
Some(host_address) => Ok(host_address),
None => Err(Error::new(
eyre!("Primary Url not found for {}", data.host_id),
crate::ErrorKind::NotFound,
)),
}
}
async fn list_service_interfaces(
context: EffectContext,
@@ -419,9 +491,24 @@ async fn list_service_interfaces(
.into_service_interfaces()
.de()
}
async fn remove_address(context: EffectContext, data: RemoveAddressParams) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
async fn remove_address(context: EffectContext, data: RemoveAddressParams) -> Result<Value, Error> {
todo!()
context
.ctx
.db
.mutate(|db| {
let model = db
.as_public_mut()
.as_package_data_mut()
.as_idx_mut(&package_id)
.or_not_found(&package_id)?
.as_service_interfaces_mut();
model.remove(&data.id)
})
.await?;
Ok(())
}
async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<(), Error> {
let context = context.deref()?;
@@ -607,7 +694,7 @@ fn chroot<C: Context>(
cmd.env(k, v);
}
}
nix::unistd::setsid().with_kind(ErrorKind::Lxc)?; // TODO: error code
nix::unistd::setsid().ok(); // https://stackoverflow.com/questions/25701333/os-setsid-operation-not-permitted
std::os::unix::fs::chroot(path)?;
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
cmd.uid(uid);
@@ -735,7 +822,7 @@ async fn set_store(
let model = db
.as_private_mut()
.as_package_stores_mut()
.upsert(&package_id, || json!({}))?;
.upsert(&package_id, || Ok(json!({})))?;
let mut model_value = model.de()?;
if model_value.is_null() {
model_value = json!({});
@@ -1053,7 +1140,7 @@ pub async fn create_overlayed_image(
.s9pk
.as_archive()
.contents()
.get_path(dbg!(&path))
.get_path(&path)
.and_then(|e| e.as_file())
{
let guid = new_guid();