domains api + migration

This commit is contained in:
Aiden McClelland
2025-08-06 14:29:35 -06:00
parent ea12251a7e
commit d6dfaf8feb
39 changed files with 496 additions and 87 deletions

47
core/Cargo.lock generated
View File

@@ -1923,6 +1923,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "enum-as-inner" name = "enum-as-inner"
version = "0.6.1" version = "0.6.1"
@@ -3811,6 +3817,15 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.24.3" version = "0.24.3"
@@ -4700,6 +4715,16 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@@ -6060,7 +6085,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "start-os" name = "start-os"
version = "0.4.0-alpha.9" version = "0.4.0-alpha.10"
dependencies = [ dependencies = [
"aes 0.7.5", "aes 0.7.5",
"async-acme", "async-acme",
@@ -6191,6 +6216,7 @@ dependencies = [
"tracing-futures", "tracing-futures",
"tracing-journald", "tracing-journald",
"tracing-subscriber", "tracing-subscriber",
"trust-dns-client",
"trust-dns-server", "trust-dns-server",
"ts-rs", "ts-rs",
"typed-builder", "typed-builder",
@@ -6932,6 +6958,25 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "trust-dns-client"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14135e72c7e6d4c9b6902d4437881a8598f0145dbb2e3f86f92dbad845b61e63"
dependencies = [
"cfg-if",
"data-encoding",
"futures-channel",
"futures-util",
"once_cell",
"radix_trie",
"rand 0.8.5",
"thiserror 1.0.69",
"tokio",
"tracing",
"trust-dns-proto",
]
[[package]] [[package]]
name = "trust-dns-proto" name = "trust-dns-proto"
version = "0.23.2" version = "0.23.2"

View File

@@ -14,7 +14,7 @@ keywords = [
name = "start-os" name = "start-os"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Start9Labs/start-os" repository = "https://github.com/Start9Labs/start-os"
version = "0.4.0-alpha.9" # VERSION_BUMP version = "0.4.0-alpha.10" # VERSION_BUMP
license = "MIT" license = "MIT"
[lib] [lib]
@@ -228,7 +228,8 @@ tracing-error = "0.2.0"
tracing-futures = "0.2.5" tracing-futures = "0.2.5"
tracing-journald = "0.3.0" tracing-journald = "0.3.0"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
trust-dns-server = "0.23.1" trust-dns-server = "0.23.2"
trust-dns-client = "0.23.2"
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0" ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0"
typed-builder = "0.21.0" typed-builder = "0.21.0"
unix-named-pipe = "0.2.0" unix-named-pipe = "0.2.0"

View File

@@ -12,7 +12,7 @@ use tracing::instrument;
use crate::context::config::ServerConfig; use crate::context::config::ServerConfig;
use crate::context::rpc::InitRpcContextPhases; use crate::context::rpc::InitRpcContextPhases;
use crate::context::{DiagnosticContext, InitContext, RpcContext}; use crate::context::{DiagnosticContext, InitContext, RpcContext};
use crate::net::network_interface::SelfContainedNetworkInterfaceListener; use crate::net::gateway::SelfContainedNetworkInterfaceListener;
use crate::net::web_server::{Acceptor, UpgradableListener, WebServer}; use crate::net::web_server::{Acceptor, UpgradableListener, WebServer};
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::system::launch_metrics_task; use crate::system::launch_metrics_task;

View File

@@ -92,8 +92,9 @@ impl Public {
enabled: true, enabled: true,
..Default::default() ..Default::default()
}, },
network_interfaces: OrdMap::new(), gateways: OrdMap::new(),
acme: BTreeMap::new(), acme: BTreeMap::new(),
domains: BTreeMap::new(),
}, },
status_info: ServerStatus { status_info: ServerStatus {
backup_progress: None, backup_progress: None,
@@ -191,9 +192,12 @@ pub struct NetworkInfo {
pub host: Host, pub host: Host,
#[ts(as = "BTreeMap::<GatewayId, NetworkInterfaceInfo>")] #[ts(as = "BTreeMap::<GatewayId, NetworkInterfaceInfo>")]
#[serde(default)] #[serde(default)]
pub network_interfaces: OrdMap<GatewayId, NetworkInterfaceInfo>, pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
#[serde(default)] #[serde(default)]
pub acme: BTreeMap<AcmeProvider, AcmeSettings>, pub acme: BTreeMap<AcmeProvider, AcmeSettings>,
#[serde(default)]
#[ts(as = "BTreeMap::<String, DomainSettings>")]
pub domains: BTreeMap<InternedString, DomainSettings>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)] #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
@@ -303,6 +307,14 @@ pub struct AcmeSettings {
pub contact: Vec<String>, pub contact: Vec<String>,
} }
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
#[ts(export)]
pub struct DomainSettings {
pub gateway: GatewayId,
}
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[model = "Model<Self>"] #[model = "Model<Self>"]
#[ts(export)] #[ts(export)]

View File

@@ -150,7 +150,7 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
) )
.subcommand( .subcommand(
"net", "net",
net::net::<C>().with_about("Network commands related to tor and dhcp"), net::net_api::<C>().with_about("Network commands related to tor and dhcp"),
) )
.subcommand( .subcommand(
"auth", "auth",

View File

@@ -174,7 +174,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> {
} }
} }
pub fn acme<C: Context>() -> ParentHandler<C> { pub fn acme_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new() ParentHandler::new()
.subcommand( .subcommand(
"init", "init",

View File

@@ -0,0 +1,204 @@
use std::collections::BTreeMap;
use clap::Parser;
use futures::TryFutureExt;
use helpers::NonDetachingJoinHandle;
use imbl_value::InternedString;
use models::GatewayId;
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
use crate::context::{CliContext, RpcContext};
use crate::db::model::public::DomainSettings;
use crate::prelude::*;
use crate::util::new_guid;
use crate::util::serde::{display_serializable, HandlerExtSerde};
pub fn domain_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new()
.subcommand(
"list",
from_fn_async(list)
.with_display_serializable()
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
use prettytable::*;
if let Some(format) = params.format {
return display_serializable(format, res);
}
let mut table = Table::new();
table.add_row(row![bc => "DOMAIN", "GATEWAY"]);
for (domain, info) in res {
table.add_row(row![domain, info.gateway]);
}
table.print_tty(false)?;
Ok(())
})
.with_about("List domains available to StartOS")
.with_call_remote::<CliContext>(),
)
.subcommand(
"add",
from_fn_async(add)
.with_metadata("sync_db", Value::Bool(true))
.no_display()
.with_about("Add a domain for use with StartOS")
.with_call_remote::<CliContext>(),
)
.subcommand(
"remove",
from_fn_async(remove)
.with_metadata("sync_db", Value::Bool(true))
.no_display()
.with_about("Remove a domain for use with StartOS")
.with_call_remote::<CliContext>(),
)
.subcommand(
"test-dns",
from_fn_async(test_dns)
.with_display_serializable()
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
use prettytable::*;
if let Some(format) = params.format {
return display_serializable(format, res);
}
let mut table = Table::new();
table.add_row(row![bc -> "ROOT", if res.root { "✅️" } else { "❌️" }]);
table.add_row(row![bc -> "WILDCARD", if res.wildcard { "✅️" } else { "❌️" }]);
table.print_tty(false)?;
Ok(())
})
.with_about("Test the DNS configuration for a domain"),
)
}
pub async fn list(ctx: RpcContext) -> Result<BTreeMap<InternedString, DomainSettings>, Error> {
ctx.db
.peek()
.await
.into_public()
.into_server_info()
.into_network()
.into_domains()
.de()
}
#[derive(Deserialize, Serialize, Parser)]
pub struct AddDomainParams {
pub fqdn: InternedString,
pub gateway: GatewayId,
}
pub async fn add(
ctx: RpcContext,
AddDomainParams { fqdn, gateway }: AddDomainParams,
) -> Result<(), Error> {
ctx.db
.mutate(|db| {
db.as_public_mut()
.as_server_info_mut()
.as_network_mut()
.as_domains_mut()
.insert(&fqdn, &DomainSettings { gateway })
})
.await
.result?;
Ok(())
}
#[derive(Deserialize, Serialize, Parser)]
pub struct RemoveDomainParams {
pub fqdn: InternedString,
}
pub async fn remove(
ctx: RpcContext,
RemoveDomainParams { fqdn }: RemoveDomainParams,
) -> Result<(), Error> {
ctx.db
.mutate(|db| {
db.as_public_mut()
.as_server_info_mut()
.as_network_mut()
.as_domains_mut()
.remove(&fqdn)
})
.await
.result?;
Ok(())
}
#[derive(Deserialize, Serialize)]
pub struct TestDnsResult {
pub root: bool,
pub wildcard: bool,
}
pub async fn test_dns(
ctx: RpcContext,
AddDomainParams { fqdn, ref gateway }: AddDomainParams,
) -> Result<TestDnsResult, Error> {
use tokio::net::UdpSocket;
use trust_dns_client::client::{AsyncClient, ClientHandle};
use trust_dns_client::op::DnsResponse;
use trust_dns_client::proto::error::ProtoError;
use trust_dns_client::rr::{DNSClass, Name, RecordType};
use trust_dns_client::udp::UdpClientStream;
let wan_ip = ctx
.db
.peek()
.await
.into_public()
.into_server_info()
.into_network()
.into_gateways()
.into_idx(&gateway)
.or_not_found(&gateway)?
.into_ip_info()
.transpose()
.and_then(|i| i.into_wan_ip().transpose())
.or_not_found(lazy_format!("WAN IP for {gateway}"))?
.de()?;
let stream = UdpClientStream::<UdpSocket>::new(([127, 0, 0, 53], 53).into());
let (mut client, bg) = AsyncClient::connect(stream.map_err(ProtoError::from))
.await
.with_kind(ErrorKind::Network)?;
let bg: NonDetachingJoinHandle<_> = tokio::spawn(bg).into();
let root = fqdn.parse::<Name>().with_kind(ErrorKind::Network)?;
let wildcard = new_guid()
.parse::<Name>()
.with_kind(ErrorKind::Network)?
.append_domain(&root)
.with_kind(ErrorKind::Network)?;
let q_root = client
.query(root, DNSClass::IN, RecordType::A)
.await
.with_kind(ErrorKind::Network)?;
let q_wildcard = client
.query(wildcard, DNSClass::IN, RecordType::A)
.await
.with_kind(ErrorKind::Network)?;
bg.abort();
let check_q = |q: DnsResponse| {
q.answers().iter().any(|a| {
a.data()
.and_then(|d| d.as_a())
.map_or(false, |d| d.0 == wan_ip)
})
};
Ok(TestDnsResult {
root: check_q(q_root),
wildcard: check_q(q_wildcard),
})
}

