From 2030aafe15755f05f8989ed90c37b198bb11ac60 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Thu, 19 Jun 2025 15:04:04 -0600 Subject: [PATCH] create command Co-authored-by: Dominion5254 --- core/startos/src/db/model/public.rs | 17 ++ core/startos/src/init.rs | 7 + core/startos/src/net/mod.rs | 5 + core/startos/src/net/proxy/install-wg.sh | 251 ++++++++++++++++++ core/startos/src/net/proxy/mod.rs | 165 ++++++++++++ core/startos/src/ssh.rs | 2 +- core/startos/src/util/io.rs | 12 + sdk/base/lib/osBindings/NetworkInfo.ts | 2 + sdk/base/lib/osBindings/Proxies.ts | 4 + sdk/base/lib/osBindings/ProxyInfo.ts | 3 + sdk/base/lib/osBindings/index.ts | 2 + .../ui/src/app/services/api/mock-patch.ts | 1 + 12 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 core/startos/src/net/proxy/install-wg.sh create mode 100644 core/startos/src/net/proxy/mod.rs create mode 100644 sdk/base/lib/osBindings/Proxies.ts create mode 100644 sdk/base/lib/osBindings/ProxyInfo.ts diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index a91f1b372..740eaf0db 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -18,6 +18,7 @@ use crate::db::model::package::AllPackageData; use crate::net::acme::AcmeProvider; use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo}; use crate::net::host::Host; +use crate::net::proxy::ProxyInfo; use crate::net::utils::ipv6_is_local; use crate::net::vhost::AlpnInfo; use crate::prelude::*; @@ -91,6 +92,7 @@ impl Public { }, network_interfaces: BTreeMap::new(), acme: BTreeMap::new(), + proxy: Proxies::default(), }, status_info: ServerStatus { backup_progress: None, @@ -191,6 +193,21 @@ pub struct NetworkInfo { pub network_interfaces: BTreeMap, #[serde(default)] pub acme: BTreeMap, + #[serde(default)] + pub proxy: Proxies, +} + +#[derive(Debug, Default, Deserialize, Serialize, TS)] +pub struct Proxies(pub BTreeMap); +impl Map for Proxies { + type Key = u8; + type Value = ProxyInfo; + fn key_string(key: &Self::Key) -> Result { + Ok(InternedString::from_display(key)) + } + fn key_str(key: &Self::Key) -> Result, Error> { + Self::key_string(key) + } } #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)] diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 5261d2646..8c35262a3 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -346,6 +346,13 @@ pub async fn init( SSH_DIR, ) .await?; + crate::ssh::sync_keys( + &Hostname(peek.as_public().as_server_info().as_hostname().de()?), + &peek.as_private().as_ssh_privkey().de()?, + &Default::default(), + "/root/.ssh", + ) + .await?; load_ssh_keys.complete(); tracing::info!("Synced SSH Keys"); diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index 49d3560ef..cd68129ec 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -8,6 +8,7 @@ pub mod keys; pub mod mdns; pub mod net_controller; pub mod network_interface; +pub mod proxy; pub mod service_interface; pub mod ssl; pub mod static_server; @@ -36,4 +37,8 @@ pub fn net() -> ParentHandler { "vhost", vhost::vhost_api::().with_about("Manage ssl virtual host proxy"), ) + .subcommand( + "proxy", + proxy::proxy_api::().with_about("Manage wireguard proxies"), + ) } diff --git a/core/startos/src/net/proxy/install-wg.sh b/core/startos/src/net/proxy/install-wg.sh new file mode 100644 index 000000000..b39e1bf57 --- /dev/null +++ b/core/startos/src/net/proxy/install-wg.sh @@ -0,0 +1,251 @@ +#!/bin/bash +# +# StartOS WireGuard VPS Setup Tool +# https://github.com/start9labs/wg-vps-setup +# Derived from github.com/Nyr/wireguard-install (MIT License) + +set -e + +# Detect OS +# $os_version variables aren't always in use, but are kept here for convenience +if grep -qs "ubuntu" /etc/os-release; then + os="ubuntu" + os_version=$(grep 'VERSION_ID' /etc/os-release | cut -d '"' -f 2 | tr -d '.') +elif [[ -e /etc/debian_version ]]; then + os="debian" + os_version=$(grep -oE '[0-9]+' /etc/debian_version | head -1) +else + >&2 echo "This installer seems to be running on an unsupported distribution. +Supported distros are Ubuntu, Debian." + exit 1 +fi + +if [[ "$os" == "ubuntu" && "$os_version" -lt 2204 ]]; then + >&2 echo "Ubuntu 22.04 or higher is required to use this installer. +This version of Ubuntu is too old and unsupported." + exit 1 +fi + +if [[ "$os" == "debian" ]]; then + if grep -q '/sid' /etc/debian_version; then + >&2 echo "Debian Testing and Debian Unstable are unsupported by this installer." + exit 1 + fi + if [[ "$os_version" -lt 11 ]]; then + >&2 echo "Debian 11 or higher is required to use this installer. +This version of Debian is too old and unsupported." + exit 1 + fi +fi + +# Detect environments where $PATH does not include the sbin directories +if ! grep -q sbin <<< "$PATH"; then + >&2 echo '$PATH does not include sbin. Try using "su -" instead of "su".' + exit 1 +fi + +# Detect if BoringTun (userspace WireGuard) needs to be used +if ! systemd-detect-virt -cq; then + # Not running inside a container + use_boringtun="0" +elif grep -q '^wireguard ' /proc/modules; then + # Running inside a container, but the wireguard kernel module is available + use_boringtun="0" +else + # Running inside a container and the wireguard kernel module is not available + use_boringtun="1" +fi + +if [[ "$EUID" -ne 0 ]]; then + >&2 echo "This installer needs to be run with superuser privileges." + exit 1 +fi + +if [[ "$use_boringtun" -eq 1 ]]; then + if [ "$(uname -m)" != "x86_64" ]; then + >&2 echo "In containerized systems without the wireguard kernel module, this installer +supports only the x86_64 architecture. +The system runs on $(uname -m) and is unsupported." + exit 1 + fi + # TUN device is required to use BoringTun + if [[ ! -e /dev/net/tun ]] || ! ( exec 7<>/dev/net/tun ) 2>/dev/null; then + >&2 echo "The system does not have the TUN device available. +TUN needs to be enabled before running this installer." + exit 1 + fi +fi + +if ! which curl > /dev/null; then + >&2 echo "curl is required to use this installer." + >&2 apt-get update + >&2 apt-get install -y curl +fi + +if [ -z "$IP" ]; then + >&2 echo "IP environment variable required" + exit 1 +fi +PRIMARY_INTERFACE=$(ip -o -4 addr show | awk -v ip="$IP" '$4 ~ "^"ip"/" {print $2; exit}') + +# Use default port 51820 +port="51820" + +# Use STARTOS_HOSTNAME if set, otherwise default to "vps-clearnet" +client="${STARTOS_HOSTNAME:-vps-clearnet}" +# Sanitize the client name (although it should already be safe if it comes from STARTOS_HOSTNAME) +client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$client" | cut -c-15) + + + +# Set up automatic updates for BoringTun if the user is fine with that +# if [[ "$use_boringtun" -eq 1 ]]; then +# >&2 echo +# >&2 echo "BoringTun will be installed to set up WireGuard in the system." +# read -p "Should automatic updates be enabled for it? [Y/n]: " boringtun_updates +# until [[ "$boringtun_updates" =~ ^[yYnN]*$ ]]; do +# echo "$remove: invalid selection." +# read -p "Should automatic updates be enabled for it? [Y/n]: " boringtun_updates +# done +# [[ -z "$boringtun_updates" ]] && boringtun_updates="y" +# if [[ "$boringtun_updates" =~ ^[yY]$ ]]; then +# cron="cron" +# fi +# fi + +# Install iptables if iptables are not already available +if ! which iptables > /dev/null; then + # iptables is way less invasive than firewalld so no warning is given + firewall="iptables" +fi +# Install WireGuard +# If BoringTun is not required, set up with the WireGuard kernel module +if [[ "$use_boringtun" -eq 0 ]]; then + if [[ "$os" == "ubuntu" ]]; then + # Ubuntu + >&2 apt-get update + >&2 apt-get install -y wireguard $firewall + elif [[ "$os" == "debian" ]]; then + # Debian + >&2 apt-get update + >&2 apt-get install -y wireguard $firewall + fi +else + # Install required packages + if [[ "$os" == "ubuntu" ]]; then + # Ubuntu + >&2 apt-get update + >&2 apt-get install -y ca-certificates $cron $firewall + >&2 apt-get install -y wireguard-tools --no-install-recommends + elif [[ "$os" == "debian" ]]; then + # Debian + >&2 apt-get update + >&2 apt-get install -y ca-certificates $cron $firewall + >&2 apt-get install -y wireguard-tools --no-install-recommends + fi + # Grab the BoringTun binary curl and extract into the right place. + # Don't use this service elsewhere without permission! Contact me before you do! + curl -sL https://wg.nyr.be/1/latest/download | tar xz -C /usr/local/sbin/ --wildcards 'boringtun-*/boringtun' --strip-components 1 + # Configure wg-quick to use BoringTun + mkdir -p /etc/systemd/system/wg-quick@wg0.service.d/ + echo "[Service] +Environment=WG_QUICK_USERSPACE_IMPLEMENTATION=boringtun +Environment=WG_SUDO=1" > /etc/systemd/system/wg-quick@wg0.service.d/boringtun.conf +fi + +privkey=$(wg genkey) +pubkey=$(echo $privkey | wg pubkey) + +WG_SUBNET=${WG_SUBNET:-10.59.0.0} +WG_GATEWAY="${WG_SUBNET%.0}.1" +WG_FIRSTPEER="${WG_SUBNET%.0}.2" + +cat << EOF > /etc/wireguard/wg0.conf +[Interface] +Address = ${WG_GATEWAY}/24 +PrivateKey = $privkey +ListenPort = $port +PostUp = wg addconf wg0 /etc/wireguard/peers.conf +EOF +chmod 600 /etc/wireguard/wg0.conf + +firstpeer_privkey=$(wg genkey) +firstpeer_pubkey=$(echo $firstpeer_privkey | wg pubkey) + +cat << EOF > /etc/wireguard/peers.conf +[Peer] +Endpoint = $IP:$port +PublicKey = $pubkey +AllowedIPs = ${WG_GATEWAY}/32 +PersistentKeepalive = 25 + +[Peer] +PublicKey = ${firstpeer_pubkey} +AllowedIPs = ${WG_FIRSTPEER}/32 +PersistentKeepalive = 25 + +EOF +chmod 600 /etc/wireguard/peers.conf + +systemctl enable --now wg-quick@wg0.service + +# Enable net.ipv4.ip_forward for the system +echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard-forward.conf +echo 1 > /proc/sys/net/ipv4/ip_forward + +iptables_path=$(command -v iptables) +ip6tables_path=$(command -v ip6tables) +# nf_tables is not available as standard in OVZ kernels. So use iptables-legacy +# if we are in OVZ, with a nf_tables backend and iptables-legacy is available. +if [[ $(systemd-detect-virt) == "openvz" ]] && readlink -f "$(command -v iptables)" | grep -q "nft" && hash iptables-legacy 2>/dev/null; then + iptables_path=$(command -v iptables-legacy) + ip6tables_path=$(command -v ip6tables-legacy) +fi + + +cat << EOF > /etc/systemd/system/wg-iptables.service +[Unit] +Before=network.target + +[Service] +Type=oneshot +RemainAfterExit=yes +# IPv4 rules +ExecStart=$iptables_path -t nat -A POSTROUTING -s ${WG_SUBNET}/24 ! -d ${WG_SUBNET}/24 -j SNAT --to $ip +ExecStart=$iptables_path -I INPUT -p udp --dport $port -j ACCEPT +ExecStart=$iptables_path -I FORWARD -s ${WG_SUBNET}/24 -j ACCEPT +ExecStart=$iptables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT +ExecStart=$iptables_path -t nat -A POSTROUTING -o $PRIMARY_INTERFACE -j MASQUERADE +ExecStart=$iptables_path -t nat -A PREROUTING -i $PRIMARY_INTERFACE -p tcp ! --dport 22 -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStart=$iptables_path -t nat -A PREROUTING -i $PRIMARY_INTERFACE -p udp -m multiport ! --dports 22,$port -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStart=$iptables_path -t nat -A PREROUTING -i wg0 -s ${WG_SUBNET}/24 -d $ip -p tcp ! --dport 22 -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStart=$iptables_path -t nat -A PREROUTING -i wg0 -s ${WG_SUBNET}/24 -d $ip -p udp -m multiport ! --dports 22,$port -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStart=$iptables_path -t nat -A POSTROUTING -o wg0 -s ${WG_SUBNET}/24 -d ${WG_FIRSTPEER}/32 -p tcp ! --dport 22 -j SNAT --to-source ${WG_GATEWAY} +ExecStart=$iptables_path -t nat -A POSTROUTING -o wg0 -s ${WG_SUBNET}/24 -d ${WG_FIRSTPEER}/32 -p udp -m multiport ! --dports 22,$port -j SNAT --to-source ${WG_GATEWAY} +ExecStart=$iptables_path -A FORWARD -j ACCEPT +# IPv4 stop rules +ExecStop=$iptables_path -t nat -D POSTROUTING -s ${WG_SUBNET}/24 ! -d ${WG_SUBNET}/24 -j SNAT --to $ip +ExecStop=$iptables_path -D INPUT -p udp --dport $port -j ACCEPT +ExecStop=$iptables_path -D FORWARD -s ${WG_SUBNET}/24 -j ACCEPT +ExecStop=$iptables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT +ExecStop=$iptables_path -t nat -D POSTROUTING -o $PRIMARY_INTERFACE -j MASQUERADE +ExecStop=$iptables_path -t nat -D PREROUTING -i $PRIMARY_INTERFACE -p tcp ! --dport 22 -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStop=$iptables_path -t nat -D PREROUTING -i $PRIMARY_INTERFACE -p udp -m multiport ! --dports 22,$port -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStop=$iptables_path -t nat -D PREROUTING -i wg0 -s ${WG_SUBNET}/24 -d $ip -p tcp ! --dport 22 -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStop=$iptables_path -t nat -D PREROUTING -i wg0 -s ${WG_SUBNET}/24 -d $ip -p udp -m multiport ! --dports 22,$port -j DNAT --to-destination ${WG_FIRSTPEER} +ExecStop=$iptables_path -t nat -D POSTROUTING -o wg0 -s ${WG_SUBNET}/24 -d ${WG_FIRSTPEER}/32 -p tcp ! --dport 22 -j SNAT --to-source ${WG_GATEWAY} +ExecStop=$iptables_path -t nat -D POSTROUTING -o wg0 -s ${WG_SUBNET}/24 -d ${WG_FIRSTPEER}/32 -p udp -m multiport ! --dports 22,$port -j SNAT --to-source ${WG_GATEWAY} +ExecStop=$iptables_path -D FORWARD -j ACCEPT + +[Install] +WantedBy=multi-user.target +EOF + +cat << EOF +[Interface] +Address = ${WG_FIRSTPEER}/24 +PrivateKey = $firstpeer_privkey + +EOF +cat /etc/wireguard/peers.conf + diff --git a/core/startos/src/net/proxy/mod.rs b/core/startos/src/net/proxy/mod.rs new file mode 100644 index 000000000..2fa6ca850 --- /dev/null +++ b/core/startos/src/net/proxy/mod.rs @@ -0,0 +1,165 @@ +use std::io::Cursor; +use std::net::IpAddr; + +use clap::Parser; +use const_format::formatcp; +use id_pool::IdPool; +use imbl_value::InternedString; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use tokio::process::Command; +use ts_rs::TS; + +use crate::context::{CliContext, RpcContext}; +use crate::prelude::*; +use crate::util::io::{write_file, TmpDir}; +use crate::util::serde::HandlerExtSerde; +use crate::util::Invoke; + +const WIREGUARD_INSTALL_URL: &str = "https://raw.githubusercontent.com/start9labs/wireguard-vps-proxy-setup/master/wireguard-install.sh"; + +pub fn proxy_api() -> ParentHandler { + ParentHandler::new().subcommand( + "create", + from_fn_async(create_proxy) + .no_display() + .with_about("Create a new proxy") + .with_call_remote::(), + ) + // .subcommand( + // "edit", + // from_fn_async(edit_proxy) + // .no_display() + // .with_about("Change configuration of a proxy") + // .with_call_remote::(), + // ) + // .subcommand( + // "list", + // from_fn_async(list_proxies) + // .with_display_serializable() + // .with_about("List proxies") + // .with_call_remote::(), + // ) + // .subcommand( + // "remove", + // from_fn_async(remove_proxy) + // .no_display() + // .with_about("Remove an existing proxy") + // .with_call_remote::(), + // ) + // .subcommand( + // "add-client", + // from_fn_async(add_client) + // .no_display() + // .with_about("Add VPN client") + // .with_call_remote::(), + // ) + // .subcommand( + // "remove-client", + // from_fn_async(remove_client) + // .no_display() + // .with_about("Remove VPN client") + // .with_call_remote::(), + // ) + // .subcommand( + // "list-clients", + // from_fn_async(list_clients) + // .no_display() + // .with_about("List VPN clients") + // .with_call_remote::(), + // ) + // .subcommand( + // "set-outbound", + // from_fn_async(set_outbound) + // .no_display() + // .with_about("Set proxy to use for outbound connections") + // .with_call_remote::(), + // ) + // .subcommand( + // "unset-outbound", + // from_fn_async(unset_outbound) + // .no_display() + // .with_about("Disable usage of a proxy for outbound connections") + // .with_call_remote::(), + // ) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct ProxyInfo { + #[arg(long, short)] + pub ip: IpAddr, + #[arg(long, short)] + pub public: bool, +} + +pub async fn create_proxy( + ctx: RpcContext, + proxy @ ProxyInfo { ip, public }: ProxyInfo, +) -> Result<(), Error> { + // TODO: install bash + let id = ctx + .db + .peek() + .await + .as_public() + .as_server_info() + .as_network() + .as_proxy() + .keys()? + .last() + .map_or(Some(0), |k| k.checked_add(1)) + .ok_or_else(|| { + Error::new( + eyre!("proxy id cannot be greater than 255"), + ErrorKind::Unknown, // TODO + ) + })?; + let conf = Command::new("ssh") + .arg(format!("root@{ip}")) + .arg("-oStrictHostKeyChecking=accept-new") + .arg(format!("IP={ip}")) + .arg(format!("WG_SUBNET=10.59.{id}.0")) + .arg("bash") + .input(Some(&mut Cursor::new(include_bytes!("./install-wg.sh")))) + .invoke(ErrorKind::Unknown) // TODO + .await?; + ctx.db + .mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_network_mut() + .as_proxy_mut() + .insert(&id, &proxy) + }) + .await + .result?; + let dir = TmpDir::new().await?; + let iface = InternedString::from_display(&lazy_format!("wg-{id}")); + let conf_path = dir.join(format!("{iface}.conf")); + write_file(&conf_path, &conf).await?; + Command::new("nmcli") + .arg("connection") + .arg("import") + .arg("type") + .arg("wireguard") + .arg("file") + .arg(conf_path) + .invoke(ErrorKind::Network) + .await?; + dir.delete().await?; + + ctx.net_controller + .net_iface + .subscribe() + .wait_for(|ifaces| ifaces.contains_key(&iface)) + .await; + + ctx.net_controller + .net_iface + .set_inbound(&iface, Some(public)) + .await?; + + Ok(()) +} diff --git a/core/startos/src/ssh.rs b/core/startos/src/ssh.rs index dc2f9440a..406c70ac1 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -21,7 +21,7 @@ use crate::util::Invoke; pub const SSH_DIR: &str = "/home/start9/.ssh"; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct SshKeys(BTreeMap>); impl SshKeys { pub fn new() -> Self { diff --git a/core/startos/src/util/io.rs b/core/startos/src/util/io.rs index dc4c1eebd..05f09c31a 100644 --- a/core/startos/src/util/io.rs +++ b/core/startos/src/util/io.rs @@ -1008,6 +1008,18 @@ pub async fn rename(src: impl AsRef, dst: impl AsRef) -> Result<(), .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("mv {src:?} -> {dst:?}"))) } +pub async fn write_file(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<(), Error> { + let path = path.as_ref(); + let mut file = create_file(path).await?; + file.write_all(contents.as_ref()) + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("w {path:?}")))?; + file.sync_all() + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("fsync {path:?}")))?; + Ok(()) +} + fn poll_flush_prefix( mut writer: Pin<&mut W>, cx: &mut std::task::Context<'_>, diff --git a/sdk/base/lib/osBindings/NetworkInfo.ts b/sdk/base/lib/osBindings/NetworkInfo.ts index 1933332e4..fef5238cc 100644 --- a/sdk/base/lib/osBindings/NetworkInfo.ts +++ b/sdk/base/lib/osBindings/NetworkInfo.ts @@ -3,6 +3,7 @@ import type { AcmeProvider } from "./AcmeProvider" import type { AcmeSettings } from "./AcmeSettings" import type { Host } from "./Host" import type { NetworkInterfaceInfo } from "./NetworkInterfaceInfo" +import type { Proxies } from "./Proxies" import type { WifiInfo } from "./WifiInfo" export type NetworkInfo = { @@ -10,4 +11,5 @@ export type NetworkInfo = { host: Host networkInterfaces: { [key: string]: NetworkInterfaceInfo } acme: { [key: AcmeProvider]: AcmeSettings } + proxy: Proxies } diff --git a/sdk/base/lib/osBindings/Proxies.ts b/sdk/base/lib/osBindings/Proxies.ts new file mode 100644 index 000000000..c94059d24 --- /dev/null +++ b/sdk/base/lib/osBindings/Proxies.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ProxyInfo } from "./ProxyInfo" + +export type Proxies = { [key: number]: ProxyInfo } diff --git a/sdk/base/lib/osBindings/ProxyInfo.ts b/sdk/base/lib/osBindings/ProxyInfo.ts new file mode 100644 index 000000000..87cc10f58 --- /dev/null +++ b/sdk/base/lib/osBindings/ProxyInfo.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ProxyInfo = { ip: string; public: boolean } diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index e6e3a7bff..a1d4a4c09 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -158,6 +158,8 @@ export { Percentage } from "./Percentage" export { ProcedureId } from "./ProcedureId" export { Progress } from "./Progress" export { ProgressUnits } from "./ProgressUnits" +export { Proxies } from "./Proxies" +export { ProxyInfo } from "./ProxyInfo" export { Public } from "./Public" export { RecoverySource } from "./RecoverySource" export { RegistryAsset } from "./RegistryAsset" diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 2eb9a5c46..95c4eea8f 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -33,6 +33,7 @@ export const mockPatchData: DataModel = { contact: ['mailto:support@start9.com'], }, }, + proxy: {}, host: { bindings: { 80: {