Feature/network (#2622)

* Feature: Add in the clear bindings

* wip: Working on network

* fix: Make it so the config gives the url

* chore: Remove the repeated types

* chore: Add in the todo's here

* chore: UPdate and remove some poorly name var

* chore: Remove the clear-bindings impl

* chore: Remove the wrapper

* handle HostnameInfo for Host bindings

Co-authored-by: Jade <Blu-J@users.noreply.github.com>

* ??

* chore: Make the install work

* Fix: Url's not being created

* chore: Fix the local onion in url

* include port in hostname

* Chore of adding a comment just to modify.

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Jade <Blu-J@users.noreply.github.com>
This commit is contained in:
Jade
2024-06-06 15:39:54 -06:00
committed by GitHub
parent 412c5d68cc
commit 2c12af5af8
67 changed files with 798 additions and 1516 deletions

View File

@@ -14,7 +14,7 @@
"filebrowser": "^1.0.0", "filebrowser": "^1.0.0",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"node-fetch": "^3.1.0", "node-fetch": "^3.1.0",
"ts-matches": "^5.4.1", "ts-matches": "^5.5.1",
"tslib": "^2.5.3", "tslib": "^2.5.3",
"typescript": "^5.1.3", "typescript": "^5.1.3",
"yaml": "^2.3.1" "yaml": "^2.3.1"
@@ -29,7 +29,7 @@
}, },
"../sdk/dist": { "../sdk/dist": {
"name": "@start9labs/start-sdk", "name": "@start9labs/start-sdk",
"version": "0.4.0-rev0.lib0.rc8.beta10", "version": "0.3.6-alpha1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
@@ -2428,9 +2428,9 @@
} }
}, },
"node_modules/ts-matches": { "node_modules/ts-matches": {
"version": "5.4.1", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.4.1.tgz", "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.5.1.tgz",
"integrity": "sha512-kXrY75F0s0WD15N2bWKDScKlKgwnusN6dTRzGs1N7LlxQRnazrsBISC1HL4sy2adsyk65Zbx3Ui3IGN8leAFOQ==" "integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg=="
}, },
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.6.2", "version": "2.6.2",
@@ -4195,9 +4195,9 @@
} }
}, },
"ts-matches": { "ts-matches": {
"version": "5.4.1", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.4.1.tgz", "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.5.1.tgz",
"integrity": "sha512-kXrY75F0s0WD15N2bWKDScKlKgwnusN6dTRzGs1N7LlxQRnazrsBISC1HL4sy2adsyk65Zbx3Ui3IGN8leAFOQ==" "integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg=="
}, },
"tslib": { "tslib": {
"version": "2.6.2", "version": "2.6.2",

View File

@@ -22,7 +22,7 @@
"filebrowser": "^1.0.0", "filebrowser": "^1.0.0",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"node-fetch": "^3.1.0", "node-fetch": "^3.1.0",
"ts-matches": "^5.4.1", "ts-matches": "^5.5.1",
"tslib": "^2.5.3", "tslib": "^2.5.3",
"typescript": "^5.1.3", "typescript": "^5.1.3",
"yaml": "^2.3.1" "yaml": "^2.3.1"

View File

@@ -96,7 +96,10 @@ export class HostSystemStartOs implements Effects {
} }
bind(...[options]: Parameters<T.Effects["bind"]>) { bind(...[options]: Parameters<T.Effects["bind"]>) {
return this.rpcRound("bind", options) as ReturnType<T.Effects["bind"]> return this.rpcRound("bind", {
...options,
stack: new Error().stack,
}) as ReturnType<T.Effects["bind"]>
} }
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) { clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
return this.rpcRound("clearBindings", null) as ReturnType< return this.rpcRound("clearBindings", null) as ReturnType<
@@ -228,11 +231,6 @@ export class HostSystemStartOs implements Effects {
restart(...[]: Parameters<T.Effects["restart"]>) { restart(...[]: Parameters<T.Effects["restart"]>) {
return this.rpcRound("restart", null) return this.rpcRound("restart", null)
} }
reverseProxy(...[options]: Parameters<T.Effects["reverseProxy"]>) {
return this.rpcRound("reverseProxy", options) as ReturnType<
T.Effects["reverseProxy"]
>
}
running(...[packageId]: Parameters<T.Effects["running"]>) { running(...[packageId]: Parameters<T.Effects["running"]>) {
return this.rpcRound("running", { packageId }) as ReturnType< return this.rpcRound("running", { packageId }) as ReturnType<
T.Effects["running"] T.Effects["running"]

View File

@@ -97,11 +97,9 @@ export class MainLoop {
id: interfaceId, id: interfaceId,
internalPort, internalPort,
preferredExternalPort: torConf?.external || internalPort, preferredExternalPort: torConf?.external || internalPort,
scheme: "http",
secure: null, secure: null,
addSsl: lanConf?.ssl addSsl: lanConf?.ssl
? { ? {
scheme: "https",
preferredExternalPort: lanConf.external, preferredExternalPort: lanConf.external,
alpn: { specified: ["http/1.1"] }, alpn: { specified: ["http/1.1"] },
} }

View File

@@ -31,6 +31,16 @@ import { HostSystemStartOs } from "../../HostSystemStartOs"
import { JsonPath, unNestPath } from "../../../Models/JsonPath" import { JsonPath, unNestPath } from "../../../Models/JsonPath"
import { RpcResult, matchRpcResult } from "../../RpcListener" import { RpcResult, matchRpcResult } from "../../RpcListener"
import { CT } from "@start9labs/start-sdk" import { CT } from "@start9labs/start-sdk"
import {
AddSslOptions,
BindOptions,
} from "@start9labs/start-sdk/cjs/lib/osBindings"
import {
BindOptionsByProtocol,
Host,
MultiHost,
} from "@start9labs/start-sdk/cjs/lib/interfaces/Host"
import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder"
type Optional<A> = A | undefined | null type Optional<A> = A | undefined | null
function todo(): never { function todo(): never {
@@ -335,6 +345,85 @@ export class SystemForEmbassy implements System {
await this.migration(effects, previousVersion, timeoutMs) await this.migration(effects, previousVersion, timeoutMs)
await effects.setMainStatus({ status: "stopped" }) await effects.setMainStatus({ status: "stopped" })
await this.exportActions(effects) await this.exportActions(effects)
await this.exportNetwork(effects)
}
async exportNetwork(effects: HostSystemStartOs) {
for (const [id, interfaceValue] of Object.entries(
this.manifest.interfaces,
)) {
const host = new MultiHost({ effects, id })
const internalPorts = new Set(
Object.values(interfaceValue["tor-config"]?.["port-mapping"] ?? {})
.map(Number.parseInt)
.concat(
...Object.values(interfaceValue["lan-config"] ?? {}).map(
(c) => c.internal,
),
)
.filter(Boolean),
)
const bindings = Array.from(internalPorts).map<
[number, BindOptionsByProtocol]
>((port) => {
const lanPort = Object.entries(interfaceValue["lan-config"] ?? {}).find(
([external, internal]) => internal.internal === port,
)?.[0]
const torPort = Object.entries(
interfaceValue["tor-config"]?.["port-mapping"] ?? {},
).find(
([external, internal]) => Number.parseInt(internal) === port,
)?.[0]
let addSsl: AddSslOptions | null = null
if (lanPort) {
const lanPortNum = Number.parseInt(lanPort)
if (lanPortNum === 443) {
return [port, { protocol: "http", preferredExternalPort: 80 }]
}
addSsl = {
preferredExternalPort: lanPortNum,
alpn: { specified: [] },
}
}
return [
port,
{
secure: null,
preferredExternalPort: Number.parseInt(
torPort || lanPort || String(port),
),
addSsl,
},
]
})
await Promise.all(
bindings.map(async ([internal, options]) => {
if (internal == null) {
return
}
if (options?.preferredExternalPort == null) {
return
}
const origin = await host.bindPort(internal, options)
await origin.export([
new ServiceInterfaceBuilder({
effects,
name: interfaceValue.name,
id: `${id}-${internal}`,
description: interfaceValue.description,
hasPrimary: false,
disabled: false,
type: "api",
masked: false,
path: "",
schemeOverride: null,
search: {},
username: null,
}),
])
}),
)
}
} }
async exportActions(effects: HostSystemStartOs) { async exportActions(effects: HostSystemStartOs) {
const manifest = this.manifest const manifest = this.manifest
@@ -486,6 +575,7 @@ export class SystemForEmbassy implements System {
const newConfig = structuredClone(newConfigWithoutPointers) const newConfig = structuredClone(newConfigWithoutPointers)
await updateConfig( await updateConfig(
effects, effects,
this.manifest,
await this.getConfigUncleaned(effects, timeoutMs).then((x) => x.spec), await this.getConfigUncleaned(effects, timeoutMs).then((x) => x.spec),
newConfig, newConfig,
) )
@@ -866,6 +956,7 @@ function cleanConfigFromPointers<C, S>(
async function updateConfig( async function updateConfig(
effects: HostSystemStartOs, effects: HostSystemStartOs,
manifest: Manifest,
spec: unknown, spec: unknown,
mutConfigValue: unknown, mutConfigValue: unknown,
) { ) {
@@ -877,7 +968,12 @@ async function updateConfig(
const newConfigValue = mutConfigValue[key] const newConfigValue = mutConfigValue[key]
if (matchSpec.test(specValue)) { if (matchSpec.test(specValue)) {
const updateObject = { spec: null } const updateObject = { spec: null }
await updateConfig(effects, { spec: specValue.spec }, updateObject) await updateConfig(
effects,
manifest,
{ spec: specValue.spec },
updateObject,
)
mutConfigValue[key] = updateObject.spec mutConfigValue[key] = updateObject.spec
} }
if ( if (
@@ -899,20 +995,48 @@ async function updateConfig(
if (matchPointerPackage.test(specValue)) { if (matchPointerPackage.test(specValue)) {
if (specValue.target === "tor-key") if (specValue.target === "tor-key")
throw new Error("This service uses an unsupported target TorKey") throw new Error("This service uses an unsupported target TorKey")
const specInterface = specValue.interface
const serviceInterfaceId = extractServiceInterfaceId(
manifest,
specInterface,
)
const filled = await utils const filled = await utils
.getServiceInterface(effects, { .getServiceInterface(effects, {
packageId: specValue["package-id"], packageId: specValue["package-id"],
id: specValue.interface, id: serviceInterfaceId,
}) })
.once() .once()
.catch(() => null) .catch((x) => {
console.error("Could not get the service interface", x)
mutConfigValue[key] = return null
})
const catchFn = <X>(fn: () => X) => {
try {
return fn()
} catch (e) {
return undefined
}
}
const url: string =
filled === null filled === null
? "" ? ""
: specValue.target === "lan-address" : catchFn(() =>
? filled.addressInfo.localHostnames[0] utils.hostnameInfoToAddress(
: filled.addressInfo.onionHostnames[0] specValue.target === "lan-address"
? filled.addressInfo.localHostnames[0] ||
filled.addressInfo.onionHostnames[0]
: filled.addressInfo.onionHostnames[0] ||
filled.addressInfo.localHostnames[0],
),
) || ""
mutConfigValue[key] = url
} }
} }
} }
function extractServiceInterfaceId(manifest: Manifest, specInterface: string) {
let serviceInterfaceId
const lanConfig = manifest.interfaces[specInterface]?.["lan-config"] || {}
serviceInterfaceId = `${specInterface}-${Object.entries(lanConfig)[0]?.[1]?.internal}`
return serviceInterfaceId
}

View File

@@ -72,6 +72,7 @@ export const matchManifest = object(
object( object(
{ {
name: string, name: string,
description: string,
"tor-config": object({ "tor-config": object({
"port-mapping": dictionary([string, string]), "port-mapping": dictionary([string, string]),
}), }),

View File

@@ -99,6 +99,7 @@ export type Effects = {
/** Sandbox mode lets us read but not write */ /** Sandbox mode lets us read but not write */
is_sandboxed(): boolean is_sandboxed(): boolean
// Does a volume and path exist?
exists(input: { volumeId: string; path: string }): Promise<boolean> exists(input: { volumeId: string; path: string }): Promise<boolean>
fetch( fetch(

View File

@@ -24,7 +24,7 @@ pub use service_interface::ServiceInterfaceId;
pub use volume::VolumeId; pub use volume::VolumeId;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref ID_REGEX: Regex = Regex::new("^[a-z]+(-[a-z]+)*$").unwrap(); static ref ID_REGEX: Regex = Regex::new("^[a-z]+(-[a-z0-9]+)*$").unwrap();
pub static ref SYSTEM_ID: Id = Id(InternedString::intern("x_system")); pub static ref SYSTEM_ID: Id = Id(InternedString::intern("x_system"));
} }

View File

@@ -10,8 +10,8 @@ use reqwest::Url;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
use crate::net::host::HostInfo; use crate::net::host::Hosts;
use crate::net::service_interface::ServiceInterfaceWithHostInfo; use crate::net::service_interface::ServiceInterface;
use crate::prelude::*; use crate::prelude::*;
use crate::progress::FullProgress; use crate::progress::FullProgress;
use crate::s9pk::manifest::Manifest; use crate::s9pk::manifest::Manifest;
@@ -333,8 +333,8 @@ pub struct PackageDataEntry {
pub last_backup: Option<DateTime<Utc>>, pub last_backup: Option<DateTime<Utc>>,
pub current_dependencies: CurrentDependencies, pub current_dependencies: CurrentDependencies,
pub actions: BTreeMap<ActionId, ActionMetadata>, pub actions: BTreeMap<ActionId, ActionMetadata>,
pub service_interfaces: BTreeMap<ServiceInterfaceId, ServiceInterfaceWithHostInfo>, pub service_interfaces: BTreeMap<ServiceInterfaceId, ServiceInterface>,
pub hosts: HostInfo, pub hosts: Hosts,
#[ts(type = "string[]")] #[ts(type = "string[]")]
pub store_exposed_dependents: Vec<JsonPointer>, pub store_exposed_dependents: Vec<JsonPointer>,
} }

View File

@@ -1,8 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use rpc_toolkit::{ use rpc_toolkit::{from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler};
from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::context::{CliContext, RpcContext}; use crate::context::{CliContext, RpcContext};

View File

@@ -3,7 +3,7 @@ use std::net::IpAddr;
use clap::Parser; use clap::Parser;
use futures::TryStreamExt; use futures::TryStreamExt;
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use ts_rs::TS; use ts_rs::TS;

View File

@@ -1,4 +1,3 @@
use imbl_value::InternedString;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
@@ -11,17 +10,31 @@ use crate::prelude::*;
#[ts(export)] #[ts(export)]
pub struct BindInfo { pub struct BindInfo {
pub options: BindOptions, pub options: BindOptions,
pub assigned_lan_port: Option<u16>, pub lan: LanInfo,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct LanInfo {
pub assigned_port: Option<u16>,
pub assigned_ssl_port: Option<u16>,
} }
impl BindInfo { impl BindInfo {
pub fn new(available_ports: &mut AvailablePorts, options: BindOptions) -> Result<Self, Error> { pub fn new(available_ports: &mut AvailablePorts, options: BindOptions) -> Result<Self, Error> {
let mut assigned_lan_port = None; let mut assigned_port = None;
if options.add_ssl.is_some() || options.secure.is_some() { let mut assigned_ssl_port = None;
assigned_lan_port = Some(available_ports.alloc()?); if options.secure.is_some() {
assigned_port = Some(available_ports.alloc()?);
}
if options.add_ssl.is_some() {
assigned_ssl_port = Some(available_ports.alloc()?);
} }
Ok(Self { Ok(Self {
options, options,
assigned_lan_port, lan: LanInfo {
assigned_port,
assigned_ssl_port,
},
}) })
} }
pub fn update( pub fn update(
@@ -29,29 +42,38 @@ impl BindInfo {
available_ports: &mut AvailablePorts, available_ports: &mut AvailablePorts,
options: BindOptions, options: BindOptions,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let Self { let Self { mut lan, .. } = self;
mut assigned_lan_port, if options
.. .secure
} = self; .map_or(false, |s| !(s.ssl && options.add_ssl.is_some()))
if options.add_ssl.is_some() || options.secure.is_some() { // doesn't make sense to have 2 listening ports, both with ssl
assigned_lan_port = if let Some(port) = assigned_lan_port.take() { {
lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
Some(port) Some(port)
} else { } else {
Some(available_ports.alloc()?) Some(available_ports.alloc()?)
}; };
} else { } else {
if let Some(port) = assigned_lan_port.take() { if let Some(port) = lan.assigned_port.take() {
available_ports.free([port]); available_ports.free([port]);
} }
} }
Ok(Self { if options.add_ssl.is_some() {
options, lan.assigned_ssl_port = if let Some(port) = lan.assigned_ssl_port.take() {
assigned_lan_port, Some(port)
}) } else {
Some(available_ports.alloc()?)
};
} else {
if let Some(port) = lan.assigned_ssl_port.take() {
available_ports.free([port]);
}
}
Ok(Self { options, lan })
} }
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Security { pub struct Security {
@@ -62,8 +84,6 @@ pub struct Security {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export)] #[ts(export)]
pub struct BindOptions { pub struct BindOptions {
#[ts(type = "string | null")]
pub scheme: Option<InternedString>,
pub preferred_external_port: u16, pub preferred_external_port: u16,
pub add_ssl: Option<AddSslOptions>, pub add_ssl: Option<AddSslOptions>,
pub secure: Option<Security>, pub secure: Option<Security>,
@@ -73,11 +93,8 @@ pub struct BindOptions {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export)] #[ts(export)]
pub struct AddSslOptions { pub struct AddSslOptions {
#[ts(type = "string | null")]
pub scheme: Option<InternedString>,
pub preferred_external_port: u16, pub preferred_external_port: u16,
// #[serde(default)] // #[serde(default)]
// pub add_x_forwarded_headers: bool, // TODO // pub add_x_forwarded_headers: bool, // TODO
#[serde(default)] pub alpn: Option<AlpnInfo>,
pub alpn: AlpnInfo,
} }

View File

@@ -3,13 +3,13 @@ use std::collections::{BTreeMap, BTreeSet};
use imbl_value::InternedString; use imbl_value::InternedString;
use models::{HostId, PackageId}; use models::{HostId, PackageId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
use ts_rs::TS; use ts_rs::TS;
use crate::db::model::DatabaseModel; use crate::db::model::DatabaseModel;
use crate::net::forward::AvailablePorts; use crate::net::forward::AvailablePorts;
use crate::net::host::address::HostAddress; use crate::net::host::address::HostAddress;
use crate::net::host::binding::{BindInfo, BindOptions}; use crate::net::host::binding::{BindInfo, BindOptions};
use crate::net::service_interface::HostnameInfo;
use crate::prelude::*; use crate::prelude::*;
pub mod address; pub mod address;
@@ -23,7 +23,8 @@ pub struct Host {
pub kind: HostKind, pub kind: HostKind,
pub bindings: BTreeMap<u16, BindInfo>, pub bindings: BTreeMap<u16, BindInfo>,
pub addresses: BTreeSet<HostAddress>, pub addresses: BTreeSet<HostAddress>,
pub primary: Option<HostAddress>, /// COMPUTED: NetService::update
pub hostname_info: BTreeMap<u16, Vec<HostnameInfo>>, // internal port -> Hostnames
} }
impl AsRef<Host> for Host { impl AsRef<Host> for Host {
fn as_ref(&self) -> &Host { fn as_ref(&self) -> &Host {
@@ -36,7 +37,7 @@ impl Host {
kind, kind,
bindings: BTreeMap::new(), bindings: BTreeMap::new(),
addresses: BTreeSet::new(), addresses: BTreeSet::new(),
primary: None, hostname_info: BTreeMap::new(),
} }
} }
} }
@@ -53,9 +54,9 @@ pub enum HostKind {
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[model = "Model<Self>"] #[model = "Model<Self>"]
#[ts(export)] #[ts(export)]
pub struct HostInfo(BTreeMap<HostId, Host>); pub struct Hosts(pub BTreeMap<HostId, Host>);
impl Map for HostInfo { impl Map for Hosts {
type Key = HostId; type Key = HostId;
type Value = Host; type Value = Host;
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> { fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
@@ -75,7 +76,7 @@ pub fn host_for<'a>(
fn host_info<'a>( fn host_info<'a>(
db: &'a mut DatabaseModel, db: &'a mut DatabaseModel,
package_id: &PackageId, package_id: &PackageId,
) -> Result<&'a mut Model<HostInfo>, Error> { ) -> Result<&'a mut Model<Hosts>, Error> {
Ok::<_, Error>( Ok::<_, Error>(
db.as_public_mut() db.as_public_mut()
.as_package_data_mut() .as_package_data_mut()
@@ -129,9 +130,3 @@ impl Model<Host> {
}) })
} }
} }
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

@@ -4,7 +4,6 @@ use std::sync::{Arc, Weak};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use imbl::OrdMap; use imbl::OrdMap;
use lazy_format::lazy_format;
use models::{HostId, OptionExt, PackageId}; use models::{HostId, OptionExt, PackageId};
use torut::onion::{OnionAddressV3, TorSecretKeyV3}; use torut::onion::{OnionAddressV3, TorSecretKeyV3};
use tracing::instrument; use tracing::instrument;
@@ -15,8 +14,9 @@ use crate::hostname::Hostname;
use crate::net::dns::DnsController; use crate::net::dns::DnsController;
use crate::net::forward::LanPortForwardController; use crate::net::forward::LanPortForwardController;
use crate::net::host::address::HostAddress; use crate::net::host::address::HostAddress;
use crate::net::host::binding::{AddSslOptions, BindOptions}; use crate::net::host::binding::{AddSslOptions, BindOptions, LanInfo};
use crate::net::host::{host_for, Host, HostKind}; use crate::net::host::{host_for, Host, HostKind};
use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname};
use crate::net::tor::TorController; use crate::net::tor::TorController;
use crate::net::vhost::{AlpnInfo, VHostController}; use crate::net::vhost::{AlpnInfo, VHostController};
use crate::prelude::*; use crate::prelude::*;
@@ -164,7 +164,7 @@ impl NetController {
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct HostBinds { struct HostBinds {
lan: BTreeMap<u16, (u16, Option<AddSslOptions>, Arc<()>)>, lan: BTreeMap<u16, (LanInfo, Option<AddSslOptions>, Vec<Arc<()>>)>,
tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>, tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
} }
@@ -209,105 +209,173 @@ impl NetService {
.await?; .await?;
self.update(id, host).await self.update(id, host).await
} }
pub async fn clear_bindings(&mut self) -> Result<(), Error> {
// TODO BLUJ
Ok(())
}
async fn update(&mut self, id: HostId, host: Host) -> Result<(), Error> { async fn update(&mut self, id: HostId, host: Host) -> Result<(), Error> {
dbg!(&host);
dbg!(&self.binds);
let ctrl = self.net_controller()?; let ctrl = self.net_controller()?;
let mut hostname_info = BTreeMap::new();
let binds = { let binds = {
if !self.binds.contains_key(&id) { if !self.binds.contains_key(&id) {
self.binds.insert(id.clone(), Default::default()); self.binds.insert(id.clone(), Default::default());
} }
self.binds.get_mut(&id).unwrap() self.binds.get_mut(&id).unwrap()
}; };
if true let peek = ctrl.db.peek().await;
// TODO: if should listen lan
{ // LAN
for (port, bind) in &host.bindings { let server_info = peek.as_public().as_server_info();
let old_lan_bind = binds.lan.remove(port); let ip_info = server_info.as_ip_info().de()?;
let old_lan_port = old_lan_bind.as_ref().map(|(external, _, _)| *external); let hostname = server_info.as_hostname().de()?;
let lan_bind = old_lan_bind.filter(|(external, ssl, _)| { for (port, bind) in &host.bindings {
ssl == &bind.options.add_ssl let old_lan_bind = binds.lan.remove(port);
&& bind.assigned_lan_port.as_ref() == Some(external) let old_lan_port = old_lan_bind.as_ref().map(|(external, _, _)| *external);
}); // only keep existing binding if relevant details match let lan_bind = old_lan_bind
if let Some(external) = bind.assigned_lan_port { .filter(|(external, ssl, _)| ssl == &bind.options.add_ssl && bind.lan == *external); // only keep existing binding if relevant details match
let new_lan_bind = if let Some(b) = lan_bind { if bind.lan.assigned_port.is_some() || bind.lan.assigned_ssl_port.is_some() {
b let new_lan_bind = if let Some(b) = lan_bind {
} else { b
if let Some(ssl) = &bind.options.add_ssl { } else {
let rc = ctrl let mut rcs = Vec::with_capacity(2);
.vhost if let Some(ssl) = &bind.options.add_ssl {
let external = bind
.lan
.assigned_ssl_port
.or_not_found("assigned ssl port")?;
rcs.push(
ctrl.vhost
.add( .add(
None, None,
external, external,
(self.ip, *port).into(), (self.ip, *port).into(),
if bind.options.secure.as_ref().map_or(false, |s| s.ssl) { if let Some(alpn) = ssl.alpn.clone() {
Ok(()) Err(alpn)
} else { } else {
Err(ssl.alpn.clone()) if bind.options.secure.as_ref().map_or(false, |s| s.ssl) {
Ok(())
} else {
Err(AlpnInfo::Reflect)
}
}, },
) )
.await?; .await?,
(*port, Some(ssl.clone()), rc) );
}
if let Some(security) = bind.options.secure {
if bind.options.add_ssl.is_some() && security.ssl {
// doesn't make sense to have 2 listening ports, both with ssl
} else { } else {
let rc = ctrl.forward.add(external, (self.ip, *port).into()).await?; let external =
(*port, None, rc) bind.lan.assigned_port.or_not_found("assigned lan port")?;
rcs.push(ctrl.forward.add(external, (self.ip, *port).into()).await?);
} }
}; }
binds.lan.insert(*port, new_lan_bind); (bind.lan, bind.options.add_ssl.clone(), rcs)
};
let mut bind_hostname_info: Vec<HostnameInfo> =
hostname_info.remove(port).unwrap_or_default();
for (interface, ip_info) in &ip_info {
bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(),
public: false,
hostname: IpHostname::Local {
value: format!("{hostname}.local"),
port: new_lan_bind.0.assigned_port,
ssl_port: new_lan_bind.0.assigned_ssl_port,
},
});
if let Some(ipv4) = ip_info.ipv4 {
bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(),
public: false,
hostname: IpHostname::Ipv4 {
value: ipv4,
port: new_lan_bind.0.assigned_port,
ssl_port: new_lan_bind.0.assigned_ssl_port,
},
});
}
if let Some(ipv6) = ip_info.ipv6 {
bind_hostname_info.push(HostnameInfo::Ip {
network_interface_id: interface.clone(),
public: false,
hostname: IpHostname::Ipv6 {
value: ipv6,
port: new_lan_bind.0.assigned_port,
ssl_port: new_lan_bind.0.assigned_ssl_port,
},
});
}
} }
if let Some(external) = old_lan_port { hostname_info.insert(*port, bind_hostname_info);
binds.lan.insert(*port, new_lan_bind);
}
if let Some(lan) = old_lan_port {
if let Some(external) = lan.assigned_ssl_port {
ctrl.vhost.gc(None, external).await?; ctrl.vhost.gc(None, external).await?;
}
if let Some(external) = lan.assigned_port {
ctrl.forward.gc(external).await?; ctrl.forward.gc(external).await?;
} }
} }
let mut removed = BTreeSet::new(); }
let mut removed_ssl = BTreeSet::new(); let mut removed = BTreeSet::new();
binds.lan.retain(|internal, (external, ssl, _)| { binds.lan.retain(|internal, (external, _, _)| {
if host.bindings.contains_key(internal) { if host.bindings.contains_key(internal) {
true true
} else { } else {
if ssl.is_some() { removed.insert(*external);
removed_ssl.insert(*external);
} else { false
removed.insert(*external);
}
false
}
});
for external in removed {
ctrl.forward.gc(external).await?;
} }
for external in removed_ssl { });
for lan in removed {
if let Some(external) = lan.assigned_ssl_port {
ctrl.vhost.gc(None, external).await?; ctrl.vhost.gc(None, external).await?;
} }
if let Some(external) = lan.assigned_port {
ctrl.forward.gc(external).await?;
}
} }
let tor_binds: OrdMap<u16, SocketAddr> = host
.bindings struct TorHostnamePorts {
.iter() non_ssl: Option<u16>,
.flat_map(|(internal, info)| { ssl: Option<u16>,
let non_ssl = ( }
info.options.preferred_external_port, let mut tor_hostname_ports = BTreeMap::<u16, TorHostnamePorts>::new();
SocketAddr::from((self.ip, *internal)), let mut tor_binds = OrdMap::<u16, SocketAddr>::new();
for (internal, info) in &host.bindings {
tor_binds.insert(
info.options.preferred_external_port,
SocketAddr::from((self.ip, *internal)),
);
if let (Some(ssl), Some(ssl_internal)) =
(&info.options.add_ssl, info.lan.assigned_ssl_port)
{
tor_binds.insert(
ssl.preferred_external_port,
SocketAddr::from(([127, 0, 0, 1], ssl_internal)),
); );
if let (Some(ssl), Some(ssl_internal)) = tor_hostname_ports.insert(
(&info.options.add_ssl, info.assigned_lan_port) *internal,
{ TorHostnamePorts {
itertools::Either::Left( non_ssl: Some(info.options.preferred_external_port)
[ .filter(|p| *p != ssl.preferred_external_port),
( ssl: Some(ssl.preferred_external_port),
ssl.preferred_external_port, },
SocketAddr::from(([127, 0, 0, 1], ssl_internal)), );
), } else {
non_ssl, tor_hostname_ports.insert(
] *internal,
.into_iter(), TorHostnamePorts {
) non_ssl: Some(info.options.preferred_external_port),
} else { ssl: None,
itertools::Either::Right([non_ssl].into_iter()) },
} );
}) }
.collect(); }
let mut keep_tor_addrs = BTreeSet::new(); let mut keep_tor_addrs = BTreeSet::new();
for addr in match host.kind { for addr in match host.kind {
HostKind::Multi => { HostKind::Multi => {
@@ -324,13 +392,10 @@ impl NetService {
let new_tor_bind = if let Some(tor_bind) = tor_bind { let new_tor_bind = if let Some(tor_bind) = tor_bind {
tor_bind tor_bind
} else { } else {
let key = ctrl let key = peek
.db .as_private()
.peek() .as_key_store()
.await .as_onion()
.into_private()
.into_key_store()
.into_onion()
.get_key(address)?; .get_key(address)?;
let rcs = ctrl let rcs = ctrl
.tor .tor
@@ -338,6 +403,18 @@ impl NetService {
.await?; .await?;
(tor_binds.clone(), rcs) (tor_binds.clone(), rcs)
}; };
for (internal, ports) in &tor_hostname_ports {
let mut bind_hostname_info =
hostname_info.remove(internal).unwrap_or_default();
bind_hostname_info.push(HostnameInfo::Onion {
hostname: OnionHostname {
value: address.to_string(),
port: ports.non_ssl,
ssl_port: ports.ssl,
},
});
hostname_info.insert(*internal, bind_hostname_info);
}
binds.tor.insert(address.clone(), new_tor_bind); binds.tor.insert(address.clone(), new_tor_bind);
} }
} }
@@ -347,6 +424,14 @@ impl NetService {
ctrl.tor.gc(Some(addr.clone()), None).await?; ctrl.tor.gc(Some(addr.clone()), None).await?;
} }
} }
self.net_controller()?
.db
.mutate(|db| {
host_for(db, &self.id, &id, host.kind)?
.as_hostname_info_mut()
.ser(&hostname_info)
})
.await?;
Ok(()) Ok(())
} }
@@ -355,12 +440,13 @@ impl NetService {
let mut errors = ErrorCollection::new(); let mut errors = ErrorCollection::new();
if let Some(ctrl) = Weak::upgrade(&self.controller) { if let Some(ctrl) = Weak::upgrade(&self.controller) {
for (_, binds) in std::mem::take(&mut self.binds) { for (_, binds) in std::mem::take(&mut self.binds) {
for (_, (external, ssl, rc)) in binds.lan { for (_, (lan, _, rc)) in binds.lan {
drop(rc); drop(rc);
if ssl.is_some() { if let Some(external) = lan.assigned_ssl_port {
errors.handle(ctrl.vhost.gc(None, external).await); ctrl.vhost.gc(None, external).await?;
} else { }
errors.handle(ctrl.forward.gc(external).await); if let Some(external) = lan.assigned_port {
ctrl.forward.gc(external).await?;
} }
} }
for (addr, (_, rcs)) in binds.tor { for (addr, (_, rcs)) in binds.tor {
@@ -384,12 +470,12 @@ impl NetService {
self.ip self.ip
} }
pub fn get_ext_port(&self, host_id: HostId, internal_port: u16) -> Result<u16, Error> { pub fn get_ext_port(&self, host_id: HostId, internal_port: u16) -> Result<LanInfo, Error> {
let host_id_binds = self.binds.get_key_value(&host_id); let host_id_binds = self.binds.get_key_value(&host_id);
match host_id_binds { match host_id_binds {
Some((_, binds)) => { Some((_, binds)) => {
if let Some(ext_port_info) = binds.lan.get(&internal_port) { if let Some((lan, _, _)) = binds.lan.get(&internal_port) {
Ok(ext_port_info.0) Ok(*lan)
} else { } else {
Err(Error::new( Err(Error::new(
eyre!( eyre!(

View File

@@ -1,51 +1,32 @@
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use imbl_value::InternedString;
use models::{HostId, ServiceInterfaceId}; use models::{HostId, ServiceInterfaceId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
use crate::net::host::binding::BindOptions; use crate::net::host::address::HostAddress;
use crate::net::host::HostKind;
use crate::prelude::*;
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ServiceInterfaceWithHostInfo {
#[serde(flatten)]
pub service_interface: ServiceInterface,
pub host_info: ExportedHostInfo,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExportedHostInfo {
pub id: HostId,
pub kind: HostKind,
pub hostnames: Vec<ExportedHostnameInfo>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")] #[serde(rename_all_fields = "camelCase")]
#[serde(tag = "kind")] #[serde(tag = "kind")]
pub enum ExportedHostnameInfo { pub enum HostnameInfo {
Ip { Ip {
network_interface_id: String, network_interface_id: String,
public: bool, public: bool,
hostname: ExportedIpHostname, hostname: IpHostname,
}, },
Onion { Onion {
hostname: ExportedOnionHostname, hostname: OnionHostname,
}, },
} }
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ExportedOnionHostname { pub struct OnionHostname {
pub value: String, pub value: String,
pub port: Option<u16>, pub port: Option<u16>,
pub ssl_port: Option<u16>, pub ssl_port: Option<u16>,
@@ -56,7 +37,7 @@ pub struct ExportedOnionHostname {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")] #[serde(rename_all_fields = "camelCase")]
#[serde(tag = "kind")] #[serde(tag = "kind")]
pub enum ExportedIpHostname { pub enum IpHostname {
Ipv4 { Ipv4 {
value: Ipv4Addr, value: Ipv4Addr,
port: Option<u16>, port: Option<u16>,
@@ -110,6 +91,10 @@ pub enum ServiceInterfaceType {
pub struct AddressInfo { pub struct AddressInfo {
pub username: Option<String>, pub username: Option<String>,
pub host_id: HostId, pub host_id: HostId,
pub bind_options: BindOptions, pub internal_port: u16,
#[ts(type = "string | null")]
pub scheme: Option<InternedString>,
#[ts(type = "string | null")]
pub ssl_scheme: Option<InternedString>,
pub suffix: String, pub suffix: String,
} }

View File

@@ -205,7 +205,7 @@ impl VHostServer {
.into_entries()? .into_entries()?
.into_iter() .into_iter()
.flat_map(|(_, ips)| [ .flat_map(|(_, ips)| [
ips.as_ipv4().de().map(|ip| ip.map(IpAddr::V4)), ips.as_ipv4().de().map(|ip| ip.map(IpAddr::V4)),
ips.as_ipv6().de().map(|ip| ip.map(IpAddr::V6)) ips.as_ipv6().de().map(|ip| ip.map(IpAddr::V6))
]) ])
.filter_map(|a| a.transpose()) .filter_map(|a| a.transpose())

View File

@@ -138,7 +138,6 @@ impl Middleware<RegistryContext> for Auth {
if request.headers().contains_key(AUTH_SIG_HEADER) { if request.headers().contains_key(AUTH_SIG_HEADER) {
self.signer = Some( self.signer = Some(
async { async {
let request = request;
let SignatureHeader { let SignatureHeader {
commitment, commitment,
signer, signer,

View File

@@ -10,7 +10,8 @@ use persistent_container::PersistentContainer;
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor}; use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use start_stop::StartStop; use start_stop::StartStop;
use tokio::{fs::File, sync::Notify}; use tokio::fs::File;
use tokio::sync::Notify;
use ts_rs::TS; use ts_rs::TS;
use crate::context::{CliContext, RpcContext}; use crate::context::{CliContext, RpcContext};
@@ -308,7 +309,7 @@ impl Service {
.send(transition::restore::Restore { .send(transition::restore::Restore {
path: backup_source.path().to_path_buf(), path: backup_source.path().to_path_buf(),
}) })
.await?; .await??;
Ok(service) Ok(service)
} }
@@ -370,7 +371,7 @@ impl Service {
.send(transition::backup::Backup { .send(transition::backup::Backup {
path: guard.path().to_path_buf(), path: guard.path().to_path_buf(),
}) })
.await?; .await??;
Ok(()) Ok(())
} }

View File

@@ -10,7 +10,7 @@ use imbl_value::InternedString;
use models::{ProcedureName, VolumeId}; use models::{ProcedureName, VolumeId};
use rpc_toolkit::{Empty, Server, ShutdownHandle}; use rpc_toolkit::{Empty, Server, ShutdownHandle};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tokio::fs::{ File}; use tokio::fs::File;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::{oneshot, watch, Mutex, OnceCell}; use tokio::sync::{oneshot, watch, Mutex, OnceCell};
use tracing::instrument; use tracing::instrument;

View File

@@ -9,7 +9,6 @@ use std::sync::{Arc, Weak};
use clap::builder::ValueParserFactory; use clap::builder::ValueParserFactory;
use clap::Parser; use clap::Parser;
use emver::VersionRange; use emver::VersionRange;
use imbl::OrdMap;
use imbl_value::{json, InternedString}; use imbl_value::{json, InternedString};
use itertools::Itertools; use itertools::Itertools;
use models::{ use models::{
@@ -30,12 +29,9 @@ use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev; use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard; use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::net::host::address::HostAddress; use crate::net::host::address::HostAddress;
use crate::net::host::binding::BindOptions; use crate::net::host::binding::{BindOptions, LanInfo};
use crate::net::host::{self, HostKind}; use crate::net::host::{Host, HostKind};
use crate::net::service_interface::{ use crate::net::service_interface::{AddressInfo, ServiceInterface, ServiceInterfaceType};
AddressInfo, ExportedHostInfo, ExportedHostnameInfo, ServiceInterface, ServiceInterfaceType,
ServiceInterfaceWithHostInfo,
};
use crate::prelude::*; use crate::prelude::*;
use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::merkle_archive::source::http::HttpSource;
use crate::s9pk::rpc::SKIP_ENV; use crate::s9pk::rpc::SKIP_ENV;
@@ -193,7 +189,6 @@ pub fn service_effect_handler<C: Context>() -> ParentHandler<C> {
.subcommand("removeAddress", from_fn_async(remove_address).no_cli()) .subcommand("removeAddress", from_fn_async(remove_address).no_cli())
.subcommand("exportAction", from_fn_async(export_action).no_cli()) .subcommand("exportAction", from_fn_async(export_action).no_cli())
.subcommand("removeAction", from_fn_async(remove_action).no_cli()) .subcommand("removeAction", from_fn_async(remove_action).no_cli())
.subcommand("reverseProxy", from_fn_async(reverse_proxy).no_cli())
.subcommand("mount", from_fn_async(mount).no_cli()) .subcommand("mount", from_fn_async(mount).no_cli())
// TODO Callbacks // TODO Callbacks
@@ -233,8 +228,6 @@ struct ExportServiceInterfaceParams {
masked: bool, masked: bool,
address_info: AddressInfo, address_info: AddressInfo,
r#type: ServiceInterfaceType, r#type: ServiceInterfaceType,
host_kind: HostKind,
hostnames: Vec<ExportedHostnameInfo>,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)] #[ts(export)]
@@ -242,9 +235,8 @@ struct ExportServiceInterfaceParams {
struct GetPrimaryUrlParams { struct GetPrimaryUrlParams {
#[ts(type = "string | null")] #[ts(type = "string | null")]
package_id: Option<PackageId>, package_id: Option<PackageId>,
service_interface_id: String, service_interface_id: ServiceInterfaceId,
callback: Callback, callback: Callback,
host_id: HostId,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)] #[ts(export)]
@@ -276,37 +268,7 @@ struct RemoveActionParams {
#[ts(type = "string")] #[ts(type = "string")]
id: ActionId, id: ActionId,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ReverseProxyBind {
ip: Option<String>,
port: u32,
ssl: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ReverseProxyDestination {
ip: Option<String>,
port: u32,
ssl: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ReverseProxyHttp {
#[ts(type = "null | {[key: string]: string}")]
headers: Option<OrdMap<String, String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ReverseProxyParams {
bind: ReverseProxyBind,
dst: ReverseProxyDestination,
http: ReverseProxyHttp,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -367,7 +329,7 @@ async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr,
async fn get_service_port_forward( async fn get_service_port_forward(
context: EffectContext, context: EffectContext,
data: GetServicePortForwardParams, data: GetServicePortForwardParams,
) -> Result<u16, Error> { ) -> Result<LanInfo, Error> {
let internal_port = data.internal_port as u16; let internal_port = data.internal_port as u16;
let context = context.deref()?; let context = context.deref()?;
@@ -404,13 +366,10 @@ async fn export_service_interface(
masked, masked,
address_info, address_info,
r#type, r#type,
host_kind,
hostnames,
}: ExportServiceInterfaceParams, }: ExportServiceInterfaceParams,
) -> Result<(), Error> { ) -> Result<(), Error> {
let context = context.deref()?; let context = context.deref()?;
let package_id = context.id.clone(); let package_id = context.id.clone();
let host_id = address_info.host_id.clone();
let service_interface = ServiceInterface { let service_interface = ServiceInterface {
id: id.clone(), id: id.clone(),
@@ -422,15 +381,7 @@ async fn export_service_interface(
address_info, address_info,
interface_type: r#type, interface_type: r#type,
}; };
let host_info = ExportedHostInfo { let svc_interface_with_host_info = service_interface;
id: host_id,
kind: host_kind,
hostnames,
};
let svc_interface_with_host_info = ServiceInterfaceWithHostInfo {
service_interface,
host_info,
};
context context
.ctx .ctx
@@ -449,35 +400,26 @@ async fn export_service_interface(
} }
async fn get_primary_url( async fn get_primary_url(
context: EffectContext, context: EffectContext,
data: GetPrimaryUrlParams, GetPrimaryUrlParams {
) -> Result<HostAddress, Error> { package_id,
service_interface_id,
callback,
}: GetPrimaryUrlParams,
) -> Result<Option<HostAddress>, Error> {
let context = context.deref()?; let context = context.deref()?;
let package_id = context.id.clone(); let package_id = package_id.unwrap_or_else(|| context.id.clone());
let db_model = context.ctx.db.peek().await; Ok(None) // TODO
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( async fn list_service_interfaces(
context: EffectContext, context: EffectContext,
data: ListServiceInterfacesParams, ListServiceInterfacesParams {
) -> Result<BTreeMap<ServiceInterfaceId, ServiceInterfaceWithHostInfo>, Error> { package_id,
callback,
}: ListServiceInterfacesParams,
) -> Result<BTreeMap<ServiceInterfaceId, ServiceInterface>, Error> {
let context = context.deref()?; let context = context.deref()?;
let package_id = context.id.clone(); let package_id = package_id.unwrap_or_else(|| context.id.clone());
context context
.ctx .ctx
@@ -553,10 +495,8 @@ async fn remove_action(context: EffectContext, data: RemoveActionParams) -> Resu
.await?; .await?;
Ok(()) Ok(())
} }
async fn reverse_proxy(context: EffectContext, data: ReverseProxyParams) -> Result<Value, Error> {
todo!()
}
async fn mount(context: EffectContext, data: MountParams) -> Result<Value, Error> { async fn mount(context: EffectContext, data: MountParams) -> Result<Value, Error> {
// TODO
todo!() todo!()
} }
@@ -564,49 +504,42 @@ async fn mount(context: EffectContext, data: MountParams) -> Result<Value, Error
#[ts(export)] #[ts(export)]
struct Callback(#[ts(type = "() => void")] i64); struct Callback(#[ts(type = "() => void")] i64);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
enum GetHostInfoParamsKind {
Multi,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export)] #[ts(export)]
struct GetHostInfoParams { struct GetHostInfoParams {
kind: Option<GetHostInfoParamsKind>, host_id: HostId,
service_interface_id: String,
#[ts(type = "string | null")] #[ts(type = "string | null")]
package_id: Option<PackageId>, package_id: Option<PackageId>,
callback: Callback, callback: Callback,
} }
async fn get_host_info( async fn get_host_info(
ctx: EffectContext, ctx: EffectContext,
GetHostInfoParams { .. }: GetHostInfoParams, GetHostInfoParams {
) -> Result<Value, Error> { callback,
package_id,
host_id,
}: GetHostInfoParams,
) -> Result<Host, Error> {
let ctx = ctx.deref()?; let ctx = ctx.deref()?;
Ok(json!({ let db = ctx.ctx.db.peek().await;
"id": "fakeId1", let package_id = package_id.unwrap_or_else(|| ctx.id.clone());
"kind": "multi",
"hostnames": [{
"kind": "ip",
"networkInterfaceId": "fakeNetworkInterfaceId1",
"public": true,
"hostname":{
"kind": "domain",
"domain": format!("{}", ctx.id),
"subdomain": (),
"port": (),
"sslPort": ()
}
}
] db.as_public()
})) .as_package_data()
.as_idx(&package_id)
.or_not_found(&package_id)?
.as_hosts()
.as_idx(&host_id)
.or_not_found(&host_id)?
.de()
} }
async fn clear_bindings(context: EffectContext, _: Empty) -> Result<Value, Error> { async fn clear_bindings(context: EffectContext, _: Empty) -> Result<(), Error> {
todo!() let ctx = context.deref()?;
let mut svc = ctx.persistent_container.net_service.lock().await;
svc.clear_bindings().await?;
Ok(())
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
@@ -619,15 +552,13 @@ struct BindParams {
#[serde(flatten)] #[serde(flatten)]
options: BindOptions, options: BindOptions,
} }
async fn bind( async fn bind(context: EffectContext, bind_params: Value) -> Result<(), Error> {
context: EffectContext, let BindParams {
BindParams {
kind, kind,
id, id,
internal_port, internal_port,
options, options,
}: BindParams, } = from_value(bind_params)?;
) -> Result<(), Error> {
let ctx = context.deref()?; let ctx = context.deref()?;
let mut svc = ctx.persistent_container.net_service.lock().await; let mut svc = ctx.persistent_container.net_service.lock().await;
svc.bind(kind, id, internal_port, options).await svc.bind(kind, id, internal_port, options).await
@@ -639,39 +570,32 @@ async fn bind(
struct GetServiceInterfaceParams { struct GetServiceInterfaceParams {
#[ts(type = "string | null")] #[ts(type = "string | null")]
package_id: Option<PackageId>, package_id: Option<PackageId>,
service_interface_id: String, service_interface_id: ServiceInterfaceId,
callback: Callback, callback: Callback,
} }
async fn get_service_interface( async fn get_service_interface(
_: EffectContext, ctx: EffectContext,
GetServiceInterfaceParams { GetServiceInterfaceParams {
callback, callback,
package_id, package_id,
service_interface_id, service_interface_id,
}: GetServiceInterfaceParams, }: GetServiceInterfaceParams,
) -> Result<Value, Error> { ) -> Result<ServiceInterface, Error> {
// TODO @Dr_Bonez let ctx = ctx.deref()?;
Ok(json!({ let package_id = package_id.unwrap_or_else(|| ctx.id.clone());
"id": service_interface_id, let db = ctx.ctx.db.peek().await;
"name": service_interface_id,
"description": "This is a fake", let interface = db
"hasPrimary": true, .as_public()
"disabled": false, .as_package_data()
"masked": false, .as_idx(&package_id)
"addressInfo": json!({ .or_not_found(&package_id)?
"username": Value::Null, .as_service_interfaces()
"hostId": "HostId?", .as_idx(&service_interface_id)
"options": json!({ .or_not_found(&service_interface_id)?
"scheme": Value::Null, .de()?;
"preferredExternalPort": 80, Ok(interface)
"addSsl":Value::Null,
"secure": false,
"ssl": false
}),
"suffix": "http"
}),
"type": "api"
}))
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
@@ -764,6 +688,7 @@ async fn get_ssl_certificate(
host_id, host_id,
}: GetSslCertificateParams, }: GetSslCertificateParams,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
// TODO
let fake = include_str!("./fake.cert.pem"); let fake = include_str!("./fake.cert.pem");
Ok(json!([fake, fake, fake])) Ok(json!([fake, fake, fake]))
} }
@@ -785,6 +710,7 @@ async fn get_ssl_key(
algorithm, algorithm,
}: GetSslKeyParams, }: GetSslKeyParams,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
// TODO
let fake = include_str!("./fake.cert.key"); let fake = include_str!("./fake.cert.key");
Ok(json!(fake)) Ok(json!(fake))
} }

View File

@@ -59,7 +59,7 @@ import {
} from "./interfaces/setupInterfaces" } from "./interfaces/setupInterfaces"
import { successFailure } from "./trigger/successFailure" import { successFailure } from "./trigger/successFailure"
import { HealthReceipt } from "./health/HealthReceipt" import { HealthReceipt } from "./health/HealthReceipt"
import { MultiHost, Scheme, SingleHost, StaticHost } from "./interfaces/Host" import { MultiHost, Scheme } from "./interfaces/Host"
import { ServiceInterfaceBuilder } from "./interfaces/ServiceInterfaceBuilder" import { ServiceInterfaceBuilder } from "./interfaces/ServiceInterfaceBuilder"
import { GetSystemSmtp } from "./util/GetSystemSmtp" import { GetSystemSmtp } from "./util/GetSystemSmtp"
import nullIfEmpty from "./util/nullIfEmpty" import nullIfEmpty from "./util/nullIfEmpty"
@@ -178,10 +178,10 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
}, },
host: { host: {
static: (effects: Effects, id: string) => // static: (effects: Effects, id: string) =>
new StaticHost({ id, effects }), // new StaticHost({ id, effects }),
single: (effects: Effects, id: string) => // single: (effects: Effects, id: string) =>
new SingleHost({ id, effects }), // new SingleHost({ id, effects }),
multi: (effects: Effects, id: string) => new MultiHost({ id, effects }), multi: (effects: Effects, id: string) => new MultiHost({ id, effects }),
}, },
nullIfEmpty, nullIfEmpty,

View File

@@ -1,14 +1,14 @@
import { object, string } from "ts-matches" import { number, object, string } from "ts-matches"
import { Effects } from "../types" import { Effects } from "../types"
import { Origin } from "./Origin" import { Origin } from "./Origin"
import { AddSslOptions } from ".././osBindings" import { AddSslOptions, BindParams } from ".././osBindings"
import { Security } from ".././osBindings" import { Security } from ".././osBindings"
import { BindOptions } from ".././osBindings" import { BindOptions } from ".././osBindings"
import { AlpnInfo } from ".././osBindings" import { AlpnInfo } from ".././osBindings"
export { AddSslOptions, Security, BindOptions } export { AddSslOptions, Security, BindOptions }
const knownProtocols = { export const knownProtocols = {
http: { http: {
secure: null, secure: null,
defaultPort: 80, defaultPort: 80,
@@ -69,19 +69,17 @@ type NotProtocolsWithSslVariants = Exclude<
type BindOptionsByKnownProtocol = type BindOptionsByKnownProtocol =
| { | {
protocol: ProtocolsWithSslVariants protocol: ProtocolsWithSslVariants
preferredExternalPort?: number preferredExternalPort: number
scheme?: Scheme
addSsl?: Partial<AddSslOptions> addSsl?: Partial<AddSslOptions>
} }
| { | {
protocol: NotProtocolsWithSslVariants protocol: NotProtocolsWithSslVariants
preferredExternalPort?: number preferredExternalPort: number
scheme?: Scheme
addSsl?: AddSslOptions addSsl?: AddSslOptions
} }
type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions export type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions
export type HostKind = "static" | "single" | "multi" export type HostKind = BindParams["kind"]
const hasStringProtocol = object({ const hasStringProtocol = object({
protocol: string, protocol: string,
@@ -110,66 +108,62 @@ export class Host {
private async bindPortForUnknown( private async bindPortForUnknown(
internalPort: number, internalPort: number,
options: { options: {
scheme: Scheme
preferredExternalPort: number preferredExternalPort: number
addSsl: AddSslOptions | null addSsl: AddSslOptions | null
secure: { ssl: boolean } | null secure: { ssl: boolean } | null
}, },
) { ) {
await this.options.effects.bind({ const binderOptions = {
kind: this.options.kind, kind: this.options.kind,
id: this.options.id, id: this.options.id,
internalPort: internalPort, internalPort,
...options, ...options,
}) }
await this.options.effects.bind(binderOptions)
return new Origin(this, options) return new Origin(this, internalPort, null, null)
} }
private async bindPortForKnown( private async bindPortForKnown(
options: BindOptionsByKnownProtocol, options: BindOptionsByKnownProtocol,
internalPort: number, internalPort: number,
) { ) {
const scheme =
options.scheme === undefined ? options.protocol : options.scheme
const protoInfo = knownProtocols[options.protocol] const protoInfo = knownProtocols[options.protocol]
const preferredExternalPort = const preferredExternalPort =
options.preferredExternalPort || options.preferredExternalPort ||
knownProtocols[options.protocol].defaultPort knownProtocols[options.protocol].defaultPort
const addSsl = this.getAddSsl(options, protoInfo) const sslProto = this.getSslProto(options, protoInfo)
const addSsl =
sslProto && "alpn" in protoInfo
? {
// addXForwardedHeaders: null,
preferredExternalPort: knownProtocols[sslProto].defaultPort,
scheme: sslProto,
alpn: protoInfo.alpn,
...("addSsl" in options ? options.addSsl : null),
}
: null
const secure: Security | null = !protoInfo.secure ? null : { ssl: false } const secure: Security | null = !protoInfo.secure ? null : { ssl: false }
const newOptions = {
scheme,
preferredExternalPort,
addSsl,
secure,
}
await this.options.effects.bind({ await this.options.effects.bind({
kind: this.options.kind, kind: this.options.kind,
id: this.options.id, id: this.options.id,
internalPort, internalPort,
...newOptions, preferredExternalPort,
addSsl,
secure,
}) })
return new Origin(this, newOptions) return new Origin(this, internalPort, options.protocol, sslProto)
} }
private getAddSsl( private getSslProto(
options: BindOptionsByKnownProtocol, options: BindOptionsByKnownProtocol,
protoInfo: KnownProtocols[keyof KnownProtocols], protoInfo: KnownProtocols[keyof KnownProtocols],
): AddSslOptions | null { ) {
if (inObject("noAddSsl", options) && options.noAddSsl) return null if (inObject("noAddSsl", options) && options.noAddSsl) return null
if ("withSsl" in protoInfo && protoInfo.withSsl) if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
return {
// addXForwardedHeaders: null,
preferredExternalPort: knownProtocols[protoInfo.withSsl].defaultPort,
scheme: protoInfo.withSsl,
alpn: protoInfo.alpn,
...("addSsl" in options ? options.addSsl : null),
}
return null return null
} }
} }
@@ -181,17 +175,17 @@ function inObject<Key extends string>(
return key in obj return key in obj
} }
export class StaticHost extends Host { // export class StaticHost extends Host {
constructor(options: { effects: Effects; id: string }) { // constructor(options: { effects: Effects; id: string }) {
super({ ...options, kind: "static" }) // super({ ...options, kind: "static" })
} // }
} // }
export class SingleHost extends Host { // export class SingleHost extends Host {
constructor(options: { effects: Effects; id: string }) { // constructor(options: { effects: Effects; id: string }) {
super({ ...options, kind: "single" }) // super({ ...options, kind: "single" })
} // }
} // }
export class MultiHost extends Host { export class MultiHost extends Host {
constructor(options: { effects: Effects; id: string }) { constructor(options: { effects: Effects; id: string }) {

View File

@@ -6,7 +6,9 @@ import { ServiceInterfaceBuilder } from "./ServiceInterfaceBuilder"
export class Origin<T extends Host> { export class Origin<T extends Host> {
constructor( constructor(
readonly host: T, readonly host: T,
readonly options: BindOptions, readonly internalPort: number,
readonly scheme: string | null,
readonly sslScheme: string | null,
) {} ) {}
build({ username, path, search, schemeOverride }: BuildOptions): AddressInfo { build({ username, path, search, schemeOverride }: BuildOptions): AddressInfo {
@@ -20,18 +22,9 @@ export class Origin<T extends Host> {
return { return {
hostId: this.host.options.id, hostId: this.host.options.id,
bindOptions: { internalPort: this.internalPort,
...this.options, scheme: schemeOverride ? schemeOverride.noSsl : this.scheme,
scheme: schemeOverride ? schemeOverride.noSsl : this.options.scheme, sslScheme: schemeOverride ? schemeOverride.ssl : this.sslScheme,
addSsl: this.options.addSsl
? {
...this.options.addSsl,
scheme: schemeOverride
? schemeOverride.ssl
: this.options.addSsl.scheme,
}
: null,
},
suffix: `${path}${qp}`, suffix: `${path}${qp}`,
username, username,
} }

View File

@@ -6,7 +6,6 @@ import type { Version } from "./Version"
export type AddAssetParams = { export type AddAssetParams = {
version: Version version: Version
platform: string platform: string
upload: boolean
url: string url: string
signature: AnySignature signature: AnySignature
commitment: Blake3Commitment commitment: Blake3Commitment

View File

@@ -0,0 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AnySignature } from "./AnySignature"
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
export type AddPackageParams = {
url: string
commitment: MerkleArchiveCommitment
signature: AnySignature
}

View File

@@ -2,7 +2,6 @@
import type { AlpnInfo } from "./AlpnInfo" import type { AlpnInfo } from "./AlpnInfo"
export type AddSslOptions = { export type AddSslOptions = {
scheme: string | null
preferredExternalPort: number preferredExternalPort: number
alpn: AlpnInfo alpn: AlpnInfo | null
} }

View File

@@ -1,10 +1,11 @@
// 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 { BindOptions } from "./BindOptions"
import type { HostId } from "./HostId" import type { HostId } from "./HostId"
export type AddressInfo = { export type AddressInfo = {
username: string | null username: string | null
hostId: HostId hostId: HostId
bindOptions: BindOptions internalPort: number
scheme: string | null
sslScheme: string | null
suffix: string suffix: string
} }

View File

@@ -1,4 +1,5 @@
// 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 { BindOptions } from "./BindOptions" import type { BindOptions } from "./BindOptions"
import type { LanInfo } from "./LanInfo"
export type BindInfo = { options: BindOptions; assignedLanPort: number | null } export type BindInfo = { options: BindOptions; lan: LanInfo }

View File

@@ -3,7 +3,6 @@ import type { AddSslOptions } from "./AddSslOptions"
import type { Security } from "./Security" import type { Security } from "./Security"
export type BindOptions = { export type BindOptions = {
scheme: string | null
preferredExternalPort: number preferredExternalPort: number
addSsl: AddSslOptions | null addSsl: AddSslOptions | null
secure: Security | null secure: Security | null

View File

@@ -8,7 +8,6 @@ export type BindParams = {
kind: HostKind kind: HostKind
id: HostId id: HostId
internalPort: number internalPort: number
scheme: string | null
preferredExternalPort: number preferredExternalPort: number
addSsl: AddSslOptions | null addSsl: AddSslOptions | null
secure: Security | null secure: Security | null

View File

@@ -1,7 +1,5 @@
// 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 { AddressInfo } from "./AddressInfo" import type { AddressInfo } from "./AddressInfo"
import type { ExportedHostnameInfo } from "./ExportedHostnameInfo"
import type { HostKind } from "./HostKind"
import type { ServiceInterfaceId } from "./ServiceInterfaceId" import type { ServiceInterfaceId } from "./ServiceInterfaceId"
import type { ServiceInterfaceType } from "./ServiceInterfaceType" import type { ServiceInterfaceType } from "./ServiceInterfaceType"
@@ -14,6 +12,4 @@ export type ExportServiceInterfaceParams = {
masked: boolean masked: boolean
addressInfo: AddressInfo addressInfo: AddressInfo
type: ServiceInterfaceType type: ServiceInterfaceType
hostKind: HostKind
hostnames: Array<ExportedHostnameInfo>
} }

View File

@@ -1,10 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExportedHostnameInfo } from "./ExportedHostnameInfo"
import type { HostId } from "./HostId"
import type { HostKind } from "./HostKind"
export type ExportedHostInfo = {
id: HostId
kind: HostKind
hostnames: Array<ExportedHostnameInfo>
}

View File

@@ -1,12 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExportedIpHostname } from "./ExportedIpHostname"
import type { ExportedOnionHostname } from "./ExportedOnionHostname"
export type ExportedHostnameInfo =
| {
kind: "ip"
networkInterfaceId: string
public: boolean
hostname: ExportedIpHostname
}
| { kind: "onion"; hostname: ExportedOnionHostname }

View File

@@ -1,10 +1,9 @@
// 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 { Callback } from "./Callback" import type { Callback } from "./Callback"
import type { GetHostInfoParamsKind } from "./GetHostInfoParamsKind" import type { HostId } from "./HostId"
export type GetHostInfoParams = { export type GetHostInfoParams = {
kind: GetHostInfoParamsKind | null hostId: HostId
serviceInterfaceId: string
packageId: string | null packageId: string | null
callback: Callback callback: Callback
} }

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type GetHostInfoParamsKind = "multi"

View File

@@ -4,6 +4,7 @@ import type { PackageVersionInfo } from "./PackageVersionInfo"
import type { Version } from "./Version" import type { Version } from "./Version"
export type GetPackageResponse = { export type GetPackageResponse = {
categories: string[]
best: { [key: Version]: PackageVersionInfo } best: { [key: Version]: PackageVersionInfo }
otherVersions?: { [key: Version]: PackageInfoShort } otherVersions?: { [key: Version]: PackageInfoShort }
} }

View File

@@ -3,6 +3,7 @@ import type { PackageVersionInfo } from "./PackageVersionInfo"
import type { Version } from "./Version" import type { Version } from "./Version"
export type GetPackageResponseFull = { export type GetPackageResponseFull = {
categories: string[]
best: { [key: Version]: PackageVersionInfo } best: { [key: Version]: PackageVersionInfo }
otherVersions: { [key: Version]: PackageVersionInfo } otherVersions: { [key: Version]: PackageVersionInfo }
} }

View File

@@ -1,10 +1,9 @@
// 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 { Callback } from "./Callback" import type { Callback } from "./Callback"
import type { HostId } from "./HostId" import type { ServiceInterfaceId } from "./ServiceInterfaceId"
export type GetPrimaryUrlParams = { export type GetPrimaryUrlParams = {
packageId: string | null packageId: string | null
serviceInterfaceId: string serviceInterfaceId: ServiceInterfaceId
callback: Callback callback: Callback
hostId: HostId
} }

View File

@@ -1,8 +1,9 @@
// 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 { Callback } from "./Callback" import type { Callback } from "./Callback"
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
export type GetServiceInterfaceParams = { export type GetServiceInterfaceParams = {
packageId: string | null packageId: string | null
serviceInterfaceId: string serviceInterfaceId: ServiceInterfaceId
callback: Callback callback: Callback
} }

View File

@@ -3,5 +3,5 @@
export type HardwareRequirements = { export type HardwareRequirements = {
device: { [key: string]: string } device: { [key: string]: string }
ram: number | null ram: number | null
arch: Array<string> | null arch: string[] | null
} }

View File

@@ -2,10 +2,14 @@
import type { BindInfo } from "./BindInfo" import type { BindInfo } from "./BindInfo"
import type { HostAddress } from "./HostAddress" import type { HostAddress } from "./HostAddress"
import type { HostKind } from "./HostKind" import type { HostKind } from "./HostKind"
import type { HostnameInfo } from "./HostnameInfo"
export type Host = { export type Host = {
kind: HostKind kind: HostKind
bindings: { [key: number]: BindInfo } bindings: { [key: number]: BindInfo }
addresses: Array<HostAddress> addresses: Array<HostAddress>
primary: HostAddress | null /**
* COMPUTED: NetService::update
*/
hostnameInfo: { [key: number]: Array<HostnameInfo> }
} }

View File

@@ -0,0 +1,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { IpHostname } from "./IpHostname"
import type { OnionHostname } from "./OnionHostname"
export type HostnameInfo =
| {
kind: "ip"
networkInterfaceId: string
public: boolean
hostname: IpHostname
}
| { kind: "onion"; hostname: OnionHostname }

View File

@@ -2,4 +2,4 @@
import type { Host } from "./Host" import type { Host } from "./Host"
import type { HostId } from "./HostId" import type { HostId } from "./HostId"
export type HostInfo = { [key: HostId]: Host } export type Hosts = { [key: HostId]: Host }

View File

@@ -1,6 +1,6 @@
// 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.
export type ExportedIpHostname = export type IpHostname =
| { kind: "ipv4"; value: string; port: number | null; sslPort: number | null } | { kind: "ipv4"; value: string; port: number | null; sslPort: number | null }
| { kind: "ipv6"; value: string; port: number | null; sslPort: number | null } | { kind: "ipv6"; value: string; port: number | null; sslPort: number | null }
| { | {

View File

@@ -1,7 +1,6 @@
// 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.
export type ReverseProxyDestination = { export type LanInfo = {
ip: string | null assignedPort: number | null
port: number assignedSslPort: number | null
ssl: boolean
} }

View File

@@ -1,6 +1,6 @@
// 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.
export type ExportedOnionHostname = { export type OnionHostname = {
value: string value: string
port: number | null port: number | null
sslPort: number | null sslPort: number | null

View File

@@ -7,7 +7,7 @@ export type OsVersionInfo = {
headline: string headline: string
releaseNotes: string releaseNotes: string
sourceVersion: string sourceVersion: string
signers: Array<Guid> authorized: Array<Guid>
iso: { [key: string]: RegistryAsset<Blake3Commitment> } iso: { [key: string]: RegistryAsset<Blake3Commitment> }
squashfs: { [key: string]: RegistryAsset<Blake3Commitment> } squashfs: { [key: string]: RegistryAsset<Blake3Commitment> }
img: { [key: string]: RegistryAsset<Blake3Commitment> } img: { [key: string]: RegistryAsset<Blake3Commitment> }

View File

@@ -3,10 +3,10 @@ import type { ActionId } from "./ActionId"
import type { ActionMetadata } from "./ActionMetadata" import type { ActionMetadata } from "./ActionMetadata"
import type { CurrentDependencies } from "./CurrentDependencies" import type { CurrentDependencies } from "./CurrentDependencies"
import type { DataUrl } from "./DataUrl" import type { DataUrl } from "./DataUrl"
import type { HostInfo } from "./HostInfo" import type { Hosts } from "./Hosts"
import type { PackageState } from "./PackageState" import type { PackageState } from "./PackageState"
import type { ServiceInterface } from "./ServiceInterface"
import type { ServiceInterfaceId } from "./ServiceInterfaceId" import type { ServiceInterfaceId } from "./ServiceInterfaceId"
import type { ServiceInterfaceWithHostInfo } from "./ServiceInterfaceWithHostInfo"
import type { Status } from "./Status" import type { Status } from "./Status"
export type PackageDataEntry = { export type PackageDataEntry = {
@@ -18,7 +18,7 @@ export type PackageDataEntry = {
lastBackup: string | null lastBackup: string | null
currentDependencies: CurrentDependencies currentDependencies: CurrentDependencies
actions: { [key: ActionId]: ActionMetadata } actions: { [key: ActionId]: ActionMetadata }
serviceInterfaces: { [key: ServiceInterfaceId]: ServiceInterfaceWithHostInfo } serviceInterfaces: { [key: ServiceInterfaceId]: ServiceInterface }
hosts: HostInfo hosts: Hosts
storeExposedDependents: string[] storeExposedDependents: string[]
} }

View File

@@ -4,6 +4,7 @@ import type { PackageVersionInfo } from "./PackageVersionInfo"
import type { Version } from "./Version" import type { Version } from "./Version"
export type PackageInfo = { export type PackageInfo = {
signers: Array<Guid> authorized: Array<Guid>
versions: { [key: Version]: PackageVersionInfo } versions: { [key: Version]: PackageVersionInfo }
categories: string[]
} }

View File

@@ -17,7 +17,6 @@ export type PackageVersionInfo = {
upstreamRepo: string upstreamRepo: string
supportSite: string supportSite: string
marketingSite: string marketingSite: string
categories: string[]
osVersion: Version osVersion: Version
hardwareRequirements: HardwareRequirements hardwareRequirements: HardwareRequirements
sourceVersion: string | null sourceVersion: string | null

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ReverseProxyBind = { ip: string | null; port: number; ssl: boolean }

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ReverseProxyHttp = { headers: null | { [key: string]: string } }

View File

@@ -1,10 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReverseProxyBind } from "./ReverseProxyBind"
import type { ReverseProxyDestination } from "./ReverseProxyDestination"
import type { ReverseProxyHttp } from "./ReverseProxyHttp"
export type ReverseProxyParams = {
bind: ReverseProxyBind
dst: ReverseProxyDestination
http: ReverseProxyHttp
}

View File

@@ -1,17 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AddressInfo } from "./AddressInfo"
import type { ExportedHostInfo } from "./ExportedHostInfo"
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
import type { ServiceInterfaceType } from "./ServiceInterfaceType"
export type ServiceInterfaceWithHostInfo = {
hostInfo: ExportedHostInfo
id: ServiceInterfaceId
name: string
description: string
hasPrimary: boolean
disabled: boolean
masked: boolean
addressInfo: AddressInfo
type: ServiceInterfaceType
}

View File

@@ -3,6 +3,7 @@ export { ActionId } from "./ActionId"
export { ActionMetadata } from "./ActionMetadata" export { ActionMetadata } from "./ActionMetadata"
export { AddAdminParams } from "./AddAdminParams" export { AddAdminParams } from "./AddAdminParams"
export { AddAssetParams } from "./AddAssetParams" export { AddAssetParams } from "./AddAssetParams"
export { AddPackageParams } from "./AddPackageParams"
export { AddressInfo } from "./AddressInfo" export { AddressInfo } from "./AddressInfo"
export { AddSslOptions } from "./AddSslOptions" export { AddSslOptions } from "./AddSslOptions"
export { AddVersionParams } from "./AddVersionParams" export { AddVersionParams } from "./AddVersionParams"
@@ -40,15 +41,10 @@ export { Duration } from "./Duration"
export { EncryptedWire } from "./EncryptedWire" export { EncryptedWire } from "./EncryptedWire"
export { ExecuteAction } from "./ExecuteAction" export { ExecuteAction } from "./ExecuteAction"
export { ExportActionParams } from "./ExportActionParams" export { ExportActionParams } from "./ExportActionParams"
export { ExportedHostInfo } from "./ExportedHostInfo"
export { ExportedHostnameInfo } from "./ExportedHostnameInfo"
export { ExportedIpHostname } from "./ExportedIpHostname"
export { ExportedOnionHostname } from "./ExportedOnionHostname"
export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams" export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams"
export { ExposeForDependentsParams } from "./ExposeForDependentsParams" export { ExposeForDependentsParams } from "./ExposeForDependentsParams"
export { FullIndex } from "./FullIndex" export { FullIndex } from "./FullIndex"
export { FullProgress } from "./FullProgress" export { FullProgress } from "./FullProgress"
export { GetHostInfoParamsKind } from "./GetHostInfoParamsKind"
export { GetHostInfoParams } from "./GetHostInfoParams" export { GetHostInfoParams } from "./GetHostInfoParams"
export { GetOsAssetParams } from "./GetOsAssetParams" export { GetOsAssetParams } from "./GetOsAssetParams"
export { GetPackageParams } from "./GetPackageParams" export { GetPackageParams } from "./GetPackageParams"
@@ -69,14 +65,17 @@ export { HealthCheckId } from "./HealthCheckId"
export { HealthCheckResult } from "./HealthCheckResult" export { HealthCheckResult } from "./HealthCheckResult"
export { HostAddress } from "./HostAddress" export { HostAddress } from "./HostAddress"
export { HostId } from "./HostId" export { HostId } from "./HostId"
export { HostInfo } from "./HostInfo"
export { HostKind } from "./HostKind" export { HostKind } from "./HostKind"
export { HostnameInfo } from "./HostnameInfo"
export { Hosts } from "./Hosts"
export { Host } from "./Host" export { Host } from "./Host"
export { ImageId } from "./ImageId" export { ImageId } from "./ImageId"
export { InstalledState } from "./InstalledState" export { InstalledState } from "./InstalledState"
export { InstallingInfo } from "./InstallingInfo" export { InstallingInfo } from "./InstallingInfo"
export { InstallingState } from "./InstallingState" export { InstallingState } from "./InstallingState"
export { IpHostname } from "./IpHostname"
export { IpInfo } from "./IpInfo" export { IpInfo } from "./IpInfo"
export { LanInfo } from "./LanInfo"
export { ListServiceInterfacesParams } from "./ListServiceInterfacesParams" export { ListServiceInterfacesParams } from "./ListServiceInterfacesParams"
export { ListVersionSignersParams } from "./ListVersionSignersParams" export { ListVersionSignersParams } from "./ListVersionSignersParams"
export { MainStatus } from "./MainStatus" export { MainStatus } from "./MainStatus"
@@ -86,6 +85,7 @@ export { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
export { MountParams } from "./MountParams" export { MountParams } from "./MountParams"
export { MountTarget } from "./MountTarget" export { MountTarget } from "./MountTarget"
export { NamedProgress } from "./NamedProgress" export { NamedProgress } from "./NamedProgress"
export { OnionHostname } from "./OnionHostname"
export { OsIndex } from "./OsIndex" export { OsIndex } from "./OsIndex"
export { OsVersionInfo } from "./OsVersionInfo" export { OsVersionInfo } from "./OsVersionInfo"
export { PackageDataEntry } from "./PackageDataEntry" export { PackageDataEntry } from "./PackageDataEntry"
@@ -106,10 +106,6 @@ export { RemoveActionParams } from "./RemoveActionParams"
export { RemoveAddressParams } from "./RemoveAddressParams" export { RemoveAddressParams } from "./RemoveAddressParams"
export { RemoveVersionParams } from "./RemoveVersionParams" export { RemoveVersionParams } from "./RemoveVersionParams"
export { RequestCommitment } from "./RequestCommitment" export { RequestCommitment } from "./RequestCommitment"
export { ReverseProxyBind } from "./ReverseProxyBind"
export { ReverseProxyDestination } from "./ReverseProxyDestination"
export { ReverseProxyHttp } from "./ReverseProxyHttp"
export { ReverseProxyParams } from "./ReverseProxyParams"
export { Security } from "./Security" export { Security } from "./Security"
export { ServerInfo } from "./ServerInfo" export { ServerInfo } from "./ServerInfo"
export { ServerSpecs } from "./ServerSpecs" export { ServerSpecs } from "./ServerSpecs"
@@ -117,7 +113,6 @@ export { ServerStatus } from "./ServerStatus"
export { ServiceInterfaceId } from "./ServiceInterfaceId" export { ServiceInterfaceId } from "./ServiceInterfaceId"
export { ServiceInterface } from "./ServiceInterface" export { ServiceInterface } from "./ServiceInterface"
export { ServiceInterfaceType } from "./ServiceInterfaceType" export { ServiceInterfaceType } from "./ServiceInterfaceType"
export { ServiceInterfaceWithHostInfo } from "./ServiceInterfaceWithHostInfo"
export { SessionList } from "./SessionList" export { SessionList } from "./SessionList"
export { Sessions } from "./Sessions" export { Sessions } from "./Sessions"
export { Session } from "./Session" export { Session } from "./Session"

View File

@@ -8,6 +8,7 @@ describe("host", () => {
const foo = sdk.host.multi(effects, "foo") const foo = sdk.host.multi(effects, "foo")
const fooOrigin = await foo.bindPort(80, { const fooOrigin = await foo.bindPort(80, {
protocol: "http" as const, protocol: "http" as const,
preferredExternalPort: 80,
}) })
const fooInterface = new ServiceInterfaceBuilder({ const fooInterface = new ServiceInterfaceBuilder({
effects, effects,

View File

@@ -25,7 +25,6 @@ import { ListServiceInterfacesParams } from ".././osBindings"
import { RemoveAddressParams } from ".././osBindings" import { RemoveAddressParams } from ".././osBindings"
import { ExportActionParams } from ".././osBindings" import { ExportActionParams } from ".././osBindings"
import { RemoveActionParams } from ".././osBindings" import { RemoveActionParams } from ".././osBindings"
import { ReverseProxyParams } from ".././osBindings"
import { MountParams } from ".././osBindings" import { MountParams } from ".././osBindings"
function typeEquality<ExpectedType>(_a: ExpectedType) {} function typeEquality<ExpectedType>(_a: ExpectedType) {}
describe("startosTypeValidation ", () => { describe("startosTypeValidation ", () => {
@@ -66,7 +65,6 @@ describe("startosTypeValidation ", () => {
removeAddress: {} as RemoveAddressParams, removeAddress: {} as RemoveAddressParams,
exportAction: {} as ExportActionParams, exportAction: {} as ExportActionParams,
removeAction: {} as RemoveActionParams, removeAction: {} as RemoveActionParams,
reverseProxy: {} as ReverseProxyParams,
mount: {} as MountParams, mount: {} as MountParams,
checkDependencies: {} as CheckDependenciesParam, checkDependencies: {} as CheckDependenciesParam,
getDependencies: undefined, getDependencies: undefined,

View File

@@ -5,6 +5,12 @@ import {
SetHealth, SetHealth,
HealthCheckResult, HealthCheckResult,
SetMainStatus, SetMainStatus,
ServiceInterface,
Host,
ExportServiceInterfaceParams,
GetPrimaryUrlParams,
LanInfo,
BindParams,
} from "./osBindings" } from "./osBindings"
import { MainEffects, ServiceInterfaceType, Signals } from "./StartSdk" import { MainEffects, ServiceInterfaceType, Signals } from "./StartSdk"
@@ -12,7 +18,7 @@ import { InputSpec } from "./config/configTypes"
import { DependenciesReceipt } from "./config/setupConfig" import { DependenciesReceipt } from "./config/setupConfig"
import { BindOptions, Scheme } from "./interfaces/Host" import { BindOptions, Scheme } from "./interfaces/Host"
import { Daemons } from "./mainFn/Daemons" import { Daemons } from "./mainFn/Daemons"
import { PathBuilder, StorePath } from "./store/PathBuilder" import { StorePath } from "./store/PathBuilder"
import { ExposedStorePaths } from "./store/setupExposeStore" import { ExposedStorePaths } from "./store/setupExposeStore"
import { UrlString } from "./util/getServiceInterface" import { UrlString } from "./util/getServiceInterface"
export * from "./osBindings" export * from "./osBindings"
@@ -184,14 +190,6 @@ export declare const hostName: unique symbol
// asdflkjadsf.onion | 1.2.3.4 // asdflkjadsf.onion | 1.2.3.4
export type Hostname = string & { [hostName]: never } export type Hostname = string & { [hostName]: never }
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
export type AddressInfo = {
username: string | null
hostId: string
bindOptions: BindOptions
suffix: string
}
export type HostnameInfoIp = { export type HostnameInfoIp = {
kind: "ip" kind: "ip"
networkInterfaceId: string networkInterfaceId: string
@@ -219,44 +217,9 @@ export type HostnameInfoOnion = {
export type HostnameInfo = HostnameInfoIp | HostnameInfoOnion export type HostnameInfo = HostnameInfoIp | HostnameInfoOnion
export type SingleHost = {
id: string
kind: "single" | "static"
hostname: HostnameInfo | null
}
export type MultiHost = {
id: string
kind: "multi"
hostnames: HostnameInfo[]
}
export type HostInfo = SingleHost | MultiHost
export type ServiceInterfaceId = string export type ServiceInterfaceId = string
export type ServiceInterface = { export { ServiceInterface }
id: ServiceInterfaceId
/** The title of this field to be displayed */
name: string
/** Human readable description, used as tooltip usually */
description: string
/** Whether or not one address must be the primary address */
hasPrimary: boolean
/** Disabled interfaces do not serve, but they retain their metadata and addresses */
disabled: boolean
/** Whether or not to mask the URIs for this interface. Useful if the URIs contain sensitive information, such as a password, macaroon, or API key */
masked: boolean
/** URI Information */
addressInfo: AddressInfo
/** The network interface could be several types, something like ui, p2p, or network */
type: ServiceInterfaceType
}
export type ServiceInterfaceWithHostInfo = ServiceInterface & {
hostInfo: HostInfo
}
export type ExposeServicePaths<Store = never> = { export type ExposeServicePaths<Store = never> = {
/** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */ /** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */
paths: ExposedStorePaths paths: ExposedStorePaths
@@ -326,13 +289,7 @@ export type Effects = {
/** Removes all network bindings */ /** Removes all network bindings */
clearBindings(): Promise<void> clearBindings(): Promise<void>
/** Creates a host connected to the specified port with the provided options */ /** Creates a host connected to the specified port with the provided options */
bind( bind(options: BindParams): Promise<void>
options: {
kind: "static" | "single" | "multi"
id: string
internalPort: number
} & BindOptions,
): Promise<void>
/** Retrieves the current hostname(s) associated with a host id */ /** Retrieves the current hostname(s) associated with a host id */
// getHostInfo(options: { // getHostInfo(options: {
// kind: "static" | "single" // kind: "static" | "single"
@@ -341,11 +298,10 @@ export type Effects = {
// callback: () => void // callback: () => void
// }): Promise<SingleHost> // }): Promise<SingleHost>
getHostInfo(options: { getHostInfo(options: {
kind: "multi" | null hostId: string
serviceInterfaceId: string
packageId: string | null packageId: string | null
callback: () => void callback: () => void
}): Promise<MultiHost> }): Promise<Host>
// /** // /**
// * Run rsync between two volumes. This is used to backup data between volumes. // * Run rsync between two volumes. This is used to backup data between volumes.
@@ -395,14 +351,14 @@ export type Effects = {
getServicePortForward(options: { getServicePortForward(options: {
internalPort: number internalPort: number
packageId: string | null packageId: string | null
}): Promise<number> }): Promise<LanInfo>
/** Removes all network interfaces */ /** Removes all network interfaces */
clearServiceInterfaces(): Promise<void> clearServiceInterfaces(): Promise<void>
/** When we want to create a link in the front end interfaces, and example is /** When we want to create a link in the front end interfaces, and example is
* exposing a url to view a web service * exposing a url to view a web service
*/ */
exportServiceInterface(options: ServiceInterface): Promise<string> exportServiceInterface(options: ExportServiceInterfaceParams): Promise<string>
exposeForDependents(options: { paths: string[] }): Promise<void> exposeForDependents(options: { paths: string[] }): Promise<void>
@@ -422,11 +378,7 @@ export type Effects = {
* The user sets the primary url for a interface * The user sets the primary url for a interface
* @param options * @param options
*/ */
getPrimaryUrl(options: { getPrimaryUrl(options: GetPrimaryUrlParams): Promise<UrlString | null>
packageId: PackageId | null
serviceInterfaceId: ServiceInterfaceId
callback: () => void
}): Promise<UrlString | null>
/** /**
* There are times that we want to see the addresses that where exported * There are times that we want to see the addresses that where exported
@@ -437,7 +389,7 @@ export type Effects = {
listServiceInterfaces(options: { listServiceInterfaces(options: {
packageId: PackageId | null packageId: PackageId | null
callback: () => void callback: () => void
}): Promise<ServiceInterface[]> }): Promise<Record<ServiceInterfaceId, ServiceInterface>>
/** /**
*Remove an address that was exported. Used problably during main or during setConfig. *Remove an address that was exported. Used problably during main or during setConfig.
@@ -501,25 +453,6 @@ export type Effects = {
/** Exists could be useful during the runtime to know if some service is running, option dep */ /** Exists could be useful during the runtime to know if some service is running, option dep */
running(options: { packageId: PackageId }): Promise<boolean> running(options: { packageId: PackageId }): Promise<boolean>
/** Instead of creating proxies with nginx, we have a utility to create and maintain a proxy in the lifetime of this running. */
reverseProxy(options: {
bind: {
/** Optional, default is 0.0.0.0 */
ip: string | null
port: number
ssl: boolean
}
dst: {
/** Optional: default is 127.0.0.1 */
ip: string | null // optional, default 127.0.0.1
port: number
ssl: boolean
}
http: {
// optional, will do TCP layer proxy only if not present
headers: Record<string, string> | null
} | null
}): Promise<{ stop(): Promise<void> }>
restart(): void restart(): void
shutdown(): void shutdown(): void

25
sdk/lib/util/Hostname.ts Normal file
View File

@@ -0,0 +1,25 @@
import { HostnameInfo } from "../types"
export function hostnameInfoToAddress(hostInfo: HostnameInfo): string {
if (hostInfo.kind === "onion") {
return `${hostInfo.hostname.value}`
}
if (hostInfo.kind !== "ip") {
throw Error("Expecting that the kind is ip.")
}
const hostname = hostInfo.hostname
if (hostname.kind === "domain") {
return `${hostname.subdomain ? `${hostname.subdomain}.` : ""}${hostname.domain}`
}
const port = hostname.sslPort || hostname.port
const portString = port ? `:${port}` : ""
if ("ipv4" === hostname.kind || "ipv6" === hostname.kind) {
return `${hostname.value}${portString}`
}
if ("local" === hostname.kind) {
return `${hostname.value}${portString}`
}
throw Error(
"Expecting to have a valid hostname kind." + JSON.stringify(hostname),
)
}

View File

@@ -1,10 +1,15 @@
import { ServiceInterfaceType } from "../StartSdk" import { ServiceInterfaceType } from "../StartSdk"
import { knownProtocols } from "../interfaces/Host"
import { import {
AddressInfo, AddressInfo,
Effects, Effects,
HostInfo, Host,
HostAddress,
Hostname, Hostname,
HostnameInfo, HostnameInfo,
HostnameInfoIp,
HostnameInfoOnion,
IpInfo,
} from "../types" } from "../types"
export type UrlString = string export type UrlString = string
@@ -20,13 +25,13 @@ export const getHostname = (url: string): Hostname | null => {
} }
export type Filled = { export type Filled = {
hostnames: Hostname[] hostnames: HostnameInfo[]
onionHostnames: Hostname[] onionHostnames: HostnameInfo[]
localHostnames: Hostname[] localHostnames: HostnameInfo[]
ipHostnames: Hostname[] ipHostnames: HostnameInfo[]
ipv4Hostnames: Hostname[] ipv4Hostnames: HostnameInfo[]
ipv6Hostnames: Hostname[] ipv6Hostnames: HostnameInfo[]
nonIpHostnames: Hostname[] nonIpHostnames: HostnameInfo[]
urls: UrlString[] urls: UrlString[]
onionUrls: UrlString[] onionUrls: UrlString[]
@@ -50,7 +55,7 @@ export type ServiceInterfaceFilled = {
/** Whether or not to mask the URIs for this interface. Useful if the URIs contain sensitive information, such as a password, macaroon, or API key */ /** Whether or not to mask the URIs for this interface. Useful if the URIs contain sensitive information, such as a password, macaroon, or API key */
masked: boolean masked: boolean
/** Information about the host for this binding */ /** Information about the host for this binding */
hostInfo: HostInfo host: Host
/** URI information */ /** URI information */
addressInfo: FilledAddressInfo addressInfo: FilledAddressInfo
/** Indicates if we are a ui/p2p/api for the kind of interface that this is representing */ /** Indicates if we are a ui/p2p/api for the kind of interface that this is representing */
@@ -69,110 +74,103 @@ const negate =
(a: A) => (a: A) =>
!fn(a) !fn(a)
const unique = <A>(values: A[]) => Array.from(new Set(values)) const unique = <A>(values: A[]) => Array.from(new Set(values))
function stringifyHostname(info: HostnameInfo): Hostname { export const addressHostToUrl = (
let base: string { scheme, sslScheme, username, suffix }: AddressInfo,
if ("kind" in info.hostname && info.hostname.kind === "domain") { host: HostnameInfo,
base = info.hostname.subdomain ): UrlString[] => {
? `${info.hostname.subdomain}.${info.hostname.domain}` const res = []
: info.hostname.domain const fmt = (scheme: string | null, host: HostnameInfo, port: number) => {
} else { const includePort =
base = info.hostname.value scheme &&
scheme in knownProtocols &&
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
let hostname
if (host.kind === "onion") {
hostname = host.hostname.value
} else if (host.kind === "ip") {
if (host.hostname.kind === "domain") {
hostname = `${host.hostname.subdomain ? `${host.hostname.subdomain}.` : ""}${host.hostname.domain}`
} else {
hostname = host.hostname.value
}
}
return `${scheme ? `${scheme}://` : ""}${
username ? `${username}@` : ""
}${hostname}${includePort ? `:${port}` : ""}${suffix}`
} }
if (info.hostname.port && info.hostname.sslPort) { if (host.hostname.sslPort !== null) {
return `${base}:${info.hostname.port}` as Hostname res.push(fmt(sslScheme, host, host.hostname.sslPort))
} else if (info.hostname.sslPort) {
return `${base}:${info.hostname.sslPort}` as Hostname
} else if (info.hostname.port) {
return `${base}:${info.hostname.port}` as Hostname
} }
return base as Hostname if (host.hostname.port !== null) {
} res.push(fmt(scheme, host, host.hostname.port))
const addressHostToUrl = ( }
{ bindOptions, username, suffix }: AddressInfo,
host: Hostname, return res
): UrlString => {
const scheme = host.endsWith(".onion")
? bindOptions.scheme
: bindOptions.addSsl
? bindOptions.addSsl.scheme
: bindOptions.scheme // TODO: encode whether hostname transport is "secure"?
return `${scheme ? `${scheme}//` : ""}${
username ? `${username}@` : ""
}${host}${suffix}`
} }
export const filledAddress = ( export const filledAddress = (
hostInfo: HostInfo, host: Host,
addressInfo: AddressInfo, addressInfo: AddressInfo,
): FilledAddressInfo => { ): FilledAddressInfo => {
const toUrl = addressHostToUrl.bind(null, addressInfo) const toUrl = addressHostToUrl.bind(null, addressInfo)
const hostnameInfo = const hostnames = host.hostnameInfo[addressInfo.internalPort]
hostInfo.kind == "multi"
? hostInfo.hostnames
: hostInfo.hostname
? [hostInfo.hostname]
: []
return { return {
...addressInfo, ...addressInfo,
hostnames: hostnameInfo.flatMap((h) => stringifyHostname(h)), hostnames,
get onionHostnames() { get onionHostnames() {
return hostnameInfo return hostnames.filter((h) => h.kind === "onion")
.filter((h) => h.kind === "onion")
.map((h) => stringifyHostname(h))
}, },
get localHostnames() { get localHostnames() {
return hostnameInfo return hostnames.filter(
.filter((h) => h.kind === "ip" && h.hostname.kind === "local") (h) => h.kind === "ip" && h.hostname.kind === "local",
.map((h) => stringifyHostname(h)) )
}, },
get ipHostnames() { get ipHostnames() {
return hostnameInfo return hostnames.filter(
.filter( (h) =>
(h) => h.kind === "ip" &&
h.kind === "ip" && (h.hostname.kind === "ipv4" || h.hostname.kind === "ipv6"),
(h.hostname.kind === "ipv4" || h.hostname.kind === "ipv6"), )
)
.map((h) => stringifyHostname(h))
}, },
get ipv4Hostnames() { get ipv4Hostnames() {
return hostnameInfo return hostnames.filter(
.filter((h) => h.kind === "ip" && h.hostname.kind === "ipv4") (h) => h.kind === "ip" && h.hostname.kind === "ipv4",
.map((h) => stringifyHostname(h)) )
}, },
get ipv6Hostnames() { get ipv6Hostnames() {
return hostnameInfo return hostnames.filter(
.filter((h) => h.kind === "ip" && h.hostname.kind === "ipv6") (h) => h.kind === "ip" && h.hostname.kind === "ipv6",
.map((h) => stringifyHostname(h)) )
}, },
get nonIpHostnames() { get nonIpHostnames() {
return hostnameInfo return hostnames.filter(
.filter( (h) =>
(h) => h.kind === "ip" &&
h.kind === "ip" && h.hostname.kind !== "ipv4" &&
h.hostname.kind !== "ipv4" && h.hostname.kind !== "ipv6",
h.hostname.kind !== "ipv6", )
)
.map((h) => stringifyHostname(h))
}, },
get urls() { get urls() {
return this.hostnames.map(toUrl) return this.hostnames.flatMap(toUrl)
}, },
get onionUrls() { get onionUrls() {
return this.onionHostnames.map(toUrl) return this.onionHostnames.flatMap(toUrl)
}, },
get localUrls() { get localUrls() {
return this.localHostnames.map(toUrl) return this.localHostnames.flatMap(toUrl)
}, },
get ipUrls() { get ipUrls() {
return this.ipHostnames.map(toUrl) return this.ipHostnames.flatMap(toUrl)
}, },
get ipv4Urls() { get ipv4Urls() {
return this.ipv4Hostnames.map(toUrl) return this.ipv4Hostnames.flatMap(toUrl)
}, },
get ipv6Urls() { get ipv6Urls() {
return this.ipv6Hostnames.map(toUrl) return this.ipv6Hostnames.flatMap(toUrl)
}, },
get nonIpUrls() { get nonIpUrls() {
return this.nonIpHostnames.map(toUrl) return this.nonIpHostnames.flatMap(toUrl)
}, },
} }
} }
@@ -193,23 +191,25 @@ const makeInterfaceFilled = async ({
packageId, packageId,
callback, callback,
}) })
const hostInfo = await effects.getHostInfo({ const hostId = serviceInterfaceValue.addressInfo.hostId
packageId, const host = await effects.getHostInfo({
kind: null,
serviceInterfaceId: serviceInterfaceValue.id,
callback,
})
const primaryUrl = await effects.getPrimaryUrl({
serviceInterfaceId: id,
packageId, packageId,
hostId,
callback, callback,
}) })
const primaryUrl = await effects
.getPrimaryUrl({
serviceInterfaceId: id,
packageId,
callback,
})
.catch((e) => null)
const interfaceFilled: ServiceInterfaceFilled = { const interfaceFilled: ServiceInterfaceFilled = {
...serviceInterfaceValue, ...serviceInterfaceValue,
primaryUrl: primaryUrl, primaryUrl: primaryUrl,
hostInfo, host,
addressInfo: filledAddress(hostInfo, serviceInterfaceValue.addressInfo), addressInfo: filledAddress(host, serviceInterfaceValue.addressInfo),
get primaryHostname() { get primaryHostname() {
if (primaryUrl == null) return null if (primaryUrl == null) return null
return getHostname(primaryUrl) return getHostname(primaryUrl)

View File

@@ -18,41 +18,27 @@ const makeManyInterfaceFilled = async ({
packageId, packageId,
callback, callback,
}) })
const hostIdsRecord = Object.fromEntries(
await Promise.all(
Array.from(new Set(serviceInterfaceValues.map((x) => x.id))).map(
async (id) =>
[
id,
await effects.getHostInfo({
kind: null,
packageId,
serviceInterfaceId: id,
callback,
}),
] as const,
),
),
)
const serviceInterfacesFilled: ServiceInterfaceFilled[] = await Promise.all( const serviceInterfacesFilled: ServiceInterfaceFilled[] = await Promise.all(
serviceInterfaceValues.map(async (serviceInterfaceValue) => { Object.values(serviceInterfaceValues).map(async (serviceInterfaceValue) => {
const hostInfo = await effects.getHostInfo({ const hostId = serviceInterfaceValue.addressInfo.hostId
kind: null, const host = await effects.getHostInfo({
packageId,
serviceInterfaceId: serviceInterfaceValue.id,
callback,
})
const primaryUrl = await effects.getPrimaryUrl({
serviceInterfaceId: serviceInterfaceValue.id,
packageId, packageId,
hostId,
callback, callback,
}) })
const primaryUrl = await effects
.getPrimaryUrl({
serviceInterfaceId: serviceInterfaceValue.id,
packageId,
callback,
})
.catch(() => null)
return { return {
...serviceInterfaceValue, ...serviceInterfaceValue,
primaryUrl: primaryUrl, primaryUrl: primaryUrl,
hostInfo, host,
addressInfo: filledAddress(hostInfo, serviceInterfaceValue.addressInfo), addressInfo: filledAddress(host, serviceInterfaceValue.addressInfo),
get primaryHostname() { get primaryHostname() {
if (primaryUrl == null) return null if (primaryUrl == null) return null
return getHostname(primaryUrl) return getHostname(primaryUrl)

View File

@@ -10,6 +10,8 @@ import "./once"
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface" export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
export { getServiceInterfaces } from "./getServiceInterfaces" export { getServiceInterfaces } from "./getServiceInterfaces"
export { addressHostToUrl } from "./getServiceInterface"
export { hostnameInfoToAddress } from "./Hostname"
// prettier-ignore // prettier-ignore
export type FlattenIntersection<T> = export type FlattenIntersection<T> =
T extends ArrayLike<any> ? T : T extends ArrayLike<any> ? T :

View File

@@ -8,6 +8,7 @@ import { PatchDB } from 'patch-db-client'
import { QRComponent } from 'src/app/components/qr/qr.component' import { QRComponent } from 'src/app/components/qr/qr.component'
import { map } from 'rxjs' import { map } from 'rxjs'
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
import { addressHostToUrl } from '@start9labs/start-sdk/cjs/lib/util/getServiceInterface'
type MappedInterface = T.ServiceInterface & { type MappedInterface = T.ServiceInterface & {
addresses: MappedAddress[] addresses: MappedAddress[]
@@ -33,10 +34,14 @@ export class AppInterfacesPage {
.sort(iface => .sort(iface =>
iface.name.toLowerCase() > iface.name.toLowerCase() ? -1 : 1, iface.name.toLowerCase() > iface.name.toLowerCase() ? -1 : 1,
) )
.map(iface => ({ .map(iface => {
...iface, // TODO @Matt
addresses: getAddresses(iface), const host = {} as any
})) return {
...iface,
addresses: getAddresses(iface, host),
}
})
return { return {
ui: sorted.filter(val => val.type === 'ui'), ui: sorted.filter(val => val.type === 'ui'),
@@ -99,66 +104,40 @@ export class AppInterfacesItemComponent {
} }
function getAddresses( function getAddresses(
serviceInterface: T.ServiceInterfaceWithHostInfo, serviceInterface: T.ServiceInterface,
host: T.Host,
): MappedAddress[] { ): MappedAddress[] {
const host = serviceInterface.hostInfo
const addressInfo = serviceInterface.addressInfo const addressInfo = serviceInterface.addressInfo
const username = addressInfo.username ? addressInfo.username + '@' : '' const username = addressInfo.username ? addressInfo.username + '@' : ''
const suffix = addressInfo.suffix || '' const suffix = addressInfo.suffix || ''
const hostnames = host.kind === 'multi' ? host.hostnames : [] // TODO: non-multi const hostnames =
host.kind === 'multi' ? host.hostnameInfo[addressInfo.internalPort] : [] // TODO: non-multi
/* host.hostname /* host.hostname
? [host.hostname] ? [host.hostname]
: [] */ : [] */
const addresses: MappedAddress[] = [] return hostnames.flatMap(h => {
hostnames.forEach(h => {
let name = '' let name = ''
let hostname = ''
if (h.kind === 'onion') { if (h.kind === 'onion') {
name = 'Tor' name = 'Tor'
hostname = h.hostname.value
} else { } else {
const hostnameKind = h.hostname.kind const hostnameKind = h.hostname.kind
if (hostnameKind === 'domain') { if (hostnameKind === 'domain') {
name = 'Domain' name = 'Domain'
hostname = `${h.hostname.subdomain}.${h.hostname.domain}`
} else { } else {
name = name =
hostnameKind === 'local' hostnameKind === 'local'
? 'Local' ? 'Local'
: `${h.networkInterfaceId} (${hostnameKind})` : `${h.networkInterfaceId} (${hostnameKind})`
hostname = h.hostname.value
} }
} }
if (h.hostname.sslPort) { return addressHostToUrl(addressInfo, h).map(url => ({
const port = h.hostname.sslPort === 443 ? '' : `:${h.hostname.sslPort}` name,
const scheme = addressInfo.bindOptions.addSsl?.scheme url,
? `${addressInfo.bindOptions.addSsl.scheme}://` }))
: ''
addresses.push({
name: name === 'Tor' ? 'Tor (HTTPS)' : name,
url: `${scheme}${username}${hostname}${port}${suffix}`,
})
}
if (h.hostname.port) {
const port = h.hostname.port === 80 ? '' : `:${h.hostname.port}`
const scheme = addressInfo.bindOptions.scheme
? `${addressInfo.bindOptions.scheme}://`
: ''
addresses.push({
name: name === 'Tor' ? 'Tor (HTTP)' : name,
url: `${scheme}${username}${hostname}${port}${suffix}`,
})
}
}) })
return addresses
} }

View File

@@ -1422,66 +1422,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'abcdefg', hostId: 'abcdefg',
bindOptions: { internalPort: 80,
scheme: 'http', scheme: 'http',
preferredExternalPort: 80, sslScheme: 'https',
addSsl: {
// addXForwardedHeaders: false,
preferredExternalPort: 443,
scheme: 'https',
alpn: { specified: ['http/1.1', 'h2'] },
},
secure: null,
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'abcdefg',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 1234,
},
},
{
kind: 'onion',
hostname: {
value: 'bitcoin-ui-address.onion',
port: 80,
sslPort: 443,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: null,
sslPort: 1234,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 1234,
},
},
],
},
}, },
rpc: { rpc: {
id: 'rpc', id: 'rpc',
@@ -1495,66 +1440,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'bcdefgh', hostId: 'bcdefgh',
bindOptions: { internalPort: 8332,
scheme: 'http', scheme: 'http',
preferredExternalPort: 80, sslScheme: 'https',
addSsl: {
// addXForwardedHeaders: false,
preferredExternalPort: 443,
scheme: 'https',
alpn: { specified: ['http/1.1'] },
},
secure: null,
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'bcdefgh',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 2345,
},
},
{
kind: 'onion',
hostname: {
value: 'bitcoin-rpc-address.onion',
port: 80,
sslPort: 443,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: null,
sslPort: 2345,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 2345,
},
},
],
},
}, },
p2p: { p2p: {
id: 'p2p', id: 'p2p',
@@ -1568,63 +1458,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'cdefghi', hostId: 'cdefghi',
bindOptions: { internalPort: 8333,
scheme: 'bitcoin', scheme: 'bitcoin',
preferredExternalPort: 8333, sslScheme: null,
addSsl: null,
secure: {
ssl: false,
},
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'cdefghi',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 3456,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'bitcoin-p2p-address.onion',
port: 8333,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 3456,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 3456,
sslPort: null,
},
},
],
},
}, },
}, },
currentDependencies: {}, currentDependencies: {},
@@ -1660,101 +1498,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'hijklmnop', hostId: 'hijklmnop',
bindOptions: { internalPort: 80,
scheme: 'http', scheme: 'http',
preferredExternalPort: 80, sslScheme: 'https',
addSsl: {
// addXForwardedHeaders: false,
preferredExternalPort: 443,
scheme: 'https',
alpn: { specified: ['http/1.1', 'h2'] },
},
secure: {
ssl: true,
},
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'hijklmnop',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 4567,
},
},
{
kind: 'onion',
hostname: {
value: 'proxy-ui-address.onion',
port: 80,
sslPort: 443,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: null,
sslPort: 4567,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 4567,
},
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 4567,
},
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.7',
port: null,
sslPort: 4567,
},
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 4567,
},
},
],
},
}, },
}, },
currentDependencies: { currentDependencies: {
@@ -1801,63 +1549,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'qrstuv', hostId: 'qrstuv',
bindOptions: { internalPort: 10009,
scheme: 'grpc', scheme: null,
preferredExternalPort: 10009, sslScheme: 'grpc',
addSsl: null,
secure: {
ssl: true,
},
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'qrstuv',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 5678,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'lnd-grpc-address.onion',
port: 10009,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 5678,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 5678,
sslPort: null,
},
},
],
},
}, },
lndconnect: { lndconnect: {
id: 'lndconnect', id: 'lndconnect',
@@ -1871,63 +1567,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'qrstuv', hostId: 'qrstuv',
bindOptions: { internalPort: 10009,
scheme: 'lndconnect', scheme: null,
preferredExternalPort: 10009, sslScheme: 'lndconnect',
addSsl: null,
secure: {
ssl: true,
},
},
suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand', suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand',
}, },
hostInfo: {
id: 'qrstuv',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 5678,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'lnd-grpc-address.onion',
port: 10009,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 5678,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 5678,
sslPort: null,
},
},
],
},
}, },
p2p: { p2p: {
id: 'p2p', id: 'p2p',
@@ -1941,63 +1585,11 @@ export module Mock {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'rstuvw', hostId: 'rstuvw',
bindOptions: { internalPort: 9735,
scheme: null, scheme: 'lightning',
preferredExternalPort: 9735, sslScheme: null,
addSsl: null,
secure: {
ssl: true,
},
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'rstuvw',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 6789,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'lnd-p2p-address.onion',
port: 9735,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 6789,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 6789,
sslPort: null,
},
},
],
},
}, },
}, },
currentDependencies: { currentDependencies: {

View File

@@ -141,66 +141,11 @@ export const mockPatchData: DataModel = {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'abcdefg', hostId: 'abcdefg',
bindOptions: { internalPort: 80,
scheme: 'http', scheme: 'http',
preferredExternalPort: 80, sslScheme: 'https',
addSsl: {
// addXForwardedHeaders: false,
preferredExternalPort: 443,
scheme: 'https',
alpn: { specified: ['http/1.1', 'h2'] },
},
secure: null,
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'abcdefg',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 1234,
},
},
{
kind: 'onion',
hostname: {
value: 'bitcoin-ui-address.onion',
port: 80,
sslPort: 443,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: null,
sslPort: 1234,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 1234,
},
},
],
},
}, },
rpc: { rpc: {
id: 'rpc', id: 'rpc',
@@ -214,66 +159,11 @@ export const mockPatchData: DataModel = {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'bcdefgh', hostId: 'bcdefgh',
bindOptions: { internalPort: 8332,
scheme: 'http', scheme: 'http',
preferredExternalPort: 80, sslScheme: 'https',
addSsl: {
// addXForwardedHeaders: false,
preferredExternalPort: 443,
scheme: 'https',
alpn: { specified: ['http/1.1'] },
},
secure: null,
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'bcdefgh',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 2345,
},
},
{
kind: 'onion',
hostname: {
value: 'bitcoin-rpc-address.onion',
port: 80,
sslPort: 443,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: null,
sslPort: 2345,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 2345,
},
},
],
},
}, },
p2p: { p2p: {
id: 'p2p', id: 'p2p',
@@ -287,63 +177,11 @@ export const mockPatchData: DataModel = {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'cdefghi', hostId: 'cdefghi',
bindOptions: { internalPort: 8333,
scheme: 'bitcoin', scheme: 'bitcoin',
preferredExternalPort: 8333, sslScheme: null,
addSsl: null,
secure: {
ssl: false,
},
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'cdefghi',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 3456,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'bitcoin-p2p-address.onion',
port: 8333,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 3456,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 3456,
sslPort: null,
},
},
],
},
}, },
}, },
currentDependencies: {}, currentDependencies: {},
@@ -382,63 +220,11 @@ export const mockPatchData: DataModel = {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'qrstuv', hostId: 'qrstuv',
bindOptions: { internalPort: 10009,
scheme: 'grpc', scheme: null,
preferredExternalPort: 10009, sslScheme: 'grpc',
addSsl: null,
secure: {
ssl: true,
},
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'qrstuv',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 5678,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'lnd-grpc-address.onion',
port: 10009,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 5678,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 5678,
sslPort: null,
},
},
],
},
}, },
lndconnect: { lndconnect: {
id: 'lndconnect', id: 'lndconnect',
@@ -452,63 +238,11 @@ export const mockPatchData: DataModel = {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'qrstuv', hostId: 'qrstuv',
bindOptions: { internalPort: 10009,
scheme: 'lndconnect', scheme: null,
preferredExternalPort: 10009, sslScheme: 'lndconnect',
addSsl: null,
secure: {
ssl: true,
},
},
suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand', suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand',
}, },
hostInfo: {
id: 'qrstuv',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 5678,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'lnd-grpc-address.onion',
port: 10009,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 5678,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 5678,
sslPort: null,
},
},
],
},
}, },
p2p: { p2p: {
id: 'p2p', id: 'p2p',
@@ -522,61 +256,11 @@ export const mockPatchData: DataModel = {
addressInfo: { addressInfo: {
username: null, username: null,
hostId: 'rstuvw', hostId: 'rstuvw',
bindOptions: { internalPort: 8333,
scheme: null, scheme: 'bitcoin',
preferredExternalPort: 9735, sslScheme: null,
addSsl: null,
secure: { ssl: true },
},
suffix: '', suffix: '',
}, },
hostInfo: {
id: 'rstuvw',
kind: 'multi',
hostnames: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: 6789,
sslPort: null,
},
},
{
kind: 'onion',
hostname: {
value: 'lnd-p2p-address.onion',
port: 9735,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: 6789,
sslPort: null,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: 6789,
sslPort: null,
},
},
],
},
}, },
}, },
currentDependencies: { currentDependencies: {

View File

@@ -57,12 +57,14 @@ export class ConfigService {
} }
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */ /** ${scheme}://${username}@${host}:${externalPort}${suffix} */
launchableAddress(interfaces: PackageDataEntry['serviceInterfaces']): string { launchableAddress(
interfaces: PackageDataEntry['serviceInterfaces'],
host: T.Host,
): string {
const ui = Object.values(interfaces).find(i => i.type === 'ui') const ui = Object.values(interfaces).find(i => i.type === 'ui')
if (!ui) return '' if (!ui) return ''
const host = ui.hostInfo
const addressInfo = ui.addressInfo const addressInfo = ui.addressInfo
const scheme = this.isHttps() ? 'https' : 'http' const scheme = this.isHttps() ? 'https' : 'http'
const username = addressInfo.username ? addressInfo.username + '@' : '' const username = addressInfo.username ? addressInfo.username + '@' : ''
@@ -70,20 +72,25 @@ export class ConfigService {
const url = new URL(`${scheme}://${username}placeholder${suffix}`) const url = new URL(`${scheme}://${username}placeholder${suffix}`)
if (host.kind === 'multi') { if (host.kind === 'multi') {
const onionHostname = host.hostnames.find(h => h.kind === 'onion') const onionHostname = host.addresses.find(h => h.kind === 'onion')
?.hostname as T.ExportedOnionHostname ?.address as T.OnionHostname | undefined
if (!onionHostname)
throw new Error('Expecting that there is an onion hostname')
if (this.isTor() && onionHostname) { if (this.isTor() && onionHostname) {
url.hostname = onionHostname.value url.hostname = onionHostname.value
} else {
const ipHostname = host.hostnames.find(h => h.kind === 'ip')
?.hostname as T.ExportedIpHostname
if (!ipHostname) return ''
url.hostname = this.hostname
url.port = String(ipHostname.sslPort || ipHostname.port)
} }
// TODO Handle single
// else {
// const ipHostname = host.addresses.find(h => h.kind === 'ip')
// ?.hostname as T.ExportedIpHostname
// if (!ipHostname) return ''
// url.hostname = this.hostname
// url.port = String(ipHostname.sslPort || ipHostname.port)
// }
} else { } else {
throw new Error('unimplemented') throw new Error('unimplemented')
// const hostname = {} as T.ExportedHostnameInfo // host.hostname // const hostname = {} as T.ExportedHostnameInfo // host.hostname

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@angular/core'
import { WINDOW } from '@ng-web-apis/common' import { WINDOW } from '@ng-web-apis/common'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { T } from '@start9labs/start-sdk'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -13,8 +14,10 @@ export class UiLauncherService {
) {} ) {}
launch(interfaces: PackageDataEntry['serviceInterfaces']): void { launch(interfaces: PackageDataEntry['serviceInterfaces']): void {
// TODO @Matt
const host = {} as any
this.windowRef.open( this.windowRef.open(
this.config.launchableAddress(interfaces), this.config.launchableAddress(interfaces, host),
'_blank', '_blank',
'noreferrer', 'noreferrer',
) )