View File

@@ -12,7 +12,7 @@ use tokio::process::Command;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::db::model::public::NetworkInterfaceInfo; use crate::db::model::public::NetworkInterfaceInfo;
use crate::net::network_interface::{DynInterfaceFilter, InterfaceFilter}; use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter};
use crate::net::utils::ipv6_is_link_local; use crate::net::utils::ipv6_is_link_local;
use crate::prelude::*; use crate::prelude::*;
use crate::util::sync::Watch; use crate::util::sync::Watch;

View File

@@ -32,7 +32,7 @@ use crate::context::{CliContext, RpcContext};
use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceType}; use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceType};
use crate::db::model::Database; use crate::db::model::Database;
use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::forward::START9_BRIDGE_IFACE;
use crate::net::network_interface::device::DeviceProxy; use crate::net::gateway::device::DeviceProxy;
use crate::net::utils::ipv6_is_link_local; use crate::net::utils::ipv6_is_link_local;
use crate::net::web_server::Accept; use crate::net::web_server::Accept;
use crate::prelude::*; use crate::prelude::*;
@@ -43,7 +43,7 @@ use crate::util::serde::{display_serializable, HandlerExtSerde};
use crate::util::sync::{SyncMutex, Watch}; use crate::util::sync::{SyncMutex, Watch};
use crate::util::Invoke; use crate::util::Invoke;
pub fn network_interface_api<C: Context>() -> ParentHandler<C> { pub fn gateway_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new() ParentHandler::new()
.subcommand( .subcommand(
"list", "list",
@@ -88,7 +88,7 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
Ok(()) Ok(())
}) })
.with_about("Show network interfaces StartOS can listen on") .with_about("Show gateways StartOS can listen on")
.with_call_remote::<CliContext>(), .with_call_remote::<CliContext>(),
) )
.subcommand( .subcommand(
@@ -96,26 +96,26 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
from_fn_async(set_public) from_fn_async(set_public)
.with_metadata("sync_db", Value::Bool(true)) .with_metadata("sync_db", Value::Bool(true))
.no_display() .no_display()
.with_about("Indicate whether this interface has inbound access from the WAN") .with_about("Indicate whether this gateway has inbound access from the WAN")
.with_call_remote::<CliContext>(), .with_call_remote::<CliContext>(),
).subcommand( ).subcommand(
"unset-inbound", "unset-public",
from_fn_async(unset_public) from_fn_async(unset_public)
.with_metadata("sync_db", Value::Bool(true)) .with_metadata("sync_db", Value::Bool(true))
.no_display() .no_display()
.with_about("Allow this interface to infer whether it has inbound access from the WAN based on its IPv4 address") .with_about("Allow this gateway to infer whether it has inbound access from the WAN based on its IPv4 address")
.with_call_remote::<CliContext>(), .with_call_remote::<CliContext>(),
).subcommand("forget", ).subcommand("forget",
from_fn_async(forget_iface) from_fn_async(forget_iface)
.with_metadata("sync_db", Value::Bool(true)) .with_metadata("sync_db", Value::Bool(true))
.no_display() .no_display()
.with_about("Forget a disconnected interface") .with_about("Forget a disconnected gateway")
.with_call_remote::<CliContext>() .with_call_remote::<CliContext>()
).subcommand("set-name", ).subcommand("set-name",
from_fn_async(set_name) from_fn_async(set_name)
.with_metadata("sync_db", Value::Bool(true)) .with_metadata("sync_db", Value::Bool(true))
.no_display() .no_display()
.with_about("Rename an interface") .with_about("Rename a gateway")
.with_call_remote::<CliContext>() .with_call_remote::<CliContext>()
) )
} }
@@ -814,7 +814,7 @@ impl NetworkInterfaceController {
db.as_public_mut() db.as_public_mut()
.as_server_info_mut() .as_server_info_mut()
.as_network_mut() .as_network_mut()
.as_network_interfaces_mut() .as_gateways_mut()
.ser(info) .ser(info)
}) })
.await .await
@@ -881,7 +881,7 @@ impl NetworkInterfaceController {
.as_public() .as_public()
.as_server_info() .as_server_info()
.as_network() .as_network()
.as_network_interfaces() .as_gateways()
.de() .de()
{ {
Ok(mut info) => { Ok(mut info) => {
@@ -940,7 +940,7 @@ impl NetworkInterfaceController {
let mut sub = self let mut sub = self
.db .db
.subscribe( .subscribe(
"/public/serverInfo/network/networkInterfaces" "/public/serverInfo/network/gateways"
.parse::<JsonPointer<_, _>>() .parse::<JsonPointer<_, _>>()
.with_kind(ErrorKind::Database)?, .with_kind(ErrorKind::Database)?,
) )
@@ -973,7 +973,7 @@ impl NetworkInterfaceController {
let mut sub = self let mut sub = self
.db .db
.subscribe( .subscribe(
"/public/serverInfo/network/networkInterfaces" "/public/serverInfo/network/gateways"
.parse::<JsonPointer<_, _>>() .parse::<JsonPointer<_, _>>()
.with_kind(ErrorKind::Database)?, .with_kind(ErrorKind::Database)?,
) )
@@ -1043,7 +1043,7 @@ impl NetworkInterfaceController {
let (dump, mut sub) = self let (dump, mut sub) = self
.db .db
.dump_and_sub( .dump_and_sub(
"/public/serverInfo/network/networkInterfaces" "/public/serverInfo/network/gateways"
.parse::<JsonPointer<_, _>>() .parse::<JsonPointer<_, _>>()
.with_kind(ErrorKind::Database)? .with_kind(ErrorKind::Database)?
.join_end(interface.as_str()) .join_end(interface.as_str())

View File

@@ -34,6 +34,8 @@ pub enum HostAddress {
#[derive(Debug, Deserialize, Serialize, TS)] #[derive(Debug, Deserialize, Serialize, TS)]
pub struct DomainConfig { pub struct DomainConfig {
#[ts(type = "string")]
pub root: InternedString,
pub public: bool, pub public: bool,
pub acme: Option<AcmeProvider>, pub acme: Option<AcmeProvider>,
} }
@@ -177,7 +179,7 @@ pub struct AddDomainParams {
pub async fn add_domain<Kind: HostApiKind>( pub async fn add_domain<Kind: HostApiKind>(
ctx: RpcContext, ctx: RpcContext,
AddDomainParams { AddDomainParams {
domain, ref domain,
private, private,
acme, acme,
}: AddDomainParams, }: AddDomainParams,
@@ -185,24 +187,41 @@ pub async fn add_domain<Kind: HostApiKind>(
) -> Result<(), Error> { ) -> Result<(), Error> {
ctx.db ctx.db
.mutate(|db| { .mutate(|db| {
let root = db
.as_public()
.as_server_info()
.as_network()
.as_domains()
.keys()?
.into_iter()
.find(|root| domain.ends_with(&**root))
.or_not_found(lazy_format!("root domain for {domain}"))?;
if let Some(acme) = &acme { if let Some(acme) = &acme {
if !db.as_public().as_server_info().as_network().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)); return Err(Error::new(eyre!("unknown acme provider {}, please run acme.init for this provider first", acme.0), ErrorKind::InvalidRequest));
} }
} }
Kind::host_for(&inheritance, db)?
.as_domains_mut() Kind::host_for(&inheritance, db)?.as_domains_mut().insert(
.insert( domain,
&domain, &DomainConfig {
&DomainConfig { root,
public: !private, public: !private,
acme, acme,
}, },
)?; )?;
check_duplicates(db) check_duplicates(db)
}) })
.await.result?; .await
.result?;
Kind::sync_host(&ctx, inheritance).await?; Kind::sync_host(&ctx, inheritance).await?;
Ok(()) Ok(())

View File

@@ -13,7 +13,7 @@ use crate::context::{CliContext, RpcContext};
use crate::db::model::public::NetworkInterfaceInfo; use crate::db::model::public::NetworkInterfaceInfo;
use crate::net::forward::AvailablePorts; use crate::net::forward::AvailablePorts;
use crate::net::host::HostApiKind; use crate::net::host::HostApiKind;
use crate::net::network_interface::InterfaceFilter; use crate::net::gateway::InterfaceFilter;
use crate::net::vhost::AlpnInfo; use crate::net::vhost::AlpnInfo;
use crate::prelude::*; use crate::prelude::*;
use crate::util::serde::{display_serializable, HandlerExtSerde}; use crate::util::serde::{display_serializable, HandlerExtSerde};

View File

@@ -53,7 +53,7 @@ impl Host {
self.domains self.domains
.iter() .iter()
.map( .map(
|(address, DomainConfig { public, acme })| HostAddress::Domain { |(address, DomainConfig { public, acme, .. })| HostAddress::Domain {
address: address.clone(), address: address.clone(),
public: *public, public: *public,
acme: acme.clone(), acme: acme.clone(),

View File

@@ -2,12 +2,13 @@ use rpc_toolkit::{Context, HandlerExt, ParentHandler};
pub mod acme; pub mod acme;
pub mod dns; pub mod dns;
pub mod domain;
pub mod forward; pub mod forward;
pub mod gateway;
pub mod host; pub mod host;
pub mod keys; pub mod keys;
pub mod mdns; pub mod mdns;
pub mod net_controller; pub mod net_controller;
pub mod network_interface;
pub mod service_interface; pub mod service_interface;
pub mod ssl; pub mod ssl;
pub mod static_server; pub mod static_server;
@@ -18,20 +19,23 @@ pub mod vhost;
pub mod web_server; pub mod web_server;
pub mod wifi; pub mod wifi;
pub fn net<C: Context>() -> ParentHandler<C> { pub fn net_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new() ParentHandler::new()
.subcommand( .subcommand(
"tor", "tor",
tor::tor::<C>().with_about("Tor commands such as list-services, logs, and reset"), tor::tor_api::<C>().with_about("Tor commands such as list-services, logs, and reset"),
) )
.subcommand( .subcommand(
"acme", "acme",
acme::acme::<C>().with_about("Setup automatic clearnet certificate acquisition"), acme::acme_api::<C>().with_about("Setup automatic clearnet certificate acquisition"),
) )
.subcommand( .subcommand(
"network-interface", "domain",
network_interface::network_interface_api::<C>() domain::domain_api::<C>().with_about("Setup clearnet domains"),
.with_about("View and edit network interface configurations"), )
.subcommand(
"gateway",
gateway::gateway_api::<C>().with_about("View and edit gateway configurations"),
) )
.subcommand( .subcommand(
"tunnel", "tunnel",

View File

@@ -17,13 +17,13 @@ use crate::error::ErrorCollection;
use crate::hostname::Hostname; use crate::hostname::Hostname;
use crate::net::dns::DnsController; use crate::net::dns::DnsController;
use crate::net::forward::{PortForwardController, START9_BRIDGE_IFACE}; use crate::net::forward::{PortForwardController, START9_BRIDGE_IFACE};
use crate::net::host::address::HostAddress; use crate::net::gateway::{
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
use crate::net::host::{host_for, Host, Hosts};
use crate::net::network_interface::{
AndFilter, DynInterfaceFilter, InterfaceFilter, LoopbackFilter, NetworkInterfaceController, AndFilter, DynInterfaceFilter, InterfaceFilter, LoopbackFilter, NetworkInterfaceController,
SecureFilter, SecureFilter,
}; };
use crate::net::host::address::HostAddress;
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
use crate::net::host::{host_for, Host, Hosts};
use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname}; use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname};
use crate::net::tor::TorController; use crate::net::tor::TorController;
use crate::net::utils::ipv6_is_local; use crate::net::utils::ipv6_is_local;

View File

@@ -84,7 +84,7 @@ lazy_static! {
static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap(); static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap();
} }
pub fn tor<C: Context>() -> ParentHandler<C> { pub fn tor_api<C: Context>() -> ParentHandler<C> {
ParentHandler::new() ParentHandler::new()
.subcommand( .subcommand(
"list-services", "list-services",

View File

@@ -53,7 +53,7 @@ pub async fn add_tunnel(
.into_public() .into_public()
.into_server_info() .into_server_info()
.into_network() .into_network()
.into_network_interfaces() .into_gateways()
.keys()?; .keys()?;
let mut iface = GatewayId::from("wg0"); let mut iface = GatewayId::from("wg0");
for id in 1.. { for id in 1.. {
@@ -105,7 +105,7 @@ pub async fn remove_tunnel(
.into_public() .into_public()
.into_server_info() .into_server_info()
.into_network() .into_network()
.into_network_interfaces() .into_gateways()
.into_idx(&id) .into_idx(&id)
.and_then(|e| e.into_ip_info().transpose()) .and_then(|e| e.into_ip_info().transpose())
else { else {

View File

@@ -36,7 +36,7 @@ use crate::context::{CliContext, RpcContext};
use crate::db::model::public::NetworkInterfaceInfo; use crate::db::model::public::NetworkInterfaceInfo;
use crate::db::model::Database; use crate::db::model::Database;
use crate::net::acme::{AcmeCertCache, AcmeProvider}; use crate::net::acme::{AcmeCertCache, AcmeProvider};
use crate::net::network_interface::{ use crate::net::gateway::{
Accepted, AnyFilter, DynInterfaceFilter, InterfaceFilter, NetworkInterfaceController, Accepted, AnyFilter, DynInterfaceFilter, InterfaceFilter, NetworkInterfaceController,
NetworkInterfaceListener, NetworkInterfaceListener,
}; };

View File

@@ -14,7 +14,7 @@ use tokio::net::{TcpListener, TcpStream};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
use crate::net::network_interface::{ use crate::net::gateway::{
lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener, lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener,
}; };
use crate::net::static_server::{ use crate::net::static_server::{

View File

@@ -90,7 +90,7 @@ pub async fn get_ssl_certificate(
.as_public() .as_public()
.as_server_info() .as_server_info()
.as_network() .as_network()
.as_network_interfaces() .as_gateways()
.as_entries()? .as_entries()?
.into_iter() .into_iter()
.flat_map(|(_, net)| net.as_ip_info().transpose_ref()) .flat_map(|(_, net)| net.as_ip_info().transpose_ref())

View File

@@ -20,7 +20,7 @@ use crate::context::CliContext;
use crate::middleware::auth::AuthContext; use crate::middleware::auth::AuthContext;
use crate::middleware::signature::SignatureAuthContext; use crate::middleware::signature::SignatureAuthContext;
use crate::net::forward::PortForwardController; use crate::net::forward::PortForwardController;
use crate::net::network_interface::NetworkInterfaceWatcher; use crate::net::gateway::NetworkInterfaceWatcher;
use crate::prelude::*; use crate::prelude::*;
use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations}; use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations};
use crate::tunnel::db::TunnelDatabase; use crate::tunnel::db::TunnelDatabase;

View File

@@ -49,7 +49,9 @@ mod v0_4_0_alpha_7;
mod v0_4_0_alpha_8; mod v0_4_0_alpha_8;
mod v0_4_0_alpha_9; mod v0_4_0_alpha_9;
pub type Current = v0_4_0_alpha_9::Version; // VERSION_BUMP mod v0_4_0_alpha_10;
pub type Current = v0_4_0_alpha_10::Version; // VERSION_BUMP
impl Current { impl Current {
#[instrument(skip(self, db))] #[instrument(skip(self, db))]
@@ -161,7 +163,8 @@ enum Version {
V0_4_0_alpha_6(Wrapper<v0_4_0_alpha_6::Version>), V0_4_0_alpha_6(Wrapper<v0_4_0_alpha_6::Version>),
V0_4_0_alpha_7(Wrapper<v0_4_0_alpha_7::Version>), V0_4_0_alpha_7(Wrapper<v0_4_0_alpha_7::Version>),
V0_4_0_alpha_8(Wrapper<v0_4_0_alpha_8::Version>), V0_4_0_alpha_8(Wrapper<v0_4_0_alpha_8::Version>),
V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>), // VERSION_BUMP V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>),
V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>), // VERSION_BUMP
Other(exver::Version), Other(exver::Version),
} }
@@ -213,7 +216,8 @@ impl Version {
Self::V0_4_0_alpha_6(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_6(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_7(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_7(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_8(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_8(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
Self::Other(v) => { Self::Other(v) => {
return Err(Error::new( return Err(Error::new(
eyre!("unknown version {v}"), eyre!("unknown version {v}"),
@@ -257,7 +261,8 @@ impl Version {
Version::V0_4_0_alpha_6(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_6(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_7(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_7(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_8(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_8(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(), // VERSION_BUMP Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(), // VERSION_BUMP
Version::Other(x) => x.clone(), Version::Other(x) => x.clone(),
} }
} }

View File

@@ -72,8 +72,9 @@ impl VersionT for Version {
} }
HostAddress::Domain { address } => { HostAddress::Domain { address } => {
domains.insert( domains.insert(
address, address.clone(),
DomainConfig { DomainConfig {
root: address,
public: true, public: true,
acme: None, acme: None,
}, },

View File

@@ -0,0 +1,108 @@
use std::collections::BTreeSet;
use std::sync::Arc;
use exver::{PreReleaseSegment, VersionRange};
use imbl_value::json;
use super::v0_3_5::V0_3_0_COMPAT;
use super::{v0_4_0_alpha_9, VersionT};
use crate::prelude::*;
lazy_static::lazy_static! {
static ref V0_4_0_alpha_10: exver::Version = exver::Version::new(
[0, 4, 0],
[PreReleaseSegment::String("alpha".into()), 10.into()]
);
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Version;
impl VersionT for Version {
type Previous = v0_4_0_alpha_9::Version;
type PreUpRes = ();
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
Ok(())
}
fn semver(self) -> exver::Version {
V0_4_0_alpha_10.clone()
}
fn compat(self) -> &'static VersionRange {
&V0_3_0_COMPAT
}
#[instrument]
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
let default_gateway = db["public"]["serverInfo"]["network"]["networkInterfaces"]
.as_object()
.into_iter()
.flatten()
.find(|(_, i)| i["ipInfo"]["wanIp"].is_string())
.map(|(g, _)| g.clone());
let mut roots = BTreeSet::new();
for (_, package) in db["public"]["packageData"]
.as_object_mut()
.ok_or_else(|| {
Error::new(
eyre!("expected public.packageData to be an object"),
ErrorKind::Database,
)
})?
.iter_mut()
{
for (_, host) in package["hosts"]
.as_object_mut()
.ok_or_else(|| {
Error::new(
eyre!("expected public.packageData[id].hosts to be an object"),
ErrorKind::Database,
)
})?
.iter_mut()
{
if default_gateway.is_none() {
host["domains"] = json!({});
continue;
}
for (domain, info) in host["domains"]
.as_object_mut()
.ok_or_else(|| {
Error::new(
eyre!(
"expected public.packageData[id].hosts[id].domains to be an object"
),
ErrorKind::Database,
)
})?
.iter_mut()
{
let Some(info) = info.as_object_mut() else {
continue;
};
let root = domain.clone();
info.insert("root".into(), Value::String(Arc::new((&*root).to_owned())));
roots.insert(root);
}
}
}
let network = db["public"]["serverInfo"]["network"]
.as_object_mut()
.ok_or_else(|| {
Error::new(
eyre!("expected public.serverInfo.network to be an object"),
ErrorKind::Database,
)
})?;
network["gateways"] = network["networkInterfaces"].clone();
if let Some(gateway) = default_gateway {
for root in roots {
network["domains"][&*root] = json!({ "gateway": gateway });
}
}
Ok(Value::Null)
}
fn down(self, _db: &mut Value) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,4 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // 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 { AcmeProvider } from "./AcmeProvider"
export type DomainConfig = { public: boolean; acme: AcmeProvider | null } export type DomainConfig = {
root: string
public: boolean
acme: AcmeProvider | null
}

View 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 { GatewayId } from "./GatewayId"
export type DomainSettings = { gateway: GatewayId }

View File

@@ -1,6 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // 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 { AcmeProvider } from "./AcmeProvider"
import type { AcmeSettings } from "./AcmeSettings" import type { AcmeSettings } from "./AcmeSettings"
import type { DomainSettings } from "./DomainSettings"
import type { GatewayId } from "./GatewayId" import type { GatewayId } from "./GatewayId"
import type { Host } from "./Host" import type { Host } from "./Host"
import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo" import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
@@ -9,6 +10,7 @@ import type { WifiInfo } from "./WifiInfo"
export type NetworkInfo = { export type NetworkInfo = {
wifi: WifiInfo wifi: WifiInfo
host: Host host: Host
networkInterfaces: { [key: GatewayId]: NetworkInterfaceInfo } gateways: { [key: GatewayId]: NetworkInterfaceInfo }
acme: { [key: AcmeProvider]: AcmeSettings } acme: { [key: AcmeProvider]: AcmeSettings }
domains: { [key: string]: DomainSettings }
} }

View File

@@ -67,6 +67,7 @@ export { Description } from "./Description"
export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams"
export { DeviceFilter } from "./DeviceFilter" export { DeviceFilter } from "./DeviceFilter"
export { DomainConfig } from "./DomainConfig" export { DomainConfig } from "./DomainConfig"
export { DomainSettings } from "./DomainSettings"
export { Duration } from "./Duration" export { Duration } from "./Duration"
export { EchoParams } from "./EchoParams" export { EchoParams } from "./EchoParams"
export { EditSignerParams } from "./EditSignerParams" export { EditSignerParams } from "./EditSignerParams"

View File

@@ -12,8 +12,9 @@ export class IpAddress {
this.octets[octIdx++] = num & 255 this.octets[octIdx++] = num & 255
idx += 1 idx += 1
} }
if (idx < 7) { const lastSegIdx = segs.length - 1
idx = segs.length - 1 if (idx < lastSegIdx) {
idx = lastSegIdx
octIdx = 15 octIdx = 15
while (segs[idx]) { while (segs[idx]) {
const num = parseInt(segs[idx], 16) const num = parseInt(segs[idx], 16)

View File

@@ -61,7 +61,7 @@ import {
} from "../../base/lib/inits" } from "../../base/lib/inits"
import { DropGenerator } from "../../base/lib/util/Drop" import { DropGenerator } from "../../base/lib/util/Drop"
export const OSVersion = testTypeVersion("0.4.0-alpha.9") export const OSVersion = testTypeVersion("0.4.0-alpha.10")
// prettier-ignore // prettier-ignore
type AnyNeverCond<T extends any[], Then, Else> = type AnyNeverCond<T extends any[], Then, Else> =

4
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "startos-ui", "name": "startos-ui",
"version": "0.4.0-alpha.9", "version": "0.4.0-alpha.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "startos-ui", "name": "startos-ui",
"version": "0.4.0-alpha.9", "version": "0.4.0-alpha.10",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@angular/animations": "^20.1.0", "@angular/animations": "^20.1.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "startos-ui", "name": "startos-ui",
"version": "0.4.0-alpha.9", "version": "0.4.0-alpha.10",
"author": "Start9 Labs, Inc", "author": "Start9 Labs, Inc",
"homepage": "https://start9.com/", "homepage": "https://start9.com/",
"license": "MIT", "license": "MIT",

View File

@@ -47,10 +47,8 @@ export class DomainService {
readonly data = toSignal( readonly data = toSignal(
this.patch.watch$('serverInfo', 'network').pipe( this.patch.watch$('serverInfo', 'network').pipe(
map(({ networkInterfaces, domains, acme }) => ({ map(({ gateways, domains, acme }) => ({
gateways: Object.entries(networkInterfaces).reduce< gateways: Object.entries(gateways).reduce<Record<string, string>>(
Record<string, string>
>(
(obj, [id, n]) => ({ (obj, [id, n]) => ({
...obj, ...obj,
[id]: n.ipInfo?.name || '', [id]: n.ipInfo?.name || '',
@@ -64,7 +62,7 @@ export class DomainService {
subdomain: parse(fqdn).subdomain, subdomain: parse(fqdn).subdomain,
gateway: { gateway: {
id: gateway, id: gateway,
ipInfo: networkInterfaces[gateway]?.ipInfo || null, ipInfo: gateways[gateway]?.ipInfo || null,
}, },
authority: { authority: {
url: acme, url: acme,

View File

@@ -84,7 +84,7 @@ export default class GatewaysComponent {
private readonly formDialog = inject(FormDialogService) private readonly formDialog = inject(FormDialogService)
readonly gateways$ = inject<PatchDB<DataModel>>(PatchDB) readonly gateways$ = inject<PatchDB<DataModel>>(PatchDB)
.watch$('serverInfo', 'network', 'networkInterfaces') .watch$('serverInfo', 'network', 'gateways')
.pipe( .pipe(
map(gateways => map(gateways =>
Object.entries(gateways) Object.entries(gateways)

View File

@@ -110,7 +110,7 @@ export namespace Mock {
squashfs: { squashfs: {
aarch64: { aarch64: {
publishedAt: '2025-04-21T20:58:48.140749883Z', publishedAt: '2025-04-21T20:58:48.140749883Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_aarch64.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_aarch64.squashfs',
commitment: { commitment: {
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=', hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
size: 1343500288, size: 1343500288,
@@ -122,7 +122,7 @@ export namespace Mock {
}, },
'aarch64-nonfree': { 'aarch64-nonfree': {
publishedAt: '2025-04-21T21:07:00.249285116Z', publishedAt: '2025-04-21T21:07:00.249285116Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_aarch64-nonfree.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_aarch64-nonfree.squashfs',
commitment: { commitment: {
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=', hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
size: 1653075968, size: 1653075968,
@@ -134,7 +134,7 @@ export namespace Mock {
}, },
raspberrypi: { raspberrypi: {
publishedAt: '2025-04-21T21:16:12.933319237Z', publishedAt: '2025-04-21T21:16:12.933319237Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_raspberrypi.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_raspberrypi.squashfs',
commitment: { commitment: {
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=', hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
size: 1490731008, size: 1490731008,
@@ -146,7 +146,7 @@ export namespace Mock {
}, },
x86_64: { x86_64: {
publishedAt: '2025-04-21T21:14:20.246908903Z', publishedAt: '2025-04-21T21:14:20.246908903Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_x86_64.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_x86_64.squashfs',
commitment: { commitment: {
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=', hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
size: 1411657728, size: 1411657728,
@@ -158,7 +158,7 @@ export namespace Mock {
}, },
'x86_64-nonfree': { 'x86_64-nonfree': {
publishedAt: '2025-04-21T21:15:17.955265284Z', publishedAt: '2025-04-21T21:15:17.955265284Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_x86_64-nonfree.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_x86_64-nonfree.squashfs',
commitment: { commitment: {
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=', hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
size: 1731035136, size: 1731035136,

View File

@@ -269,7 +269,7 @@ export namespace RR {
export type UpdateTunnelReq = { export type UpdateTunnelReq = {
id: string id: string
name: string name: string
} // net.netwok-interface.set-name } // net.gateway.set-name
export type UpdateTunnelRes = null export type UpdateTunnelRes = null
export type RemoveTunnelReq = { id: string } // net.tunnel.remove export type RemoveTunnelReq = { id: string } // net.tunnel.remove

View File

@@ -351,7 +351,7 @@ export class LiveApiService extends ApiService {
} }
async updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes> { async updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes> {
return this.rpcRequest({ method: 'net.netwok-interface.set-name', params }) return this.rpcRequest({ method: 'net.gateway.set-name', params })
} }
async removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes> { async removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes> {

View File

@@ -553,7 +553,7 @@ export class MockApiService extends ApiService {
const patch: AddOperation<T.NetworkInterfaceInfo>[] = [ const patch: AddOperation<T.NetworkInterfaceInfo>[] = [
{ {
op: PatchOp.ADD, op: PatchOp.ADD,
path: `/serverInfo/network/networkInterfaces/${id}`, path: `/serverInfo/network/gateways/${id}`,
value: { value: {
public: params.public, public: params.public,
secure: false, secure: false,
@@ -579,7 +579,7 @@ export class MockApiService extends ApiService {
const patch: ReplaceOperation<string>[] = [ const patch: ReplaceOperation<string>[] = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: `/serverInfo/network/networkInterfaces/${params.id}/label`, path: `/serverInfo/network/gateways/${params.id}/label`,
value: params.name, value: params.name,
}, },
] ]
@@ -593,7 +593,7 @@ export class MockApiService extends ApiService {
const patch: RemoveOperation[] = [ const patch: RemoveOperation[] = [
{ {
op: PatchOp.REMOVE, op: PatchOp.REMOVE,
path: `/serverInfo/network/networkInterfaces/${params.id}`, path: `/serverInfo/network/gateways/${params.id}`,
}, },
] ]
this.mockRevision(patch) this.mockRevision(patch)

View File

@@ -145,7 +145,7 @@ export const mockPatchData: DataModel = {
], ],
}, },
}, },
networkInterfaces: { gateways: {
eth0: { eth0: {
public: null, public: null,
secure: null, secure: null,

View File

@@ -26,14 +26,14 @@
// ) {} // ) {}
// async presentModalSetOutboundProxy(current: string | null, pkgId?: string) { // async presentModalSetOutboundProxy(current: string | null, pkgId?: string) {
// const networkInterfaces = await firstValueFrom( // const gateways = await firstValueFrom(
// this.patch.watch$('serverInfo', 'network', 'networkInterfaces'), // this.patch.watch$('serverInfo', 'network', 'gateways'),
// ) // )
// const config = ISB.InputSpec.of({ // const config = ISB.InputSpec.of({
// proxyId: ISB.Value.select({ // proxyId: ISB.Value.select({
// name: 'Select Proxy', // name: 'Select Proxy',
// default: current || '', // default: current || '',
// values: Object.entries(networkInterfaces) // values: Object.entries(gateways)
// .filter( // .filter(
// ([_, n]) => n.outbound && n.ipInfo?.deviceType === 'wireguard', // ([_, n]) => n.outbound && n.ipInfo?.deviceType === 'wireguard',
// ) // )