diff --git a/core/src/net/host/address.rs b/core/src/net/host/address.rs index d99ee1efc..8cb6da743 100644 --- a/core/src/net/host/address.rs +++ b/core/src/net/host/address.rs @@ -161,6 +161,7 @@ pub fn address_api() } #[derive(Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] #[ts(export)] pub struct AddPublicDomainParams { #[arg(help = "help.arg.fqdn")] @@ -169,6 +170,8 @@ pub struct AddPublicDomainParams { pub acme: Option, #[arg(help = "help.arg.gateway-id")] pub gateway: GatewayId, + #[arg(help = "help.arg.internal-port")] + pub internal_port: u16, } #[derive(Debug, Clone, Deserialize, Serialize, TS)] @@ -177,7 +180,7 @@ pub struct AddPublicDomainParams { pub struct AddPublicDomainRes { #[ts(type = "string | null")] pub dns: Option, - pub port: Vec, + pub port: CheckPortRes, } pub async fn add_public_domain( @@ -186,10 +189,11 @@ pub async fn add_public_domain( fqdn, acme, gateway, + internal_port, }: AddPublicDomainParams, inheritance: Kind::Inheritance, ) -> Result { - let ports = ctx + let ext_port = ctx .db .mutate(|db| { if let Some(acme) = &acme { @@ -224,14 +228,46 @@ pub async fn add_public_domain( let available_ports = db.as_private().as_available_ports().de()?; let host = Kind::host_for(&inheritance, db)?; host.update_addresses(&hostname, &gateways, &available_ports)?; + + // Find the external port for the target binding let bindings = host.as_bindings().de()?; - let ports: BTreeSet = bindings - .values() - .flat_map(|b| &b.addresses.available) - .filter(|a| a.public && a.hostname == fqdn) - .filter_map(|a| a.port) - .collect(); - Ok(ports) + let target_bind = bindings + .get(&internal_port) + .ok_or_else(|| Error::new(eyre!("binding not found for internal port {internal_port}"), ErrorKind::NotFound))?; + let ext_port = target_bind + .addresses + .available + .iter() + .find(|a| a.public && a.hostname == fqdn) + .and_then(|a| a.port) + .ok_or_else(|| Error::new(eyre!("no public address found for {fqdn} on port {internal_port}"), ErrorKind::NotFound))?; + + // Disable the domain on all other bindings + host.as_bindings_mut().mutate(|b| { + for (&port, bind) in b.iter_mut() { + if port == internal_port { + continue; + } + let has_addr = bind + .addresses + .available + .iter() + .any(|a| a.public && a.hostname == fqdn); + if has_addr { + let other_ext = bind + .addresses + .available + .iter() + .find(|a| a.public && a.hostname == fqdn) + .and_then(|a| a.port) + .unwrap_or(ext_port); + bind.addresses.disabled.insert((fqdn.clone(), other_ext)); + } + } + Ok(()) + })?; + + Ok(ext_port) }) .await .result?; @@ -239,7 +275,7 @@ pub async fn add_public_domain( let ctx2 = ctx.clone(); let fqdn2 = fqdn.clone(); - let (dns_result, port_results) = tokio::join!( + let (dns_result, port_result) = tokio::join!( async { tokio::task::spawn_blocking(move || { crate::net::dns::query_dns(ctx2, crate::net::dns::QueryDnsParams { fqdn: fqdn2 }) @@ -247,20 +283,18 @@ pub async fn add_public_domain( .await .with_kind(ErrorKind::Unknown)? }, - futures::future::join_all(ports.into_iter().map(|port| { - check_port( - ctx.clone(), - CheckPortParams { - port, - gateway: gateway.clone(), - }, - ) - })) + check_port( + ctx.clone(), + CheckPortParams { + port: ext_port, + gateway: gateway.clone(), + }, + ) ); Ok(AddPublicDomainRes { dns: dns_result?, - port: port_results.into_iter().collect::, _>>()?, + port: port_result?, }) } diff --git a/sdk/base/lib/osBindings/AddPublicDomainParams.ts b/sdk/base/lib/osBindings/AddPublicDomainParams.ts index 3d7ddbdc1..552121859 100644 --- a/sdk/base/lib/osBindings/AddPublicDomainParams.ts +++ b/sdk/base/lib/osBindings/AddPublicDomainParams.ts @@ -6,4 +6,5 @@ export type AddPublicDomainParams = { fqdn: string acme: AcmeProvider | null gateway: GatewayId + internalPort: number } diff --git a/sdk/base/lib/osBindings/AddPublicDomainRes.ts b/sdk/base/lib/osBindings/AddPublicDomainRes.ts index 05f99b617..0d02fe934 100644 --- a/sdk/base/lib/osBindings/AddPublicDomainRes.ts +++ b/sdk/base/lib/osBindings/AddPublicDomainRes.ts @@ -1,7 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { CheckPortRes } from './CheckPortRes' -export type AddPublicDomainRes = { - dns: string | null - port: Array -} +export type AddPublicDomainRes = { dns: string | null; port: CheckPortRes } diff --git a/web/projects/shared/src/i18n/dictionaries/de.ts b/web/projects/shared/src/i18n/dictionaries/de.ts index 13c47c42d..e6e4e8b24 100644 --- a/web/projects/shared/src/i18n/dictionaries/de.ts +++ b/web/projects/shared/src/i18n/dictionaries/de.ts @@ -678,7 +678,7 @@ export default { 741: 'In Ihrem Domain-Registrar für', 742: 'diesen DNS-Eintrag erstellen', 743: 'In Ihrem Gateway', - 744: 'diese Portweiterleitungsregeln erstellen', + 744: 'diese Portweiterleitungsregel erstellen', 745: 'Externer Port', 747: 'Interner Port', 749: 'DNS-Server-Konfiguration', diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts index b5571a5b8..dd84c6ccb 100644 --- a/web/projects/shared/src/i18n/dictionaries/en.ts +++ b/web/projects/shared/src/i18n/dictionaries/en.ts @@ -678,7 +678,7 @@ export const ENGLISH: Record = { 'In your domain registrar for': 741, // partial sentence, followed by a domain name 'create this DNS record': 742, 'In your gateway': 743, // partial sentence, followed by a gateway name - 'create these port forwarding rules': 744, + 'create this port forwarding rule': 744, 'External Port': 745, 'Internal Port': 747, 'DNS Server Config': 749, diff --git a/web/projects/shared/src/i18n/dictionaries/es.ts b/web/projects/shared/src/i18n/dictionaries/es.ts index ca7fa0343..bfacf8466 100644 --- a/web/projects/shared/src/i18n/dictionaries/es.ts +++ b/web/projects/shared/src/i18n/dictionaries/es.ts @@ -678,7 +678,7 @@ export default { 741: 'En su registrador de dominios para', 742: 'cree este registro DNS', 743: 'En su puerta de enlace', - 744: 'cree estas reglas de reenvío de puertos', + 744: 'cree esta regla de reenvío de puertos', 745: 'Puerto externo', 747: 'Puerto interno', 749: 'Configuración del servidor DNS', diff --git a/web/projects/shared/src/i18n/dictionaries/fr.ts b/web/projects/shared/src/i18n/dictionaries/fr.ts index 46d18b709..590fefef3 100644 --- a/web/projects/shared/src/i18n/dictionaries/fr.ts +++ b/web/projects/shared/src/i18n/dictionaries/fr.ts @@ -678,7 +678,7 @@ export default { 741: 'Dans votre registraire de domaine pour', 742: 'créez cet enregistrement DNS', 743: 'Dans votre passerelle', - 744: 'créez ces règles de redirection de port', + 744: 'créez cette règle de redirection de port', 745: 'Port externe', 747: 'Port interne', 749: 'Configuration du serveur DNS', diff --git a/web/projects/shared/src/i18n/dictionaries/pl.ts b/web/projects/shared/src/i18n/dictionaries/pl.ts index 2a4854520..e97287b0f 100644 --- a/web/projects/shared/src/i18n/dictionaries/pl.ts +++ b/web/projects/shared/src/i18n/dictionaries/pl.ts @@ -678,7 +678,7 @@ export default { 741: 'W rejestratorze domeny dla', 742: 'utwórz ten rekord DNS', 743: 'W bramie', - 744: 'utwórz te reguły przekierowania portów', + 744: 'utwórz tę regułę przekierowania portów', 745: 'Port zewnętrzny', 747: 'Port wewnętrzny', 749: 'Konfiguracja serwera DNS', diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts index 3cabcc0aa..968cbe8ae 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts @@ -269,6 +269,7 @@ export class InterfaceAddressesComponent { fqdn, gateway: gatewayId, acme: !authority || authority === 'local' ? null : authority, + internalPort: iface?.addressInfo.internalPort || 80, } try { diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts index 6aeefff71..d5715cc33 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts @@ -15,6 +15,7 @@ import { } from '@taiga-ui/kit' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PortCheckIconComponent } from 'src/app/routes/portal/components/port-check-icon.component' +import { PortCheckWarningsComponent } from 'src/app/routes/portal/components/port-check-warnings.component' import { TableComponent } from 'src/app/routes/portal/components/table.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { T } from '@start9labs/start-sdk' @@ -28,11 +29,8 @@ export type DnsGateway = T.NetworkInterfaceInfo & { export type DomainValidationData = { fqdn: string gateway: DnsGateway - ports: number[] - initialResults?: { - dnsPass: boolean - portResults: (T.CheckPortRes | null)[] - } + port: number + initialResults?: { dnsPass: boolean; portResult: T.CheckPortRes | null } } @Component({ @@ -94,50 +92,32 @@ export type DomainValidationData = {

{{ 'Port Forwarding' | i18n }}

{{ 'In your gateway' | i18n }} "{{ gatewayName }}", - {{ 'create these port forwarding rules' | i18n }} + {{ 'create this port forwarding rule' | i18n }}

+ @let portRes = portResult(); + - @for (port of context.data.ports; track port; let i = $index) { - - - - - - - } + + + + + +
- - {{ port }}{{ port }} - -
+ + {{ context.data.port }}{{ context.data.port }} + +
- @if (anyNotRunning()) { -

- {{ - 'Port status cannot be determined while service is not running' - | i18n - }} -

- } - @if (anyNoHairpinning()) { -

- {{ - 'This address will not work from your local network due to a router hairpinning limitation' - | i18n - }} -

- } + @if (!isManualMode) {