mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
feat: implement preferred port allocation and per-address enable/disable
- Add AvailablePorts::try_alloc() with SSL tracking (BTreeMap<u16, bool>) - Add DerivedAddressInfo on BindInfo with private_disabled/public_enabled/possible sets - Add Bindings wrapper with Map impl for patchdb indexed access - Flatten HostAddress from single-variant enum to struct - Replace set-gateway-enabled RPC with set-address-enabled - Remove hostname_info from Host; computed addresses now in BindInfo.addresses.possible - Compute possible addresses inline in NetServiceData::update() - Update DB migration, SDK types, frontend, and container-runtime
This commit is contained in:
18
CLAUDE.md
18
CLAUDE.md
@@ -29,6 +29,24 @@ make update-startbox REMOTE=start9@<ip> # Fastest iteration (binary + UI)
|
|||||||
make test-core # Run Rust tests
|
make test-core # Run Rust tests
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Verifying code changes
|
||||||
|
|
||||||
|
When making changes across multiple layers (Rust, SDK, web, container-runtime), verify in this order:
|
||||||
|
|
||||||
|
1. **Rust**: `cargo check -p start-os` — verifies core compiles
|
||||||
|
2. **TS bindings**: `make ts-bindings` — regenerates TypeScript types from Rust `#[ts(export)]` structs
|
||||||
|
- Runs `./core/build/build-ts.sh` to export ts-rs types to `core/bindings/`
|
||||||
|
- Syncs `core/bindings/` → `sdk/base/lib/osBindings/` via rsync
|
||||||
|
- If you manually edit files in `sdk/base/lib/osBindings/`, you must still rebuild the SDK (step 3)
|
||||||
|
3. **SDK bundle**: `cd sdk && make baseDist dist` — compiles SDK source into packages
|
||||||
|
- `baseDist/` is consumed by `/web` (via `@start9labs/start-sdk-base`)
|
||||||
|
- `dist/` is consumed by `/container-runtime` (via `@start9labs/start-sdk`)
|
||||||
|
- Web and container-runtime reference the **built** SDK, not source files
|
||||||
|
4. **Web type check**: `cd web && npm run check` — type-checks all Angular projects
|
||||||
|
5. **Container runtime type check**: `cd container-runtime && npm run check` — type-checks the runtime
|
||||||
|
|
||||||
|
**Important**: Editing `sdk/base/lib/osBindings/*.ts` alone is NOT sufficient — you must rebuild the SDK bundle (step 3) before web/container-runtime can see the changes.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Core (`/core`)
|
### Core (`/core`)
|
||||||
|
|||||||
123
agents/TODO.md
123
agents/TODO.md
@@ -33,114 +33,39 @@ Pending tasks for AI agents. Remove items when completed.
|
|||||||
`preferred_external_port: 443` won't get 443 as its `assigned_ssl_port` (it's taken), but it CAN
|
`preferred_external_port: 443` won't get 443 as its `assigned_ssl_port` (it's taken), but it CAN
|
||||||
still have domain vhost entries on port 443 — SNI routes by hostname.
|
still have domain vhost entries on port 443 — SNI routes by hostname.
|
||||||
|
|
||||||
#### 1. Preferred Port Allocation for Ownership (`forward.rs`, `binding.rs`)
|
#### 1. Preferred Port Allocation for Ownership ✅ DONE
|
||||||
|
|
||||||
Expand `AvailablePorts` to support trying a preferred port before falling back to the dynamic range:
|
`AvailablePorts::try_alloc(port) -> Option<u16>` added to `forward.rs`. `BindInfo::new()` and
|
||||||
|
`BindInfo::update()` attempt the preferred port first, falling back to dynamic-range allocation.
|
||||||
|
|
||||||
- Add `try_alloc(port) -> Option<u16>`: Attempts to exclusively allocate a specific port. Returns
|
#### 2. Per-Address Enable/Disable ✅ DONE
|
||||||
`None` if the port is already allocated or restricted.
|
|
||||||
- Enforce the restricted port list (currently noted in `vhost.rs:89`: `<=1024, >=32768, 5355, 5432,
|
|
||||||
9050, 6010, 9051, 5353`) — skip the preferred port if restricted, except for ports the OS itself
|
|
||||||
uses (80, 443).
|
|
||||||
- No SSL-vs-non-SSL distinction or refcounting needed at this layer — ownership is always exclusive.
|
|
||||||
SSL port sharing for domains is handled entirely by the VHostController via SNI.
|
|
||||||
|
|
||||||
Modify `BindInfo::new()` and `BindInfo::update()` to attempt the preferred port first:
|
Gateway-level `private_disabled`/`public_enabled` on `NetInfo` replaced with per-address
|
||||||
|
`DerivedAddressInfo` on `BindInfo`. `hostname_info` removed from `Host` — computed addresses now
|
||||||
|
live in `BindInfo.addresses.possible`.
|
||||||
|
|
||||||
```
|
**`DerivedAddressInfo` struct** (on `BindInfo`):
|
||||||
assigned_ssl_port = try_alloc(ssl.preferred_external_port)
|
|
||||||
.unwrap_or(dynamic_pool.alloc())
|
|
||||||
assigned_port = try_alloc(options.preferred_external_port)
|
|
||||||
.unwrap_or(dynamic_pool.alloc())
|
|
||||||
```
|
|
||||||
|
|
||||||
After this change, `assigned_ssl_port` may match the preferred port if it was available, or fall back
|
|
||||||
to the dynamic range as before.
|
|
||||||
|
|
||||||
#### 2. Per-Address Enable/Disable (replaces gateway overrides)
|
|
||||||
|
|
||||||
**Current model being removed**: `NetInfo` has `private_disabled: OrdSet<GatewayId>` and
|
|
||||||
`public_enabled: OrdSet<GatewayId>` — gateway-level toggles where private gateways are enabled by
|
|
||||||
default and public gateways are disabled by default. The `set-gateway-enabled` RPC endpoint and the
|
|
||||||
`InterfaceFilter` impl on `NetInfo` use these sets. This model is unintuitive because users think in
|
|
||||||
terms of individual addresses, not gateways.
|
|
||||||
|
|
||||||
**New model**: Per-address enable/disable using `DerivedAddressInfo` on `BindInfo`. Instead of
|
|
||||||
gateway-level toggles, users toggle individual addresses. The `hostnameInfo` field moves from `Host`
|
|
||||||
to `BindInfo.addresses` (as the computed `possible` set).
|
|
||||||
|
|
||||||
**`DerivedAddressInfo` struct** (added to `BindInfo`):
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct DerivedAddressInfo {
|
pub struct DerivedAddressInfo {
|
||||||
/// User-controlled: private-gateway addresses the user has disabled
|
|
||||||
pub private_disabled: BTreeSet<HostnameInfo>,
|
pub private_disabled: BTreeSet<HostnameInfo>,
|
||||||
/// User-controlled: public-gateway addresses the user has enabled
|
|
||||||
pub public_enabled: BTreeSet<HostnameInfo>,
|
pub public_enabled: BTreeSet<HostnameInfo>,
|
||||||
/// COMPUTED by update(): all possible addresses for this binding
|
pub possible: BTreeSet<HostnameInfo>, // COMPUTED by update()
|
||||||
pub possible: BTreeSet<HostnameInfo>,
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`DerivedAddressInfo::enabled()` returns `possible` filtered by the two sets: private addresses are
|
`DerivedAddressInfo::enabled()` returns `possible` filtered by the two sets. `HostnameInfo` derives
|
||||||
enabled by default (disabled if in `private_disabled`), public addresses are disabled by default
|
`Ord` for `BTreeSet` usage. `AddressFilter` (implementing `InterfaceFilter`) derives enabled
|
||||||
(enabled if in `public_enabled`). Requires `HostnameInfo` to derive `Ord` for `BTreeSet` usage.
|
gateway set from `DerivedAddressInfo` for vhost/forward filtering.
|
||||||
|
|
||||||
**How disabling works per address type**:
|
**RPC endpoint**: `set-gateway-enabled` replaced with `set-address-enabled` (on both
|
||||||
|
`server.host.binding` and `package.host.binding`).
|
||||||
|
|
||||||
The enforcement mechanism varies by address type because different addresses are reached through
|
**How disabling works per address type** (enforcement deferred to Section 3):
|
||||||
different network paths:
|
|
||||||
|
|
||||||
- **WAN IP:port** (public gateway IP addresses): Disabled via **source-IP gating** in the vhost
|
- **WAN/LAN IP:port**: Will be enforced via **source-IP gating** in the vhost layer (Section 3).
|
||||||
layer (Section 3). Public and private traffic share the same port listener, so we can't just
|
|
||||||
remove the vhost entry — that would also block private traffic. Instead, the vhost target is
|
|
||||||
tagged with which source-IP classes it accepts. When a WAN IP address is disabled, the vhost
|
|
||||||
target rejects connections whose source IP matches the gateway (i.e., NAT'd internet traffic)
|
|
||||||
or falls outside the gateway's LAN subnets. LAN traffic to the same port is unaffected.
|
|
||||||
- **LAN IP:port** (private gateway IP addresses): Also enforced via **source-IP gating**. When
|
|
||||||
disabled, the vhost target rejects connections from LAN subnets on that gateway. This is the
|
|
||||||
inverse of the WAN case — same mechanism, different source-IP class.
|
|
||||||
- **Hostname-based addresses** (`.local`, domains): Disabled by **not creating the vhost/SNI
|
- **Hostname-based addresses** (`.local`, domains): Disabled by **not creating the vhost/SNI
|
||||||
entry** for that hostname. Since hostname-based routing uses SNI (SSL) or Host header (HTTP),
|
entry** for that hostname.
|
||||||
removing the entry means the hostname simply doesn't resolve to a backend. No traffic reaches
|
|
||||||
the service for that hostname.
|
|
||||||
|
|
||||||
**Backend changes**:
|
|
||||||
|
|
||||||
- **Remove from `NetInfo`**: Delete the `private_disabled` and `public_enabled` fields entirely.
|
|
||||||
`NetInfo` becomes just `{ assigned_port: Option<u16>, assigned_ssl_port: Option<u16> }`.
|
|
||||||
- **Add `addresses: DerivedAddressInfo` to `BindInfo`**: User-controlled sets (`private_disabled`,
|
|
||||||
`public_enabled`) are preserved across updates; `possible` is recomputed by `update()`.
|
|
||||||
- **Remove `hostname_info` from `Host`**: Computed addresses now live in `BindInfo.addresses.possible`
|
|
||||||
instead of being a top-level field on `Host` that was never persisted to the DB.
|
|
||||||
- **Default behavior preserved**: Private-gateway addresses default to enabled, public-gateway
|
|
||||||
addresses default to disabled, via the `enabled()` method on `DerivedAddressInfo`.
|
|
||||||
- **Remove `set-gateway-enabled`** RPC endpoint from `binding.rs`.
|
|
||||||
- **Remove `InterfaceFilter` impl for `NetInfo`**: The per-gateway filter logic is replaced by
|
|
||||||
per-address filtering derived from `DerivedAddressInfo`.
|
|
||||||
|
|
||||||
**New RPC endpoint** (`binding.rs`):
|
|
||||||
|
|
||||||
Following the existing `HostApiKind` pattern, replace `set-gateway-enabled` with:
|
|
||||||
|
|
||||||
- **`set-address-enabled`** — Toggle an individual address on or off.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface BindingSetAddressEnabledParams {
|
|
||||||
internalPort: number
|
|
||||||
address: HostnameInfo // identifies the address directly (no separate AddressId type needed)
|
|
||||||
enabled: boolean | null // null = reset to default
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Mutates `BindInfo.addresses.private_disabled` / `.public_enabled` based on the address's `public`
|
|
||||||
field. If `public == true` and enabled, add to `public_enabled`; if disabled, remove. If
|
|
||||||
`public == false` and enabled, remove from `private_disabled`; if disabled, add. Uses `sync_db`
|
|
||||||
metadata.
|
|
||||||
|
|
||||||
This yields two RPC methods:
|
|
||||||
- `server.host.binding.set-address-enabled`
|
|
||||||
- `package.host.binding.set-address-enabled`
|
|
||||||
|
|
||||||
#### 3. Eliminate the Port 5443 Hack: Source-IP-Based WAN Blocking (`vhost.rs`, `net_controller.rs`)
|
#### 3. Eliminate the Port 5443 Hack: Source-IP-Based WAN Blocking (`vhost.rs`, `net_controller.rs`)
|
||||||
|
|
||||||
@@ -206,7 +131,7 @@ Pending tasks for AI agents. Remove items when completed.
|
|||||||
|
|
||||||
##### View Page
|
##### View Page
|
||||||
|
|
||||||
Displays all computed addresses for the interface (from `hostname_info`) as a flat list. For each
|
Displays all computed addresses for the interface (from `BindInfo.addresses`) as a flat list. For each
|
||||||
address, show: URL, type (IPv4, IPv6, .local, domain), access level (public/private),
|
address, show: URL, type (IPv4, IPv6, .local, domain), access level (public/private),
|
||||||
gateway name, SSL indicator, enable/disable state, port forward info for public addresses, and a test button
|
gateway name, SSL indicator, enable/disable state, port forward info for public addresses, and a test button
|
||||||
for reachability (see Section 7).
|
for reachability (see Section 7).
|
||||||
@@ -282,13 +207,13 @@ Pending tasks for AI agents. Remove items when completed.
|
|||||||
|
|
||||||
| File | Role |
|
| File | Role |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `core/src/net/forward.rs` | `AvailablePorts` — port pool allocation |
|
| `core/src/net/forward.rs` | `AvailablePorts` — port pool allocation, `try_alloc()` for preferred ports |
|
||||||
| `core/src/net/host/binding.rs` | `BindInfo`/`NetInfo`/`DerivedAddressInfo` — remove gateway overrides, add per-address enable/disable sets, new RPC endpoints |
|
| `core/src/net/host/binding.rs` | `Bindings` (Map wrapper for patchdb), `BindInfo`/`NetInfo`/`DerivedAddressInfo`/`AddressFilter` — per-address enable/disable, `set-address-enabled` RPC |
|
||||||
| `core/src/net/net_controller.rs:259` | `NetServiceData::update()` — compute `enabled` on `HostnameInfo`, vhost/forward/DNS reconciliation, 5443 hack removal |
|
| `core/src/net/net_controller.rs:259` | `NetServiceData::update()` — computes `DerivedAddressInfo.possible`, vhost/forward/DNS reconciliation, 5443 hack removal |
|
||||||
| `core/src/net/vhost.rs` | `VHostController` / `ProxyTarget` — source-IP gating for public/private |
|
| `core/src/net/vhost.rs` | `VHostController` / `ProxyTarget` — source-IP gating for public/private |
|
||||||
| `core/src/net/gateway.rs` | `InterfaceFilter` — remove `NetInfo` impl, simplify |
|
| `core/src/net/gateway.rs` | `InterfaceFilter` trait and filter types (`AddressFilter`, `PublicFilter`, etc.) |
|
||||||
| `core/src/net/service_interface.rs` | `HostnameInfo` — add `Ord` derives for use in `BTreeSet` |
|
| `core/src/net/service_interface.rs` | `HostnameInfo` — derives `Ord` for `BTreeSet` usage |
|
||||||
| `core/src/net/host/address.rs` | Existing domain/onion CRUD endpoints (no changes needed) |
|
| `core/src/net/host/address.rs` | `HostAddress` (flattened struct), domain CRUD endpoints |
|
||||||
| `sdk/base/lib/interfaces/Host.ts` | SDK `MultiHost.bindPort()` — no changes needed |
|
| `sdk/base/lib/interfaces/Host.ts` | SDK `MultiHost.bindPort()` — no changes needed |
|
||||||
| `core/src/db/model/public.rs` | Public DB model — port forward mapping |
|
| `core/src/db/model/public.rs` | Public DB model — port forward mapping |
|
||||||
|
|
||||||
|
|||||||
@@ -82,17 +82,14 @@ export class DockerProcedureContainer extends Drop {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else if (volumeMount.type === "certificate") {
|
} else if (volumeMount.type === "certificate") {
|
||||||
|
const hostInfo = await effects.getHostInfo({
|
||||||
|
hostId: volumeMount["interface-id"],
|
||||||
|
})
|
||||||
const hostnames = [
|
const hostnames = [
|
||||||
`${packageId}.embassy`,
|
`${packageId}.embassy`,
|
||||||
...new Set(
|
...new Set(
|
||||||
Object.values(
|
Object.values(hostInfo?.bindings || {})
|
||||||
(
|
.flatMap((b) => b.addresses.possible)
|
||||||
await effects.getHostInfo({
|
|
||||||
hostId: volumeMount["interface-id"],
|
|
||||||
})
|
|
||||||
)?.hostnameInfo || {},
|
|
||||||
)
|
|
||||||
.flatMap((h) => h)
|
|
||||||
.map((h) => h.hostname.value),
|
.map((h) => h.hostname.value),
|
||||||
).values(),
|
).values(),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1244,8 +1244,8 @@ async function updateConfig(
|
|||||||
? ""
|
? ""
|
||||||
: catchFn(
|
: catchFn(
|
||||||
() =>
|
() =>
|
||||||
filled.addressInfo!.filter({ kind: "mdns" })!
|
filled.addressInfo!.filter({ kind: "mdns" })!.hostnames[0]
|
||||||
.hostnames[0].hostname.value,
|
.hostname.value,
|
||||||
) || ""
|
) || ""
|
||||||
mutConfigValue[key] = url
|
mutConfigValue[key] = url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::db::model::Database;
|
|||||||
use crate::db::model::package::AllPackageData;
|
use crate::db::model::package::AllPackageData;
|
||||||
use crate::net::acme::AcmeProvider;
|
use crate::net::acme::AcmeProvider;
|
||||||
use crate::net::host::Host;
|
use crate::net::host::Host;
|
||||||
use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo};
|
use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, Bindings, DerivedAddressInfo, NetInfo};
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::utils::ipv6_is_local;
|
||||||
use crate::net::vhost::AlpnInfo;
|
use crate::net::vhost::AlpnInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -63,7 +63,8 @@ impl Public {
|
|||||||
post_init_migration_todos: BTreeMap::new(),
|
post_init_migration_todos: BTreeMap::new(),
|
||||||
network: NetworkInfo {
|
network: NetworkInfo {
|
||||||
host: Host {
|
host: Host {
|
||||||
bindings: [(
|
bindings: Bindings(
|
||||||
|
[(
|
||||||
80,
|
80,
|
||||||
BindInfo {
|
BindInfo {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -82,16 +83,15 @@ impl Public {
|
|||||||
net: NetInfo {
|
net: NetInfo {
|
||||||
assigned_port: None,
|
assigned_port: None,
|
||||||
assigned_ssl_port: Some(443),
|
assigned_ssl_port: Some(443),
|
||||||
private_disabled: OrdSet::new(),
|
|
||||||
public_enabled: OrdSet::new(),
|
|
||||||
},
|
},
|
||||||
|
addresses: DerivedAddressInfo::default(),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
|
),
|
||||||
public_domains: BTreeMap::new(),
|
public_domains: BTreeMap::new(),
|
||||||
private_domains: BTreeSet::new(),
|
private_domains: BTreeSet::new(),
|
||||||
hostname_info: BTreeMap::new(),
|
|
||||||
},
|
},
|
||||||
wifi: WifiInfo {
|
wifi: WifiInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::sync::{Arc, Weak};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use id_pool::IdPool;
|
|
||||||
use iddqd::{IdOrdItem, IdOrdMap};
|
use iddqd::{IdOrdItem, IdOrdMap};
|
||||||
|
use rand::Rng;
|
||||||
use imbl::OrdMap;
|
use imbl::OrdMap;
|
||||||
use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -23,25 +23,49 @@ use crate::util::serde::{HandlerExtSerde, display_serializable};
|
|||||||
use crate::util::sync::Watch;
|
use crate::util::sync::Watch;
|
||||||
|
|
||||||
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
||||||
pub const FIRST_DYNAMIC_PRIVATE_PORT: u16 = 49152;
|
const EPHEMERAL_PORT_START: u16 = 49152;
|
||||||
|
// vhost.rs:89 — not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353
|
||||||
|
const RESTRICTED_PORTS: &[u16] = &[5353, 5355, 5432, 6010, 9050, 9051];
|
||||||
|
|
||||||
|
fn is_restricted(port: u16) -> bool {
|
||||||
|
port <= 1024 || RESTRICTED_PORTS.contains(&port)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct AvailablePorts(IdPool);
|
pub struct AvailablePorts(BTreeMap<u16, bool>);
|
||||||
impl AvailablePorts {
|
impl AvailablePorts {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(IdPool::new_ranged(FIRST_DYNAMIC_PRIVATE_PORT..u16::MAX))
|
Self(BTreeMap::new())
|
||||||
}
|
}
|
||||||
pub fn alloc(&mut self) -> Result<u16, Error> {
|
pub fn alloc(&mut self, ssl: bool) -> Result<u16, Error> {
|
||||||
self.0.request_id().ok_or_else(|| {
|
let mut rng = rand::rng();
|
||||||
Error::new(
|
for _ in 0..1000 {
|
||||||
|
let port = rng.random_range(EPHEMERAL_PORT_START..u16::MAX);
|
||||||
|
if !self.0.contains_key(&port) {
|
||||||
|
self.0.insert(port, ssl);
|
||||||
|
return Ok(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
eyre!("{}", t!("net.forward.no-dynamic-ports-available")),
|
eyre!("{}", t!("net.forward.no-dynamic-ports-available")),
|
||||||
ErrorKind::Network,
|
ErrorKind::Network,
|
||||||
)
|
))
|
||||||
})
|
}
|
||||||
|
/// Try to allocate a specific port. Returns Some(port) if available, None if taken/restricted.
|
||||||
|
pub fn try_alloc(&mut self, port: u16, ssl: bool) -> Option<u16> {
|
||||||
|
if is_restricted(port) || self.0.contains_key(&port) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.0.insert(port, ssl);
|
||||||
|
Some(port)
|
||||||
|
}
|
||||||
|
/// Returns whether a given allocated port is SSL.
|
||||||
|
pub fn is_ssl(&self, port: u16) -> bool {
|
||||||
|
self.0.get(&port).copied().unwrap_or(false)
|
||||||
}
|
}
|
||||||
pub fn free(&mut self, ports: impl IntoIterator<Item = u16>) {
|
pub fn free(&mut self, ports: impl IntoIterator<Item = u16>) {
|
||||||
for port in ports {
|
for port in ports {
|
||||||
self.0.return_id(port).unwrap_or_default();
|
self.0.remove(&port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1685,19 +1685,9 @@ where
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_filter() {
|
fn test_filter() {
|
||||||
use crate::net::host::binding::NetInfo;
|
|
||||||
let wg1 = "wg1".parse::<GatewayId>().unwrap();
|
let wg1 = "wg1".parse::<GatewayId>().unwrap();
|
||||||
assert!(!InterfaceFilter::filter(
|
assert!(!InterfaceFilter::filter(
|
||||||
&AndFilter(
|
&AndFilter(IdFilter(wg1.clone()), PublicFilter { public: false }).into_dyn(),
|
||||||
NetInfo {
|
|
||||||
private_disabled: [wg1.clone()].into_iter().collect(),
|
|
||||||
public_enabled: Default::default(),
|
|
||||||
assigned_port: None,
|
|
||||||
assigned_ssl_port: None,
|
|
||||||
},
|
|
||||||
AndFilter(IdFilter(wg1.clone()), PublicFilter { public: false }),
|
|
||||||
)
|
|
||||||
.into_dyn(),
|
|
||||||
&wg1,
|
&wg1,
|
||||||
&NetworkInterfaceInfo {
|
&NetworkInterfaceInfo {
|
||||||
name: None,
|
name: None,
|
||||||
|
|||||||
@@ -16,15 +16,11 @@ use crate::prelude::*;
|
|||||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[serde(rename_all_fields = "camelCase")]
|
pub struct HostAddress {
|
||||||
#[serde(tag = "kind")]
|
pub address: InternedString,
|
||||||
pub enum HostAddress {
|
pub public: Option<PublicDomainConfig>,
|
||||||
Domain {
|
pub private: bool,
|
||||||
address: InternedString,
|
|
||||||
public: Option<PublicDomainConfig>,
|
|
||||||
private: bool,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||||
@@ -151,29 +147,18 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
|||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
||||||
for address in &res {
|
for entry in &res {
|
||||||
match address {
|
if let Some(PublicDomainConfig { gateway, acme }) = &entry.public {
|
||||||
HostAddress::Domain {
|
|
||||||
address,
|
|
||||||
public: Some(PublicDomainConfig { gateway, acme }),
|
|
||||||
private,
|
|
||||||
} => {
|
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
address,
|
entry.address,
|
||||||
&format!(
|
&format!(
|
||||||
"{} ({gateway})",
|
"{} ({gateway})",
|
||||||
if *private { "YES" } else { "ONLY" }
|
if entry.private { "YES" } else { "ONLY" }
|
||||||
),
|
),
|
||||||
acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE")
|
acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE")
|
||||||
]);
|
]);
|
||||||
}
|
} else {
|
||||||
HostAddress::Domain {
|
table.add_row(row![entry.address, &format!("NO"), "N/A"]);
|
||||||
address,
|
|
||||||
public: None,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
table.add_row(row![address, &format!("NO"), "N/A"]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
use imbl::OrdSet;
|
|
||||||
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::public::NetworkInterfaceInfo;
|
use crate::db::model::public::NetworkInterfaceInfo;
|
||||||
|
use crate::db::prelude::Map;
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
use crate::net::gateway::InterfaceFilter;
|
use crate::net::gateway::InterfaceFilter;
|
||||||
use crate::net::host::HostApiKind;
|
use crate::net::host::HostApiKind;
|
||||||
|
use crate::net::service_interface::HostnameInfo;
|
||||||
use crate::net::vhost::AlpnInfo;
|
use crate::net::vhost::AlpnInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::FromStrParser;
|
use crate::util::FromStrParser;
|
||||||
@@ -45,51 +46,137 @@ impl FromStr for BindId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS, HasModel)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct DerivedAddressInfo {
|
||||||
|
/// User-controlled: private-gateway addresses the user has disabled
|
||||||
|
pub private_disabled: BTreeSet<HostnameInfo>,
|
||||||
|
/// User-controlled: public-gateway addresses the user has enabled
|
||||||
|
pub public_enabled: BTreeSet<HostnameInfo>,
|
||||||
|
/// COMPUTED: NetServiceData::update — all possible addresses for this binding
|
||||||
|
pub possible: BTreeSet<HostnameInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerivedAddressInfo {
|
||||||
|
/// Returns addresses that are currently enabled.
|
||||||
|
/// Private addresses are enabled by default (disabled if in private_disabled).
|
||||||
|
/// Public addresses are disabled by default (enabled if in public_enabled).
|
||||||
|
pub fn enabled(&self) -> BTreeSet<&HostnameInfo> {
|
||||||
|
self.possible
|
||||||
|
.iter()
|
||||||
|
.filter(|h| {
|
||||||
|
if h.public {
|
||||||
|
self.public_enabled.contains(h)
|
||||||
|
} else {
|
||||||
|
!self.private_disabled.contains(h)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive a gateway-level InterfaceFilter from the enabled addresses.
|
||||||
|
/// A gateway passes the filter if it has any enabled address for this binding.
|
||||||
|
pub fn gateway_filter(&self) -> AddressFilter {
|
||||||
|
let enabled_gateways: BTreeSet<GatewayId> = self
|
||||||
|
.enabled()
|
||||||
|
.into_iter()
|
||||||
|
.map(|h| h.gateway.id.clone())
|
||||||
|
.collect();
|
||||||
|
AddressFilter(enabled_gateways)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gateway-level filter derived from DerivedAddressInfo.
|
||||||
|
/// Passes if the gateway has at least one enabled address.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct AddressFilter(pub BTreeSet<GatewayId>);
|
||||||
|
impl InterfaceFilter for AddressFilter {
|
||||||
|
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
||||||
|
info.ip_info.is_some() && self.0.contains(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct Bindings(pub BTreeMap<u16, BindInfo>);
|
||||||
|
|
||||||
|
impl Map for Bindings {
|
||||||
|
type Key = u16;
|
||||||
|
type Value = BindInfo;
|
||||||
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
|
Self::key_string(key)
|
||||||
|
}
|
||||||
|
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||||
|
Ok(InternedString::from_display(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Bindings {
|
||||||
|
type Target = BTreeMap<u16, BindInfo>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for Bindings {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
#[ts(export)]
|
||||||
pub struct BindInfo {
|
pub struct BindInfo {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub options: BindOptions,
|
pub options: BindOptions,
|
||||||
pub net: NetInfo,
|
pub net: NetInfo,
|
||||||
|
pub addresses: DerivedAddressInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct NetInfo {
|
pub struct NetInfo {
|
||||||
#[ts(as = "BTreeSet::<GatewayId>")]
|
|
||||||
#[serde(default)]
|
|
||||||
pub private_disabled: OrdSet<GatewayId>,
|
|
||||||
#[ts(as = "BTreeSet::<GatewayId>")]
|
|
||||||
#[serde(default)]
|
|
||||||
pub public_enabled: OrdSet<GatewayId>,
|
|
||||||
pub assigned_port: Option<u16>,
|
pub assigned_port: Option<u16>,
|
||||||
pub assigned_ssl_port: Option<u16>,
|
pub assigned_ssl_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
impl InterfaceFilter for NetInfo {
|
||||||
|
fn filter(&self, _id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
||||||
|
info.ip_info.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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_port = None;
|
let mut assigned_port = None;
|
||||||
let mut assigned_ssl_port = None;
|
let mut assigned_ssl_port = None;
|
||||||
if options.add_ssl.is_some() {
|
if let Some(ssl) = &options.add_ssl {
|
||||||
assigned_ssl_port = Some(available_ports.alloc()?);
|
assigned_ssl_port = available_ports
|
||||||
|
.try_alloc(ssl.preferred_external_port, true)
|
||||||
|
.or_else(|| Some(available_ports.alloc(true).ok()?));
|
||||||
}
|
}
|
||||||
if options
|
if options
|
||||||
.secure
|
.secure
|
||||||
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
|
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
|
||||||
{
|
{
|
||||||
assigned_port = Some(available_ports.alloc()?);
|
assigned_port = available_ports
|
||||||
|
.try_alloc(options.preferred_external_port, false)
|
||||||
|
.or_else(|| Some(available_ports.alloc(false).ok()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
options,
|
options,
|
||||||
net: NetInfo {
|
net: NetInfo {
|
||||||
private_disabled: OrdSet::new(),
|
|
||||||
public_enabled: OrdSet::new(),
|
|
||||||
assigned_port,
|
assigned_port,
|
||||||
assigned_ssl_port,
|
assigned_ssl_port,
|
||||||
},
|
},
|
||||||
|
addresses: DerivedAddressInfo::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn update(
|
pub fn update(
|
||||||
@@ -97,7 +184,11 @@ impl BindInfo {
|
|||||||
available_ports: &mut AvailablePorts,
|
available_ports: &mut AvailablePorts,
|
||||||
options: BindOptions,
|
options: BindOptions,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let Self { net: mut lan, .. } = self;
|
let Self {
|
||||||
|
net: mut lan,
|
||||||
|
addresses,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
if options
|
if options
|
||||||
.secure
|
.secure
|
||||||
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
|
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
|
||||||
@@ -105,19 +196,26 @@ impl BindInfo {
|
|||||||
{
|
{
|
||||||
lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
|
lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
|
||||||
Some(port)
|
Some(port)
|
||||||
|
} else if let Some(port) =
|
||||||
|
available_ports.try_alloc(options.preferred_external_port, false)
|
||||||
|
{
|
||||||
|
Some(port)
|
||||||
} else {
|
} else {
|
||||||
Some(available_ports.alloc()?)
|
Some(available_ports.alloc(false)?)
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if let Some(port) = lan.assigned_port.take() {
|
if let Some(port) = lan.assigned_port.take() {
|
||||||
available_ports.free([port]);
|
available_ports.free([port]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.add_ssl.is_some() {
|
if let Some(ssl) = &options.add_ssl {
|
||||||
lan.assigned_ssl_port = if let Some(port) = lan.assigned_ssl_port.take() {
|
lan.assigned_ssl_port = if let Some(port) = lan.assigned_ssl_port.take() {
|
||||||
Some(port)
|
Some(port)
|
||||||
|
} else if let Some(port) = available_ports.try_alloc(ssl.preferred_external_port, true)
|
||||||
|
{
|
||||||
|
Some(port)
|
||||||
} else {
|
} else {
|
||||||
Some(available_ports.alloc()?)
|
Some(available_ports.alloc(true)?)
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if let Some(port) = lan.assigned_ssl_port.take() {
|
if let Some(port) = lan.assigned_ssl_port.take() {
|
||||||
@@ -128,22 +226,17 @@ impl BindInfo {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
options,
|
options,
|
||||||
net: lan,
|
net: lan,
|
||||||
|
addresses: DerivedAddressInfo {
|
||||||
|
private_disabled: addresses.private_disabled,
|
||||||
|
public_enabled: addresses.public_enabled,
|
||||||
|
possible: BTreeSet::new(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn disable(&mut self) {
|
pub fn disable(&mut self) {
|
||||||
self.enabled = false;
|
self.enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InterfaceFilter for NetInfo {
|
|
||||||
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
|
||||||
info.ip_info.is_some()
|
|
||||||
&& if info.public() {
|
|
||||||
self.public_enabled.contains(id)
|
|
||||||
} else {
|
|
||||||
!self.private_disabled.contains(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -188,7 +281,7 @@ pub fn binding<C: Context, Kind: HostApiKind>()
|
|||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "EXTERNAL PORT", "EXTERNAL SSL PORT"]);
|
table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "EXTERNAL PORT", "EXTERNAL SSL PORT"]);
|
||||||
for (internal, info) in res {
|
for (internal, info) in res.iter() {
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
internal,
|
internal,
|
||||||
info.enabled,
|
info.enabled,
|
||||||
@@ -213,12 +306,12 @@ pub fn binding<C: Context, Kind: HostApiKind>()
|
|||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"set-gateway-enabled",
|
"set-address-enabled",
|
||||||
from_fn_async(set_gateway_enabled::<Kind>)
|
from_fn_async(set_address_enabled::<Kind>)
|
||||||
.with_metadata("sync_db", Value::Bool(true))
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
.with_inherited(Kind::inheritance)
|
.with_inherited(Kind::inheritance)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("about.set-gateway-enabled-for-binding")
|
.with_about("about.set-address-enabled-for-binding")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -227,7 +320,7 @@ pub async fn list_bindings<Kind: HostApiKind>(
|
|||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
_: Empty,
|
_: Empty,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<BTreeMap<u16, BindInfo>, Error> {
|
) -> Result<Bindings, Error> {
|
||||||
Kind::host_for(&inheritance, &mut ctx.db.peek().await)?
|
Kind::host_for(&inheritance, &mut ctx.db.peek().await)?
|
||||||
.as_bindings()
|
.as_bindings()
|
||||||
.de()
|
.de()
|
||||||
@@ -236,50 +329,44 @@ pub async fn list_bindings<Kind: HostApiKind>(
|
|||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct BindingGatewaySetEnabledParams {
|
pub struct BindingSetAddressEnabledParams {
|
||||||
#[arg(help = "help.arg.internal-port")]
|
#[arg(help = "help.arg.internal-port")]
|
||||||
internal_port: u16,
|
internal_port: u16,
|
||||||
#[arg(help = "help.arg.gateway-id")]
|
#[arg(long, help = "help.arg.address")]
|
||||||
gateway: GatewayId,
|
address: String,
|
||||||
#[arg(long, help = "help.arg.binding-enabled")]
|
#[arg(long, help = "help.arg.binding-enabled")]
|
||||||
enabled: Option<bool>,
|
enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_gateway_enabled<Kind: HostApiKind>(
|
pub async fn set_address_enabled<Kind: HostApiKind>(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
BindingGatewaySetEnabledParams {
|
BindingSetAddressEnabledParams {
|
||||||
internal_port,
|
internal_port,
|
||||||
gateway,
|
address,
|
||||||
enabled,
|
enabled,
|
||||||
}: BindingGatewaySetEnabledParams,
|
}: BindingSetAddressEnabledParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let enabled = enabled.unwrap_or(true);
|
let enabled = enabled.unwrap_or(true);
|
||||||
let gateway_public = ctx
|
let address: HostnameInfo =
|
||||||
.net_controller
|
serde_json::from_str(&address).with_kind(ErrorKind::Deserialization)?;
|
||||||
.net_iface
|
|
||||||
.watcher
|
|
||||||
.ip_info()
|
|
||||||
.get(&gateway)
|
|
||||||
.or_not_found(&gateway)?
|
|
||||||
.public();
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_bindings_mut()
|
.as_bindings_mut()
|
||||||
.mutate(|b| {
|
.mutate(|b| {
|
||||||
let net = &mut b.get_mut(&internal_port).or_not_found(internal_port)?.net;
|
let bind = b.get_mut(&internal_port).or_not_found(internal_port)?;
|
||||||
if gateway_public {
|
if address.public {
|
||||||
if enabled {
|
if enabled {
|
||||||
net.public_enabled.insert(gateway);
|
bind.addresses.public_enabled.insert(address.clone());
|
||||||
} else {
|
} else {
|
||||||
net.public_enabled.remove(&gateway);
|
bind.addresses.public_enabled.remove(&address);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if enabled {
|
if enabled {
|
||||||
net.private_disabled.remove(&gateway);
|
bind.addresses.private_disabled.remove(&address);
|
||||||
} else {
|
} else {
|
||||||
net.private_disabled.insert(gateway);
|
bind.addresses.private_disabled.insert(address.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ use crate::context::RpcContext;
|
|||||||
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, PublicDomainConfig, address_api};
|
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
||||||
use crate::net::host::binding::{BindInfo, BindOptions, binding};
|
use crate::net::host::binding::{BindInfo, BindOptions, Bindings, binding};
|
||||||
use crate::net::service_interface::HostnameInfo;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{HostId, PackageId};
|
use crate::{HostId, PackageId};
|
||||||
|
|
||||||
@@ -26,11 +25,9 @@ pub mod binding;
|
|||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Host {
|
pub struct Host {
|
||||||
pub bindings: BTreeMap<u16, BindInfo>,
|
pub bindings: Bindings,
|
||||||
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
||||||
pub private_domains: BTreeSet<InternedString>,
|
pub private_domains: BTreeSet<InternedString>,
|
||||||
/// COMPUTED: NetService::update
|
|
||||||
pub hostname_info: BTreeMap<u16, Vec<HostnameInfo>>, // internal port -> Hostnames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Host> for Host {
|
impl AsRef<Host> for Host {
|
||||||
@@ -45,7 +42,7 @@ impl Host {
|
|||||||
pub fn addresses<'a>(&'a self) -> impl Iterator<Item = HostAddress> + 'a {
|
pub fn addresses<'a>(&'a self) -> impl Iterator<Item = HostAddress> + 'a {
|
||||||
self.public_domains
|
self.public_domains
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(address, config)| HostAddress::Domain {
|
.map(|(address, config)| HostAddress {
|
||||||
address: address.clone(),
|
address: address.clone(),
|
||||||
public: Some(config.clone()),
|
public: Some(config.clone()),
|
||||||
private: self.private_domains.contains(address),
|
private: self.private_domains.contains(address),
|
||||||
@@ -54,7 +51,7 @@ impl Host {
|
|||||||
self.private_domains
|
self.private_domains
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| !self.public_domains.contains_key(*a))
|
.filter(|a| !self.public_domains.contains_key(*a))
|
||||||
.map(|address| HostAddress::Domain {
|
.map(|address| HostAddress {
|
||||||
address: address.clone(),
|
address: address.clone(),
|
||||||
public: None,
|
public: None,
|
||||||
private: true,
|
private: true,
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ use crate::hostname::Hostname;
|
|||||||
use crate::net::dns::DnsController;
|
use crate::net::dns::DnsController;
|
||||||
use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule};
|
use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule};
|
||||||
use crate::net::gateway::{
|
use crate::net::gateway::{
|
||||||
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
AndFilter, AnyFilter, DynInterfaceFilter, IdFilter, InterfaceFilter,
|
||||||
PublicFilter, SecureFilter,
|
NetworkInterfaceController, OrFilter, PublicFilter, SecureFilter,
|
||||||
};
|
};
|
||||||
use crate::net::host::address::HostAddress;
|
use crate::net::host::address::HostAddress;
|
||||||
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
||||||
@@ -202,7 +202,7 @@ impl NetServiceData {
|
|||||||
.as_entries_mut()?
|
.as_entries_mut()?
|
||||||
{
|
{
|
||||||
host.as_bindings_mut().mutate(|b| {
|
host.as_bindings_mut().mutate(|b| {
|
||||||
for (internal_port, info) in b {
|
for (internal_port, info) in b.iter_mut() {
|
||||||
if !except.contains(&BindId {
|
if !except.contains(&BindId {
|
||||||
id: host_id.clone(),
|
id: host_id.clone(),
|
||||||
internal_port: *internal_port,
|
internal_port: *internal_port,
|
||||||
@@ -233,7 +233,7 @@ impl NetServiceData {
|
|||||||
.as_network_mut()
|
.as_network_mut()
|
||||||
.as_host_mut();
|
.as_host_mut();
|
||||||
host.as_bindings_mut().mutate(|b| {
|
host.as_bindings_mut().mutate(|b| {
|
||||||
for (internal_port, info) in b {
|
for (internal_port, info) in b.iter_mut() {
|
||||||
if !except.contains(&BindId {
|
if !except.contains(&BindId {
|
||||||
id: HostId::default(),
|
id: HostId::default(),
|
||||||
internal_port: *internal_port,
|
internal_port: *internal_port,
|
||||||
@@ -251,11 +251,15 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> {
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
ctrl: &NetController,
|
||||||
|
id: HostId,
|
||||||
|
mut host: Host,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let mut forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter)> = BTreeMap::new();
|
let mut forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter)> = BTreeMap::new();
|
||||||
let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
|
let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
|
||||||
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||||
let mut hostname_info: BTreeMap<u16, Vec<HostnameInfo>> = BTreeMap::new();
|
|
||||||
let binds = self.binds.entry(id.clone()).or_default();
|
let binds = self.binds.entry(id.clone()).or_default();
|
||||||
|
|
||||||
let peek = ctrl.db.peek().await;
|
let peek = ctrl.db.peek().await;
|
||||||
@@ -264,12 +268,30 @@ impl NetServiceData {
|
|||||||
let server_info = peek.as_public().as_server_info();
|
let server_info = peek.as_public().as_server_info();
|
||||||
let net_ifaces = ctrl.net_iface.watcher.ip_info();
|
let net_ifaces = ctrl.net_iface.watcher.ip_info();
|
||||||
let hostname = server_info.as_hostname().de()?;
|
let hostname = server_info.as_hostname().de()?;
|
||||||
for (port, bind) in &host.bindings {
|
let host_addresses: Vec<_> = host.addresses().collect();
|
||||||
|
for (port, bind) in host.bindings.iter_mut() {
|
||||||
if !bind.enabled {
|
if !bind.enabled {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if bind.net.assigned_port.is_some() || bind.net.assigned_ssl_port.is_some() {
|
if bind.net.assigned_port.is_none() && bind.net.assigned_ssl_port.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mut hostnames = BTreeSet::new();
|
let mut hostnames = BTreeSet::new();
|
||||||
|
let mut gw_filter = AnyFilter(
|
||||||
|
[PublicFilter { public: false }.into_dyn()]
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
bind.addresses
|
||||||
|
.public_enabled
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.gateway.id.clone())
|
||||||
|
.collect::<BTreeSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.map(IdFilter)
|
||||||
|
.map(InterfaceFilter::into_dyn),
|
||||||
|
)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
if let Some(ssl) = &bind.options.add_ssl {
|
if let Some(ssl) = &bind.options.add_ssl {
|
||||||
let external = bind
|
let external = bind
|
||||||
.net
|
.net
|
||||||
@@ -289,23 +311,22 @@ impl NetServiceData {
|
|||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(hostname, external),
|
(hostname, external),
|
||||||
ProxyTarget {
|
ProxyTarget {
|
||||||
filter: bind.net.clone().into_dyn(),
|
filter: gw_filter.clone().into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
connect_ssl: connect_ssl
|
connect_ssl: connect_ssl
|
||||||
.clone()
|
.clone()
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
}, // TODO: allow public traffic?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for address in host.addresses() {
|
for HostAddress {
|
||||||
match address {
|
|
||||||
HostAddress::Domain {
|
|
||||||
address,
|
address,
|
||||||
public,
|
public,
|
||||||
private,
|
private,
|
||||||
} => {
|
} in host_addresses.iter().cloned()
|
||||||
|
{
|
||||||
if hostnames.insert(address.clone()) {
|
if hostnames.insert(address.clone()) {
|
||||||
let address = Some(address.clone());
|
let address = Some(address.clone());
|
||||||
if ssl.preferred_external_port == 443 {
|
if ssl.preferred_external_port == 443 {
|
||||||
@@ -323,8 +344,7 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: public.acme.clone(),
|
acme: public.acme.clone(),
|
||||||
addr,
|
addr,
|
||||||
add_x_forwarded_headers: ssl
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
.add_x_forwarded_headers,
|
|
||||||
connect_ssl: connect_ssl
|
connect_ssl: connect_ssl
|
||||||
.clone()
|
.clone()
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
@@ -352,8 +372,7 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: public.acme.clone(),
|
acme: public.acme.clone(),
|
||||||
addr,
|
addr,
|
||||||
add_x_forwarded_headers: ssl
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
.add_x_forwarded_headers,
|
|
||||||
connect_ssl: connect_ssl
|
connect_ssl: connect_ssl
|
||||||
.clone()
|
.clone()
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
@@ -370,8 +389,7 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
add_x_forwarded_headers: ssl
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
.add_x_forwarded_headers,
|
|
||||||
connect_ssl: connect_ssl
|
connect_ssl: connect_ssl
|
||||||
.clone()
|
.clone()
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
@@ -392,15 +410,13 @@ impl NetServiceData {
|
|||||||
)
|
)
|
||||||
.into_dyn()
|
.into_dyn()
|
||||||
} else {
|
} else {
|
||||||
IdFilter(public.gateway.clone())
|
IdFilter(public.gateway.clone()).into_dyn()
|
||||||
.into_dyn()
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: public.acme.clone(),
|
acme: public.acme.clone(),
|
||||||
addr,
|
addr,
|
||||||
add_x_forwarded_headers: ssl
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
.add_x_forwarded_headers,
|
|
||||||
connect_ssl: connect_ssl
|
connect_ssl: connect_ssl
|
||||||
.clone()
|
.clone()
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
@@ -417,8 +433,7 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
add_x_forwarded_headers: ssl
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
.add_x_forwarded_headers,
|
|
||||||
connect_ssl: connect_ssl
|
connect_ssl: connect_ssl
|
||||||
.clone()
|
.clone()
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
@@ -429,8 +444,6 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if bind
|
if bind
|
||||||
.options
|
.options
|
||||||
.secure
|
.secure
|
||||||
@@ -451,8 +464,7 @@ impl NetServiceData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut bind_hostname_info: Vec<HostnameInfo> =
|
bind.addresses.possible.clear();
|
||||||
hostname_info.remove(port).unwrap_or_default();
|
|
||||||
for (gateway_id, info) in net_ifaces
|
for (gateway_id, info) in net_ifaces
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, info)| {
|
.filter(|(_, info)| {
|
||||||
@@ -481,7 +493,7 @@ impl NetServiceData {
|
|||||||
i.device_type != Some(NetworkInterfaceType::Wireguard)
|
i.device_type != Some(NetworkInterfaceType::Wireguard)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
bind_hostname_info.push(HostnameInfo {
|
bind.addresses.possible.insert(HostnameInfo {
|
||||||
gateway: gateway.clone(),
|
gateway: gateway.clone(),
|
||||||
public: false,
|
public: false,
|
||||||
hostname: IpHostname::Local {
|
hostname: IpHostname::Local {
|
||||||
@@ -494,19 +506,17 @@ impl NetServiceData {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for address in host.addresses() {
|
for HostAddress {
|
||||||
if let HostAddress::Domain {
|
|
||||||
address,
|
address,
|
||||||
public,
|
public,
|
||||||
private,
|
private,
|
||||||
} = address
|
} in host_addresses.iter().cloned()
|
||||||
{
|
{
|
||||||
if public.is_none() {
|
if public.is_none() {
|
||||||
private_dns.insert(address.clone());
|
private_dns.insert(address.clone());
|
||||||
}
|
}
|
||||||
let private = private && !info.public();
|
let private = private && !info.public();
|
||||||
let public =
|
let public = public.as_ref().map_or(false, |p| &p.gateway == gateway_id);
|
||||||
public.as_ref().map_or(false, |p| &p.gateway == gateway_id);
|
|
||||||
if public || private {
|
if public || private {
|
||||||
if bind
|
if bind
|
||||||
.options
|
.options
|
||||||
@@ -514,7 +524,7 @@ impl NetServiceData {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |ssl| ssl.preferred_external_port == 443)
|
.map_or(false, |ssl| ssl.preferred_external_port == 443)
|
||||||
{
|
{
|
||||||
bind_hostname_info.push(HostnameInfo {
|
bind.addresses.possible.insert(HostnameInfo {
|
||||||
gateway: gateway.clone(),
|
gateway: gateway.clone(),
|
||||||
public,
|
public,
|
||||||
hostname: IpHostname::Domain {
|
hostname: IpHostname::Domain {
|
||||||
@@ -524,7 +534,7 @@ impl NetServiceData {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
bind_hostname_info.push(HostnameInfo {
|
bind.addresses.possible.insert(HostnameInfo {
|
||||||
gateway: gateway.clone(),
|
gateway: gateway.clone(),
|
||||||
public,
|
public,
|
||||||
hostname: IpHostname::Domain {
|
hostname: IpHostname::Domain {
|
||||||
@@ -536,11 +546,10 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(ip_info) = &info.ip_info {
|
if let Some(ip_info) = &info.ip_info {
|
||||||
let public = info.public();
|
let public = info.public();
|
||||||
if let Some(wan_ip) = ip_info.wan_ip {
|
if let Some(wan_ip) = ip_info.wan_ip {
|
||||||
bind_hostname_info.push(HostnameInfo {
|
bind.addresses.possible.insert(HostnameInfo {
|
||||||
gateway: gateway.clone(),
|
gateway: gateway.clone(),
|
||||||
public: true,
|
public: true,
|
||||||
hostname: IpHostname::Ipv4 {
|
hostname: IpHostname::Ipv4 {
|
||||||
@@ -554,7 +563,7 @@ impl NetServiceData {
|
|||||||
match ipnet {
|
match ipnet {
|
||||||
IpNet::V4(net) => {
|
IpNet::V4(net) => {
|
||||||
if !public {
|
if !public {
|
||||||
bind_hostname_info.push(HostnameInfo {
|
bind.addresses.possible.insert(HostnameInfo {
|
||||||
gateway: gateway.clone(),
|
gateway: gateway.clone(),
|
||||||
public,
|
public,
|
||||||
hostname: IpHostname::Ipv4 {
|
hostname: IpHostname::Ipv4 {
|
||||||
@@ -566,7 +575,7 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IpNet::V6(net) => {
|
IpNet::V6(net) => {
|
||||||
bind_hostname_info.push(HostnameInfo {
|
bind.addresses.possible.insert(HostnameInfo {
|
||||||
gateway: gateway.clone(),
|
gateway: gateway.clone(),
|
||||||
public: public && !ipv6_is_local(net.addr()),
|
public: public && !ipv6_is_local(net.addr()),
|
||||||
hostname: IpHostname::Ipv6 {
|
hostname: IpHostname::Ipv6 {
|
||||||
@@ -581,8 +590,6 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hostname_info.insert(*port, bind_hostname_info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let all = binds
|
let all = binds
|
||||||
@@ -673,11 +680,28 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
ctrl.dns.gc_private_domains(&rm)?;
|
ctrl.dns.gc_private_domains(&rm)?;
|
||||||
|
|
||||||
|
let res = ctrl
|
||||||
|
.db
|
||||||
|
.mutate(|db| {
|
||||||
|
let bindings = host_for(db, self.id.as_ref(), &id)?.as_bindings_mut();
|
||||||
|
for (port, bind) in host.bindings.0 {
|
||||||
|
if let Some(b) = bindings.as_idx_mut(&port) {
|
||||||
|
b.as_addresses_mut()
|
||||||
|
.as_possible_mut()
|
||||||
|
.ser(&bind.addresses.possible)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
res.result?;
|
||||||
if let Some(pkg_id) = self.id.as_ref() {
|
if let Some(pkg_id) = self.id.as_ref() {
|
||||||
|
if res.revision.is_some() {
|
||||||
if let Some(cbs) = ctrl.callbacks.get_host_info(&(pkg_id.clone(), id)) {
|
if let Some(cbs) = ctrl.callbacks.get_host_info(&(pkg_id.clone(), id)) {
|
||||||
cbs.call(vector![]).await?;
|
cbs.call(vector![]).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::{GatewayId, HostId, ServiceInterfaceId};
|
use crate::{GatewayId, HostId, ServiceInterfaceId};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct HostnameInfo {
|
pub struct HostnameInfo {
|
||||||
@@ -20,7 +20,7 @@ impl HostnameInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GatewayInfo {
|
pub struct GatewayInfo {
|
||||||
@@ -29,7 +29,7 @@ pub struct GatewayInfo {
|
|||||||
pub public: bool,
|
pub public: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, 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")]
|
||||||
|
|||||||
@@ -175,8 +175,12 @@ pub async fn remove_tunnel(
|
|||||||
let host = host?;
|
let host = host?;
|
||||||
host.as_bindings_mut().mutate(|b| {
|
host.as_bindings_mut().mutate(|b| {
|
||||||
Ok(b.values_mut().for_each(|v| {
|
Ok(b.values_mut().for_each(|v| {
|
||||||
v.net.private_disabled.remove(&id);
|
v.addresses
|
||||||
v.net.public_enabled.remove(&id);
|
.private_disabled
|
||||||
|
.retain(|h| h.gateway.id != id);
|
||||||
|
v.addresses
|
||||||
|
.public_enabled
|
||||||
|
.retain(|h| h.gateway.id != id);
|
||||||
}))
|
}))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ pub async fn get_ssl_certificate(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(m.as_private_domains().de()?)
|
.chain(m.as_private_domains().de()?)
|
||||||
.chain(
|
.chain(
|
||||||
m.as_hostname_info()
|
m.as_bindings()
|
||||||
.de()?
|
.de()?
|
||||||
.values()
|
.values()
|
||||||
.flatten()
|
.flat_map(|b| b.addresses.possible.iter().cloned())
|
||||||
.map(|h| h.to_san_hostname()),
|
.map(|h| h.to_san_hostname()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<InternedString>>())
|
.collect::<Vec<InternedString>>())
|
||||||
@@ -184,10 +184,10 @@ pub async fn get_ssl_key(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(m.as_private_domains().de()?)
|
.chain(m.as_private_domains().de()?)
|
||||||
.chain(
|
.chain(
|
||||||
m.as_hostname_info()
|
m.as_bindings()
|
||||||
.de()?
|
.de()?
|
||||||
.values()
|
.values()
|
||||||
.flatten()
|
.flat_map(|b| b.addresses.possible.iter().cloned())
|
||||||
.map(|h| h.to_san_hostname()),
|
.map(|h| h.to_san_hostname()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<InternedString>>())
|
.collect::<Vec<InternedString>>())
|
||||||
|
|||||||
@@ -57,29 +57,6 @@ impl VersionT for Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove onion entries from hostnameInfo in server host
|
|
||||||
migrate_hostname_info(
|
|
||||||
db.get_mut("public")
|
|
||||||
.and_then(|p| p.get_mut("serverInfo"))
|
|
||||||
.and_then(|s| s.get_mut("network"))
|
|
||||||
.and_then(|n| n.get_mut("host")),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove onion entries from hostnameInfo in all package hosts
|
|
||||||
if let Some(packages) = db
|
|
||||||
.get_mut("public")
|
|
||||||
.and_then(|p| p.get_mut("packageData"))
|
|
||||||
.and_then(|p| p.as_object_mut())
|
|
||||||
{
|
|
||||||
for (_, package) in packages.iter_mut() {
|
|
||||||
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
|
|
||||||
for (_, host) in hosts.iter_mut() {
|
|
||||||
migrate_hostname_info(Some(host));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove onion store from private keyStore
|
// Remove onion store from private keyStore
|
||||||
if let Some(key_store) = db
|
if let Some(key_store) = db
|
||||||
.get_mut("private")
|
.get_mut("private")
|
||||||
@@ -89,6 +66,29 @@ impl VersionT for Version {
|
|||||||
key_store.remove("onion");
|
key_store.remove("onion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate server host: remove hostnameInfo, add addresses to bindings, clean net
|
||||||
|
migrate_host(
|
||||||
|
db.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("serverInfo"))
|
||||||
|
.and_then(|s| s.get_mut("network"))
|
||||||
|
.and_then(|n| n.get_mut("host")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Migrate all package hosts
|
||||||
|
if let Some(packages) = db
|
||||||
|
.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("packageData"))
|
||||||
|
.and_then(|p| p.as_object_mut())
|
||||||
|
{
|
||||||
|
for (_, package) in packages.iter_mut() {
|
||||||
|
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
|
||||||
|
for (_, host) in hosts.iter_mut() {
|
||||||
|
migrate_host(Some(host));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||||
@@ -96,20 +96,35 @@ impl VersionT for Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn migrate_hostname_info(host: Option<&mut Value>) {
|
fn migrate_host(host: Option<&mut Value>) {
|
||||||
if let Some(hostname_info) = host
|
let Some(host) = host.and_then(|h| h.as_object_mut()) else {
|
||||||
.and_then(|h| h.get_mut("hostnameInfo"))
|
return;
|
||||||
.and_then(|h| h.as_object_mut())
|
};
|
||||||
{
|
|
||||||
for (_, infos) in hostname_info.iter_mut() {
|
// Remove hostnameInfo from host
|
||||||
if let Some(arr) = infos.as_array_mut() {
|
host.remove("hostnameInfo");
|
||||||
// Remove onion entries
|
|
||||||
arr.retain(|info| info.get("kind").and_then(|k| k.as_str()) != Some("onion"));
|
// For each binding: add "addresses" field, remove gateway-level fields from "net"
|
||||||
// Strip "kind" field from remaining entries (HostnameInfo flattened from enum to struct)
|
if let Some(bindings) = host.get_mut("bindings").and_then(|b| b.as_object_mut()) {
|
||||||
for info in arr.iter_mut() {
|
for (_, binding) in bindings.iter_mut() {
|
||||||
if let Some(obj) = info.as_object_mut() {
|
if let Some(binding_obj) = binding.as_object_mut() {
|
||||||
obj.remove("kind");
|
// Add addresses if not present
|
||||||
|
if !binding_obj.contains_key("addresses") {
|
||||||
|
binding_obj.insert(
|
||||||
|
"addresses".into(),
|
||||||
|
serde_json::json!({
|
||||||
|
"privateDisabled": [],
|
||||||
|
"publicEnabled": [],
|
||||||
|
"possible": []
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove gateway-level privateDisabled/publicEnabled from net
|
||||||
|
if let Some(net) = binding_obj.get_mut("net").and_then(|n| n.as_object_mut()) {
|
||||||
|
net.remove("privateDisabled");
|
||||||
|
net.remove("publicEnabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +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 { BindOptions } from './BindOptions'
|
||||||
|
import type { DerivedAddressInfo } from './DerivedAddressInfo'
|
||||||
import type { NetInfo } from './NetInfo'
|
import type { NetInfo } from './NetInfo'
|
||||||
|
|
||||||
export type BindInfo = { enabled: boolean; options: BindOptions; net: NetInfo }
|
export type BindInfo = {
|
||||||
|
enabled: boolean
|
||||||
|
options: BindOptions
|
||||||
|
net: NetInfo
|
||||||
|
addresses: DerivedAddressInfo
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { GatewayId } from './GatewayId'
|
|
||||||
|
|
||||||
export type BindingGatewaySetEnabledParams = {
|
export type BindingSetAddressEnabledParams = {
|
||||||
internalPort: number
|
internalPort: number
|
||||||
gateway: GatewayId
|
address: string
|
||||||
enabled: boolean | null
|
enabled: boolean | null
|
||||||
}
|
}
|
||||||
4
sdk/base/lib/osBindings/Bindings.ts
Normal file
4
sdk/base/lib/osBindings/Bindings.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { BindInfo } from './BindInfo'
|
||||||
|
|
||||||
|
export type Bindings = { [key: number]: BindInfo }
|
||||||
17
sdk/base/lib/osBindings/DerivedAddressInfo.ts
Normal file
17
sdk/base/lib/osBindings/DerivedAddressInfo.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { HostnameInfo } from './HostnameInfo'
|
||||||
|
|
||||||
|
export type DerivedAddressInfo = {
|
||||||
|
/**
|
||||||
|
* User-controlled: private-gateway addresses the user has disabled
|
||||||
|
*/
|
||||||
|
privateDisabled: Array<HostnameInfo>
|
||||||
|
/**
|
||||||
|
* User-controlled: public-gateway addresses the user has enabled
|
||||||
|
*/
|
||||||
|
publicEnabled: Array<HostnameInfo>
|
||||||
|
/**
|
||||||
|
* COMPUTED: NetServiceData::update — all possible addresses for this binding
|
||||||
|
*/
|
||||||
|
possible: Array<HostnameInfo>
|
||||||
|
}
|
||||||
@@ -1,14 +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 { BindInfo } from './BindInfo'
|
import type { Bindings } from './Bindings'
|
||||||
import type { HostnameInfo } from './HostnameInfo'
|
|
||||||
import type { PublicDomainConfig } from './PublicDomainConfig'
|
import type { PublicDomainConfig } from './PublicDomainConfig'
|
||||||
|
|
||||||
export type Host = {
|
export type Host = {
|
||||||
bindings: { [key: number]: BindInfo }
|
bindings: Bindings
|
||||||
publicDomains: { [key: string]: PublicDomainConfig }
|
publicDomains: { [key: string]: PublicDomainConfig }
|
||||||
privateDomains: Array<string>
|
privateDomains: Array<string>
|
||||||
/**
|
|
||||||
* COMPUTED: NetService::update
|
|
||||||
*/
|
|
||||||
hostnameInfo: { [key: number]: Array<HostnameInfo> }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +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.
|
||||||
import type { GatewayId } from './GatewayId'
|
|
||||||
|
|
||||||
export type NetInfo = {
|
export type NetInfo = {
|
||||||
privateDisabled: Array<GatewayId>
|
|
||||||
publicEnabled: Array<GatewayId>
|
|
||||||
assignedPort: number | null
|
assignedPort: number | null
|
||||||
assignedSslPort: number | null
|
assignedSslPort: number | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ export { BackupTargetFS } from './BackupTargetFS'
|
|||||||
export { Base64 } from './Base64'
|
export { Base64 } from './Base64'
|
||||||
export { BindId } from './BindId'
|
export { BindId } from './BindId'
|
||||||
export { BindInfo } from './BindInfo'
|
export { BindInfo } from './BindInfo'
|
||||||
export { BindingGatewaySetEnabledParams } from './BindingGatewaySetEnabledParams'
|
export { BindingSetAddressEnabledParams } from './BindingSetAddressEnabledParams'
|
||||||
|
export { Bindings } from './Bindings'
|
||||||
export { BindOptions } from './BindOptions'
|
export { BindOptions } from './BindOptions'
|
||||||
export { BindParams } from './BindParams'
|
export { BindParams } from './BindParams'
|
||||||
export { Blake3Commitment } from './Blake3Commitment'
|
export { Blake3Commitment } from './Blake3Commitment'
|
||||||
@@ -64,6 +65,7 @@ export { Dependencies } from './Dependencies'
|
|||||||
export { DependencyMetadata } from './DependencyMetadata'
|
export { DependencyMetadata } from './DependencyMetadata'
|
||||||
export { DependencyRequirement } from './DependencyRequirement'
|
export { DependencyRequirement } from './DependencyRequirement'
|
||||||
export { DepInfo } from './DepInfo'
|
export { DepInfo } from './DepInfo'
|
||||||
|
export { DerivedAddressInfo } from './DerivedAddressInfo'
|
||||||
export { Description } from './Description'
|
export { Description } from './Description'
|
||||||
export { DesiredStatus } from './DesiredStatus'
|
export { DesiredStatus } from './DesiredStatus'
|
||||||
export { DestroySubcontainerFsParams } from './DestroySubcontainerFsParams'
|
export { DestroySubcontainerFsParams } from './DestroySubcontainerFsParams'
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { PackageId, ServiceInterfaceId, ServiceInterfaceType } from '../types'
|
import { PackageId, ServiceInterfaceId, ServiceInterfaceType } from '../types'
|
||||||
import { knownProtocols } from '../interfaces/Host'
|
import { knownProtocols } from '../interfaces/Host'
|
||||||
import { AddressInfo, Host, Hostname, HostnameInfo } from '../types'
|
import {
|
||||||
|
AddressInfo,
|
||||||
|
DerivedAddressInfo,
|
||||||
|
Host,
|
||||||
|
Hostname,
|
||||||
|
HostnameInfo,
|
||||||
|
} from '../types'
|
||||||
import { Effects } from '../Effects'
|
import { Effects } from '../Effects'
|
||||||
import { DropGenerator, DropPromise } from './Drop'
|
import { DropGenerator, DropPromise } from './Drop'
|
||||||
import { IpAddress, IPV6_LINK_LOCAL } from './ip'
|
import { IpAddress, IPV6_LINK_LOCAL } from './ip'
|
||||||
@@ -220,6 +226,14 @@ function filterRec(
|
|||||||
return hostnames
|
return hostnames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
|
||||||
|
return addr.possible.filter((h) =>
|
||||||
|
h.public
|
||||||
|
? addr.publicEnabled.some((e) => deepEqual(e, h))
|
||||||
|
: !addr.privateDisabled.some((d) => deepEqual(d, h)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const filledAddress = (
|
export const filledAddress = (
|
||||||
host: Host,
|
host: Host,
|
||||||
addressInfo: AddressInfo,
|
addressInfo: AddressInfo,
|
||||||
@@ -229,7 +243,8 @@ export const filledAddress = (
|
|||||||
const u = toUrls(h)
|
const u = toUrls(h)
|
||||||
return [u.url, u.sslUrl].filter((u) => u !== null)
|
return [u.url, u.sslUrl].filter((u) => u !== null)
|
||||||
}
|
}
|
||||||
const hostnames = host.hostnameInfo[addressInfo.internalPort] ?? []
|
const binding = host.bindings[addressInfo.internalPort]
|
||||||
|
const hostnames = binding ? enabledAddresses(binding.addresses) : []
|
||||||
|
|
||||||
function filledAddressFromHostnames<F extends Filter>(
|
function filledAddressFromHostnames<F extends Filter>(
|
||||||
hostnames: HostnameInfo[],
|
hostnames: HostnameInfo[],
|
||||||
|
|||||||
@@ -83,32 +83,8 @@ export class InterfaceGatewaysComponent {
|
|||||||
|
|
||||||
readonly gateways = input.required<InterfaceGateway[] | undefined>()
|
readonly gateways = input.required<InterfaceGateway[] | undefined>()
|
||||||
|
|
||||||
async onToggle(gateway: InterfaceGateway) {
|
async onToggle(_gateway: InterfaceGateway) {
|
||||||
const addressInfo = this.interface.value()!.addressInfo
|
// TODO: Replace with per-address toggle UI (Section 6 frontend overhaul).
|
||||||
const pkgId = this.interface.packageId()
|
// Gateway-level toggle replaced by set-address-enabled RPC.
|
||||||
|
|
||||||
const loader = this.loader.open().subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (pkgId) {
|
|
||||||
await this.api.pkgBindingToggleGateway({
|
|
||||||
gateway: gateway.id,
|
|
||||||
enabled: !gateway.enabled,
|
|
||||||
internalPort: addressInfo.internalPort,
|
|
||||||
host: addressInfo.hostId,
|
|
||||||
package: pkgId,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.api.serverBindingToggleGateway({
|
|
||||||
gateway: gateway.id,
|
|
||||||
enabled: !gateway.enabled,
|
|
||||||
internalPort: 80,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,10 +252,16 @@ export class InterfaceService {
|
|||||||
serviceInterface: T.ServiceInterface,
|
serviceInterface: T.ServiceInterface,
|
||||||
host: T.Host,
|
host: T.Host,
|
||||||
): T.HostnameInfo[] {
|
): T.HostnameInfo[] {
|
||||||
let hostnameInfo =
|
const binding =
|
||||||
host.hostnameInfo[serviceInterface.addressInfo.internalPort]
|
host.bindings[serviceInterface.addressInfo.internalPort]
|
||||||
return (
|
if (!binding) return []
|
||||||
hostnameInfo?.filter(
|
const addr = binding.addresses
|
||||||
|
const enabled = addr.possible.filter(h =>
|
||||||
|
h.public
|
||||||
|
? addr.publicEnabled.some(e => utils.deepEqual(e, h))
|
||||||
|
: !addr.privateDisabled.some(d => utils.deepEqual(d, h)),
|
||||||
|
)
|
||||||
|
return enabled.filter(
|
||||||
h =>
|
h =>
|
||||||
this.config.accessType === 'localhost' ||
|
this.config.accessType === 'localhost' ||
|
||||||
!(
|
!(
|
||||||
@@ -263,7 +269,6 @@ export class InterfaceService {
|
|||||||
utils.IPV6_LINK_LOCAL.contains(h.hostname.value)) ||
|
utils.IPV6_LINK_LOCAL.contains(h.hostname.value)) ||
|
||||||
h.gateway.id === 'lo'
|
h.gateway.id === 'lo'
|
||||||
),
|
),
|
||||||
) || []
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ export default class ServiceInterfaceRoute {
|
|||||||
gateways.map(g => ({
|
gateways.map(g => ({
|
||||||
enabled:
|
enabled:
|
||||||
(g.public
|
(g.public
|
||||||
? binding?.net.publicEnabled.includes(g.id)
|
? binding?.addresses.publicEnabled.some(a => a.gateway.id === g.id)
|
||||||
: !binding?.net.privateDisabled.includes(g.id)) ?? false,
|
: !binding?.addresses.privateDisabled.some(a => a.gateway.id === g.id)) ?? false,
|
||||||
...g,
|
...g,
|
||||||
})) || [],
|
})) || [],
|
||||||
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ export default class StartOsUiComponent {
|
|||||||
gateways: gateways.map(g => ({
|
gateways: gateways.map(g => ({
|
||||||
enabled:
|
enabled:
|
||||||
(g.public
|
(g.public
|
||||||
? binding?.net.publicEnabled.includes(g.id)
|
? binding?.addresses.publicEnabled.some(a => a.gateway.id === g.id)
|
||||||
: !binding?.net.privateDisabled.includes(g.id)) ?? false,
|
: !binding?.addresses.privateDisabled.some(a => a.gateway.id === g.id)) ?? false,
|
||||||
...g,
|
...g,
|
||||||
})),
|
})),
|
||||||
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
||||||
|
|||||||
@@ -2126,22 +2126,12 @@ export namespace Mock {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: 80,
|
assignedPort: 80,
|
||||||
assignedSslPort: 443,
|
assignedSslPort: 443,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
},
|
publicEnabled: [],
|
||||||
options: {
|
possible: [
|
||||||
addSsl: null,
|
|
||||||
preferredExternalPort: 443,
|
|
||||||
secure: { ssl: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
publicDomains: {},
|
|
||||||
privateDomains: [],
|
|
||||||
hostnameInfo: {
|
|
||||||
80: [
|
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -2152,7 +2142,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -2163,7 +2152,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -2174,7 +2162,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -2185,7 +2172,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -2197,7 +2183,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -2210,6 +2195,15 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
options: {
|
||||||
|
addSsl: null,
|
||||||
|
preferredExternalPort: 443,
|
||||||
|
secure: { ssl: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publicDomains: {},
|
||||||
|
privateDomains: [],
|
||||||
},
|
},
|
||||||
bcdefgh: {
|
bcdefgh: {
|
||||||
bindings: {
|
bindings: {
|
||||||
@@ -2218,8 +2212,11 @@ export namespace Mock {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: 8332,
|
assignedPort: 8332,
|
||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
|
publicEnabled: [],
|
||||||
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
addSsl: null,
|
addSsl: null,
|
||||||
@@ -2230,9 +2227,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
hostnameInfo: {
|
|
||||||
8332: [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
cdefghi: {
|
cdefghi: {
|
||||||
bindings: {
|
bindings: {
|
||||||
@@ -2241,8 +2235,11 @@ export namespace Mock {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: 8333,
|
assignedPort: 8333,
|
||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
|
publicEnabled: [],
|
||||||
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
addSsl: null,
|
addSsl: null,
|
||||||
@@ -2253,9 +2250,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
hostnameInfo: {
|
|
||||||
8333: [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
storeExposedDependents: [],
|
storeExposedDependents: [],
|
||||||
|
|||||||
@@ -281,13 +281,13 @@ export namespace RR {
|
|||||||
}
|
}
|
||||||
export type RemoveAcmeRes = null
|
export type RemoveAcmeRes = null
|
||||||
|
|
||||||
export type ServerBindingToggleGatewayReq = {
|
export type ServerBindingSetAddressEnabledReq = {
|
||||||
// server.host.binding.set-gateway-enabled
|
// server.host.binding.set-address-enabled
|
||||||
gateway: T.GatewayId
|
|
||||||
internalPort: 80
|
internalPort: 80
|
||||||
enabled: boolean
|
address: string // JSON-serialized HostnameInfo
|
||||||
|
enabled: boolean | null // null = reset to default
|
||||||
}
|
}
|
||||||
export type ServerBindingToggleGatewayRes = null
|
export type ServerBindingSetAddressEnabledRes = null
|
||||||
|
|
||||||
export type OsUiAddPublicDomainReq = {
|
export type OsUiAddPublicDomainReq = {
|
||||||
// server.host.address.domain.public.add
|
// server.host.address.domain.public.add
|
||||||
@@ -315,16 +315,16 @@ export namespace RR {
|
|||||||
}
|
}
|
||||||
export type OsUiRemovePrivateDomainRes = null
|
export type OsUiRemovePrivateDomainRes = null
|
||||||
|
|
||||||
export type PkgBindingToggleGatewayReq = Omit<
|
export type PkgBindingSetAddressEnabledReq = Omit<
|
||||||
ServerBindingToggleGatewayReq,
|
ServerBindingSetAddressEnabledReq,
|
||||||
'internalPort'
|
'internalPort'
|
||||||
> & {
|
> & {
|
||||||
// package.host.binding.set-gateway-enabled
|
// package.host.binding.set-address-enabled
|
||||||
internalPort: number
|
internalPort: number
|
||||||
package: T.PackageId // string
|
package: T.PackageId // string
|
||||||
host: T.HostId // string
|
host: T.HostId // string
|
||||||
}
|
}
|
||||||
export type PkgBindingToggleGatewayRes = null
|
export type PkgBindingSetAddressEnabledRes = null
|
||||||
|
|
||||||
export type PkgAddPublicDomainReq = OsUiAddPublicDomainReq & {
|
export type PkgAddPublicDomainReq = OsUiAddPublicDomainReq & {
|
||||||
// package.host.address.domain.public.add
|
// package.host.address.domain.public.add
|
||||||
|
|||||||
@@ -336,9 +336,9 @@ export abstract class ApiService {
|
|||||||
|
|
||||||
abstract removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes>
|
abstract removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes>
|
||||||
|
|
||||||
abstract serverBindingToggleGateway(
|
abstract serverBindingSetAddressEnabled(
|
||||||
params: RR.ServerBindingToggleGatewayReq,
|
params: RR.ServerBindingSetAddressEnabledReq,
|
||||||
): Promise<RR.ServerBindingToggleGatewayRes>
|
): Promise<RR.ServerBindingSetAddressEnabledRes>
|
||||||
|
|
||||||
abstract osUiAddPublicDomain(
|
abstract osUiAddPublicDomain(
|
||||||
params: RR.OsUiAddPublicDomainReq,
|
params: RR.OsUiAddPublicDomainReq,
|
||||||
@@ -356,9 +356,9 @@ export abstract class ApiService {
|
|||||||
params: RR.OsUiRemovePrivateDomainReq,
|
params: RR.OsUiRemovePrivateDomainReq,
|
||||||
): Promise<RR.OsUiRemovePrivateDomainRes>
|
): Promise<RR.OsUiRemovePrivateDomainRes>
|
||||||
|
|
||||||
abstract pkgBindingToggleGateway(
|
abstract pkgBindingSetAddressEnabled(
|
||||||
params: RR.PkgBindingToggleGatewayReq,
|
params: RR.PkgBindingSetAddressEnabledReq,
|
||||||
): Promise<RR.PkgBindingToggleGatewayRes>
|
): Promise<RR.PkgBindingSetAddressEnabledRes>
|
||||||
|
|
||||||
abstract pkgAddPublicDomain(
|
abstract pkgAddPublicDomain(
|
||||||
params: RR.PkgAddPublicDomainReq,
|
params: RR.PkgAddPublicDomainReq,
|
||||||
|
|||||||
@@ -607,11 +607,11 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async serverBindingToggleGateway(
|
async serverBindingSetAddressEnabled(
|
||||||
params: RR.ServerBindingToggleGatewayReq,
|
params: RR.ServerBindingSetAddressEnabledReq,
|
||||||
): Promise<RR.ServerBindingToggleGatewayRes> {
|
): Promise<RR.ServerBindingSetAddressEnabledRes> {
|
||||||
return this.rpcRequest({
|
return this.rpcRequest({
|
||||||
method: 'server.host.binding.set-gateway-enabled',
|
method: 'server.host.binding.set-address-enabled',
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -652,11 +652,11 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async pkgBindingToggleGateway(
|
async pkgBindingSetAddressEnabled(
|
||||||
params: RR.PkgBindingToggleGatewayReq,
|
params: RR.PkgBindingSetAddressEnabledReq,
|
||||||
): Promise<RR.PkgBindingToggleGatewayRes> {
|
): Promise<RR.PkgBindingSetAddressEnabledRes> {
|
||||||
return this.rpcRequest({
|
return this.rpcRequest({
|
||||||
method: 'package.host.binding.set-gateway-enabled',
|
method: 'package.host.binding.set-address-enabled',
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1348,20 +1348,12 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async serverBindingToggleGateway(
|
async serverBindingSetAddressEnabled(
|
||||||
params: RR.ServerBindingToggleGatewayReq,
|
params: RR.ServerBindingSetAddressEnabledReq,
|
||||||
): Promise<RR.ServerBindingToggleGatewayRes> {
|
): Promise<RR.ServerBindingSetAddressEnabledRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
const patch = [
|
// Mock: no-op since address enable/disable modifies DerivedAddressInfo sets
|
||||||
{
|
|
||||||
op: PatchOp.REPLACE,
|
|
||||||
path: `/serverInfo/network/host/bindings/${params.internalPort}/net/publicEnabled`,
|
|
||||||
value: params.enabled ? [params.gateway] : [],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch)
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1380,10 +1372,9 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.ADD,
|
op: PatchOp.ADD,
|
||||||
path: `/serverInfo/host/hostnameInfo/80/0`,
|
path: `/serverInfo/network/host/bindings/80/addresses/possible/0`,
|
||||||
value: {
|
value: {
|
||||||
kind: 'ip',
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
gatewayId: 'eth0',
|
|
||||||
public: true,
|
public: true,
|
||||||
hostname: {
|
hostname: {
|
||||||
kind: 'domain',
|
kind: 'domain',
|
||||||
@@ -1412,7 +1403,7 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.REMOVE,
|
op: PatchOp.REMOVE,
|
||||||
path: `/serverInfo/host/hostnameInfo/80/0`,
|
path: `/serverInfo/network/host/bindings/80/addresses/possible/0`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch)
|
this.mockRevision(patch)
|
||||||
@@ -1433,10 +1424,9 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.ADD,
|
op: PatchOp.ADD,
|
||||||
path: `/serverInfo/host/hostnameInfo/80/0`,
|
path: `/serverInfo/network/host/bindings/80/addresses/possible/0`,
|
||||||
value: {
|
value: {
|
||||||
kind: 'ip',
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
gatewayId: 'eth0',
|
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
kind: 'domain',
|
kind: 'domain',
|
||||||
@@ -1466,7 +1456,7 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.REMOVE,
|
op: PatchOp.REMOVE,
|
||||||
path: `/serverInfo/host/hostnameInfo/80/0`,
|
path: `/serverInfo/network/host/bindings/80/addresses/possible/0`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch)
|
this.mockRevision(patch)
|
||||||
@@ -1474,20 +1464,12 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async pkgBindingToggleGateway(
|
async pkgBindingSetAddressEnabled(
|
||||||
params: RR.PkgBindingToggleGatewayReq,
|
params: RR.PkgBindingSetAddressEnabledReq,
|
||||||
): Promise<RR.PkgBindingToggleGatewayRes> {
|
): Promise<RR.PkgBindingSetAddressEnabledRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
const patch = [
|
// Mock: no-op since address enable/disable modifies DerivedAddressInfo sets
|
||||||
{
|
|
||||||
op: PatchOp.REPLACE,
|
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/bindings/${params.internalPort}/net/privateDisabled`,
|
|
||||||
value: params.enabled ? [] : [params.gateway],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch)
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1506,10 +1488,9 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.ADD,
|
op: PatchOp.ADD,
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
|
path: `/packageData/${params.package}/hosts/${params.host}/bindings/80/addresses/possible/0`,
|
||||||
value: {
|
value: {
|
||||||
kind: 'ip',
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
gatewayId: 'eth0',
|
|
||||||
public: true,
|
public: true,
|
||||||
hostname: {
|
hostname: {
|
||||||
kind: 'domain',
|
kind: 'domain',
|
||||||
@@ -1538,7 +1519,7 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.REMOVE,
|
op: PatchOp.REMOVE,
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
|
path: `/packageData/${params.package}/hosts/${params.host}/bindings/80/addresses/possible/0`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch)
|
this.mockRevision(patch)
|
||||||
@@ -1559,10 +1540,9 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.ADD,
|
op: PatchOp.ADD,
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
|
path: `/packageData/${params.package}/hosts/${params.host}/bindings/80/addresses/possible/0`,
|
||||||
value: {
|
value: {
|
||||||
kind: 'ip',
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
gatewayId: 'eth0',
|
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
kind: 'domain',
|
kind: 'domain',
|
||||||
@@ -1592,7 +1572,7 @@ export class MockApiService extends ApiService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: PatchOp.REMOVE,
|
op: PatchOp.REMOVE,
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
|
path: `/packageData/${params.package}/hosts/${params.host}/bindings/80/addresses/possible/0`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch)
|
this.mockRevision(patch)
|
||||||
|
|||||||
@@ -38,8 +38,74 @@ export const mockPatchData: DataModel = {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: null,
|
assignedPort: null,
|
||||||
assignedSslPort: 443,
|
assignedSslPort: 443,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
|
publicEnabled: [],
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
|
public: false,
|
||||||
|
hostname: {
|
||||||
|
kind: 'local',
|
||||||
|
value: 'adjective-noun.local',
|
||||||
|
port: null,
|
||||||
|
sslPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
|
public: false,
|
||||||
|
hostname: {
|
||||||
|
kind: 'local',
|
||||||
|
value: 'adjective-noun.local',
|
||||||
|
port: null,
|
||||||
|
sslPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
|
public: false,
|
||||||
|
hostname: {
|
||||||
|
kind: 'ipv4',
|
||||||
|
value: '10.0.0.1',
|
||||||
|
port: null,
|
||||||
|
sslPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
|
public: false,
|
||||||
|
hostname: {
|
||||||
|
kind: 'ipv4',
|
||||||
|
value: '10.0.0.2',
|
||||||
|
port: null,
|
||||||
|
sslPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
|
public: false,
|
||||||
|
hostname: {
|
||||||
|
kind: 'ipv6',
|
||||||
|
value: 'fe80::cd00:0000:0cde:1257:0000:211e:72cd',
|
||||||
|
scopeId: 2,
|
||||||
|
port: null,
|
||||||
|
sslPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
|
public: false,
|
||||||
|
hostname: {
|
||||||
|
kind: 'ipv6',
|
||||||
|
value: 'fe80::cd00:0000:0cde:1257:0000:211e:1234',
|
||||||
|
scopeId: 3,
|
||||||
|
port: null,
|
||||||
|
sslPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
preferredExternalPort: 80,
|
preferredExternalPort: 80,
|
||||||
@@ -54,78 +120,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
hostnameInfo: {
|
|
||||||
80: [
|
|
||||||
{
|
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
|
||||||
public: false,
|
|
||||||
hostname: {
|
|
||||||
kind: 'local',
|
|
||||||
value: 'adjective-noun.local',
|
|
||||||
port: null,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
|
||||||
public: false,
|
|
||||||
hostname: {
|
|
||||||
kind: 'local',
|
|
||||||
value: 'adjective-noun.local',
|
|
||||||
port: null,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
|
||||||
public: false,
|
|
||||||
hostname: {
|
|
||||||
kind: 'ipv4',
|
|
||||||
value: '10.0.0.1',
|
|
||||||
port: null,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
|
||||||
public: false,
|
|
||||||
hostname: {
|
|
||||||
kind: 'ipv4',
|
|
||||||
value: '10.0.0.2',
|
|
||||||
port: null,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
|
||||||
public: false,
|
|
||||||
hostname: {
|
|
||||||
kind: 'ipv6',
|
|
||||||
value: 'fe80::cd00:0000:0cde:1257:0000:211e:72cd',
|
|
||||||
scopeId: 2,
|
|
||||||
port: null,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
|
||||||
public: false,
|
|
||||||
hostname: {
|
|
||||||
kind: 'ipv6',
|
|
||||||
value: 'fe80::cd00:0000:0cde:1257:0000:211e:1234',
|
|
||||||
scopeId: 3,
|
|
||||||
port: null,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
gateways: {
|
gateways: {
|
||||||
eth0: {
|
eth0: {
|
||||||
@@ -503,22 +497,12 @@ export const mockPatchData: DataModel = {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: 80,
|
assignedPort: 80,
|
||||||
assignedSslPort: 443,
|
assignedSslPort: 443,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
},
|
publicEnabled: [],
|
||||||
options: {
|
possible: [
|
||||||
addSsl: null,
|
|
||||||
preferredExternalPort: 443,
|
|
||||||
secure: { ssl: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
publicDomains: {},
|
|
||||||
privateDomains: [],
|
|
||||||
hostnameInfo: {
|
|
||||||
80: [
|
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -529,7 +513,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -540,7 +523,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -551,7 +533,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -562,7 +543,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -574,7 +554,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'ip',
|
|
||||||
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
gateway: { id: 'wlan0', name: 'Wireless', public: false },
|
||||||
public: false,
|
public: false,
|
||||||
hostname: {
|
hostname: {
|
||||||
@@ -587,6 +566,15 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
options: {
|
||||||
|
addSsl: null,
|
||||||
|
preferredExternalPort: 443,
|
||||||
|
secure: { ssl: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publicDomains: {},
|
||||||
|
privateDomains: [],
|
||||||
},
|
},
|
||||||
bcdefgh: {
|
bcdefgh: {
|
||||||
bindings: {
|
bindings: {
|
||||||
@@ -595,8 +583,11 @@ export const mockPatchData: DataModel = {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: 8332,
|
assignedPort: 8332,
|
||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
|
publicEnabled: [],
|
||||||
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
addSsl: null,
|
addSsl: null,
|
||||||
@@ -607,9 +598,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
hostnameInfo: {
|
|
||||||
8332: [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
cdefghi: {
|
cdefghi: {
|
||||||
bindings: {
|
bindings: {
|
||||||
@@ -618,8 +606,11 @@ export const mockPatchData: DataModel = {
|
|||||||
net: {
|
net: {
|
||||||
assignedPort: 8333,
|
assignedPort: 8333,
|
||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
publicEnabled: [],
|
},
|
||||||
|
addresses: {
|
||||||
privateDisabled: [],
|
privateDisabled: [],
|
||||||
|
publicEnabled: [],
|
||||||
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
addSsl: null,
|
addSsl: null,
|
||||||
@@ -630,9 +621,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
hostnameInfo: {
|
|
||||||
8333: [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
storeExposedDependents: [],
|
storeExposedDependents: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user