mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
feat: refactor NetService to watch DB and reconcile network state
- NetService sync task now uses PatchDB DbWatch instead of being called directly after DB mutations - Read gateways from DB instead of network interface context when updating host addresses - gateway sync updates all host addresses in the DB - Add Watch<u64> channel for callers to wait on sync completion - Fix ts-rs codegen bug with #[ts(skip)] on flattened Plugin field - Update SDK getServiceInterface.ts for new HostnameInfo shape - Remove unnecessary HTTPS redirect in static_server.rs - Fix tunnel/api.rs to filter for WAN IPv4 address
This commit is contained in:
@@ -7,4 +7,4 @@ export type HostnameMetadata =
|
||||
| { kind: 'ipv6'; gateway: GatewayId; scopeId: number }
|
||||
| { kind: 'private-domain'; gateways: Array<GatewayId> }
|
||||
| { kind: 'public-domain'; gateway: GatewayId }
|
||||
| ({ kind: 'plugin'; package: PackageId } & { [key in string]?: unknown })
|
||||
| { kind: 'plugin'; package: PackageId }
|
||||
|
||||
@@ -49,19 +49,20 @@ type VisibilityFilter<V extends 'public' | 'private'> = V extends 'public'
|
||||
: never
|
||||
type KindFilter<K extends FilterKinds> = K extends 'mdns'
|
||||
?
|
||||
| (HostnameInfo & { hostname: { kind: 'local' } })
|
||||
| (HostnameInfo & { metadata: { kind: 'private-domain' } })
|
||||
| KindFilter<Exclude<K, 'mdns'>>
|
||||
: K extends 'domain'
|
||||
?
|
||||
| (HostnameInfo & { hostname: { kind: 'domain' } })
|
||||
| (HostnameInfo & { metadata: { kind: 'private-domain' } })
|
||||
| (HostnameInfo & { metadata: { kind: 'public-domain' } })
|
||||
| KindFilter<Exclude<K, 'domain'>>
|
||||
: K extends 'ipv4'
|
||||
?
|
||||
| (HostnameInfo & { hostname: { kind: 'ipv4' } })
|
||||
| (HostnameInfo & { metadata: { kind: 'ipv4' } })
|
||||
| KindFilter<Exclude<K, 'ipv4'>>
|
||||
: K extends 'ipv6'
|
||||
?
|
||||
| (HostnameInfo & { hostname: { kind: 'ipv6' } })
|
||||
| (HostnameInfo & { metadata: { kind: 'ipv6' } })
|
||||
| KindFilter<Exclude<K, 'ipv6'>>
|
||||
: K extends 'ip'
|
||||
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
|
||||
@@ -108,10 +109,7 @@ type FormatReturnTy<
|
||||
export type Filled<F extends Filter = {}> = {
|
||||
hostnames: HostnameInfo[]
|
||||
|
||||
toUrls: (h: HostnameInfo) => {
|
||||
url: UrlString | null
|
||||
sslUrl: UrlString | null
|
||||
}
|
||||
toUrl: (h: HostnameInfo) => UrlString
|
||||
|
||||
format: <Format extends Formats = 'urlstring'>(
|
||||
format?: Format,
|
||||
@@ -152,37 +150,29 @@ const unique = <A>(values: A[]) => Array.from(new Set(values))
|
||||
export const addressHostToUrl = (
|
||||
{ scheme, sslScheme, username, suffix }: AddressInfo,
|
||||
hostname: HostnameInfo,
|
||||
): { url: UrlString | null; sslUrl: UrlString | null } => {
|
||||
const res = []
|
||||
const fmt = (scheme: string | null, host: HostnameInfo, port: number) => {
|
||||
): UrlString => {
|
||||
const effectiveScheme = hostname.ssl ? sslScheme : scheme
|
||||
let host: string
|
||||
if (hostname.metadata.kind === 'ipv6') {
|
||||
host = IPV6_LINK_LOCAL.contains(hostname.host)
|
||||
? `[${hostname.host}%${hostname.metadata.scopeId}]`
|
||||
: `[${hostname.host}]`
|
||||
} else {
|
||||
host = hostname.host
|
||||
}
|
||||
let portStr = ''
|
||||
if (hostname.port !== null) {
|
||||
const excludePort =
|
||||
scheme &&
|
||||
scheme in knownProtocols &&
|
||||
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
|
||||
let hostname
|
||||
if (host.hostname.kind === 'domain') {
|
||||
hostname = host.hostname.value
|
||||
} else if (host.hostname.kind === 'ipv6') {
|
||||
hostname = IPV6_LINK_LOCAL.contains(host.hostname.value)
|
||||
? `[${host.hostname.value}%${host.hostname.scopeId}]`
|
||||
: `[${host.hostname.value}]`
|
||||
} else {
|
||||
hostname = host.hostname.value
|
||||
}
|
||||
return `${scheme ? `${scheme}://` : ''}${
|
||||
username ? `${username}@` : ''
|
||||
}${hostname}${excludePort ? '' : `:${port}`}${suffix}`
|
||||
effectiveScheme &&
|
||||
effectiveScheme in knownProtocols &&
|
||||
hostname.port ===
|
||||
knownProtocols[effectiveScheme as keyof typeof knownProtocols]
|
||||
.defaultPort
|
||||
if (!excludePort) portStr = `:${hostname.port}`
|
||||
}
|
||||
let url = null
|
||||
if (hostname.hostname.port !== null) {
|
||||
url = fmt(scheme, hostname, hostname.hostname.port)
|
||||
}
|
||||
let sslUrl = null
|
||||
if (hostname.hostname.sslPort !== null) {
|
||||
sslUrl = fmt(sslScheme, hostname, hostname.hostname.sslPort)
|
||||
}
|
||||
|
||||
return { url, sslUrl }
|
||||
return `${effectiveScheme ? `${effectiveScheme}://` : ''}${
|
||||
username ? `${username}@` : ''
|
||||
}${host}${portStr}${suffix}`
|
||||
}
|
||||
|
||||
function filterRec(
|
||||
@@ -209,15 +199,19 @@ function filterRec(
|
||||
hostnames = hostnames.filter(
|
||||
(h) =>
|
||||
invert !==
|
||||
((kind.has('mdns') && h.hostname.kind === 'local') ||
|
||||
(kind.has('domain') && h.hostname.kind === 'domain') ||
|
||||
(kind.has('ipv4') && h.hostname.kind === 'ipv4') ||
|
||||
(kind.has('ipv6') && h.hostname.kind === 'ipv6') ||
|
||||
((kind.has('mdns') &&
|
||||
h.metadata.kind === 'private-domain' &&
|
||||
h.host.endsWith('.local')) ||
|
||||
(kind.has('domain') &&
|
||||
(h.metadata.kind === 'private-domain' ||
|
||||
h.metadata.kind === 'public-domain')) ||
|
||||
(kind.has('ipv4') && h.metadata.kind === 'ipv4') ||
|
||||
(kind.has('ipv6') && h.metadata.kind === 'ipv6') ||
|
||||
(kind.has('localhost') &&
|
||||
['localhost', '127.0.0.1', '::1'].includes(h.hostname.value)) ||
|
||||
['localhost', '127.0.0.1', '::1'].includes(h.host)) ||
|
||||
(kind.has('link-local') &&
|
||||
h.hostname.kind === 'ipv6' &&
|
||||
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))),
|
||||
h.metadata.kind === 'ipv6' &&
|
||||
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.host)))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -226,15 +220,26 @@ function filterRec(
|
||||
return hostnames
|
||||
}
|
||||
|
||||
function isDefaultEnabled(h: HostnameInfo): boolean {
|
||||
return !(h.public && (h.hostname.kind === 'ipv4' || h.hostname.kind === 'ipv6'))
|
||||
function isPublicIp(h: HostnameInfo): boolean {
|
||||
return h.public && (h.metadata.kind === 'ipv4' || h.metadata.kind === 'ipv6')
|
||||
}
|
||||
|
||||
function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
|
||||
return addr.possible.filter((h) => {
|
||||
if (addr.enabled.some((e) => deepEqual(e, h))) return true
|
||||
if (addr.disabled.some((d) => deepEqual(d, h))) return false
|
||||
return isDefaultEnabled(h)
|
||||
return addr.available.filter((h) => {
|
||||
if (isPublicIp(h)) {
|
||||
// Public IPs: disabled by default, explicitly enabled via SocketAddr string
|
||||
if (h.port === null) return true
|
||||
const sa =
|
||||
h.metadata.kind === 'ipv6'
|
||||
? `[${h.host}]:${h.port}`
|
||||
: `${h.host}:${h.port}`
|
||||
return addr.enabled.includes(sa)
|
||||
} else {
|
||||
// Everything else: enabled by default, explicitly disabled via [host, port] tuple
|
||||
return !addr.disabled.some(
|
||||
([host, port]) => host === h.host && port === (h.port ?? 0),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -242,11 +247,7 @@ export const filledAddress = (
|
||||
host: Host,
|
||||
addressInfo: AddressInfo,
|
||||
): FilledAddressInfo => {
|
||||
const toUrls = addressHostToUrl.bind(null, addressInfo)
|
||||
const toUrlArray = (h: HostnameInfo) => {
|
||||
const u = toUrls(h)
|
||||
return [u.url, u.sslUrl].filter((u) => u !== null)
|
||||
}
|
||||
const toUrl = addressHostToUrl.bind(null, addressInfo)
|
||||
const binding = host.bindings[addressInfo.internalPort]
|
||||
const hostnames = binding ? enabledAddresses(binding.addresses) : []
|
||||
|
||||
@@ -266,11 +267,11 @@ export const filledAddress = (
|
||||
return {
|
||||
...addressInfo,
|
||||
hostnames,
|
||||
toUrls,
|
||||
toUrl,
|
||||
format: <Format extends Formats = 'urlstring'>(format?: Format) => {
|
||||
let res: FormatReturnTy<{}, Format>[] = hostnames as any
|
||||
if (format === 'hostname-info') return res
|
||||
const urls = hostnames.flatMap(toUrlArray)
|
||||
const urls = hostnames.map(toUrl)
|
||||
if (format === 'url') res = urls.map((u) => new URL(u)) as any
|
||||
else res = urls as any
|
||||
return res
|
||||
|
||||
Reference in New Issue
Block a user