Compare commits

..

2 Commits

Author SHA1 Message Date
Matt Hill
dea6bea38c Merge branch 'feat/preferred-port-design' of github.com:Start9Labs/start-os into claude 2026-02-14 08:14:43 -07:00
Matt Hill
25049ce9f1 re-arrange 2026-02-12 08:12:01 -07:00
221 changed files with 10012 additions and 6949 deletions

View File

@@ -1 +1,6 @@
{}
{
"attribution": {
"commit": "",
"pr": ""
}
}

View File

@@ -1,101 +0,0 @@
# Architecture
StartOS is an open-source Linux distribution for running personal servers. It manages discovery, installation, network configuration, backups, and health monitoring of self-hosted services.
## Tech Stack
- Backend: Rust (async/Tokio, Axum web framework)
- Frontend: Angular 20 + TypeScript + TaigaUI
- Container runtime: Node.js/TypeScript with LXC
- Database/State: Patch-DB (git submodule) - storage layer with reactive frontend sync
- API: JSON-RPC via rpc-toolkit (see `core/rpc-toolkit.md`)
- Auth: Password + session cookie, public/private key signatures, local authcookie (see `core/src/middleware/auth/`)
## Project Structure
```bash
/
├── assets/ # Screenshots for README
├── build/ # Auxiliary files and scripts for deployed images
├── container-runtime/ # Node.js program managing package containers
├── core/ # Rust backend: API, daemon (startd), CLI (start-cli)
├── debian/ # Debian package maintainer scripts
├── image-recipe/ # Scripts for building StartOS images
├── patch-db/ # (submodule) Diff-based data store for frontend sync
├── sdk/ # TypeScript SDK for building StartOS packages
└── web/ # Web UIs (Angular)
```
## Components
- **`core/`** — Rust backend daemon. Produces a single binary `startbox` that is symlinked as `startd` (main daemon), `start-cli` (CLI), `start-container` (runs inside LXC containers), `registrybox` (package registry), and `tunnelbox` (VPN/tunnel). Handles all backend logic: RPC API, service lifecycle, networking (DNS, ACME, WiFi, Tor, WireGuard), backups, and database state management. See [core/ARCHITECTURE.md](core/ARCHITECTURE.md).
- **`web/`** — Angular 20 + TypeScript workspace using Taiga UI. Contains three applications (admin UI, setup wizard, VPN management) and two shared libraries (common components/services, marketplace). Communicates with the backend exclusively via JSON-RPC. See [web/ARCHITECTURE.md](web/ARCHITECTURE.md).
- **`container-runtime/`** — Node.js runtime that runs inside each service's LXC container. Loads the service's JavaScript from its S9PK package and manages subcontainers. Communicates with the host daemon via JSON-RPC over Unix socket. See [container-runtime/CLAUDE.md](container-runtime/CLAUDE.md).
- **`sdk/`** — TypeScript SDK for packaging services for StartOS (`@start9labs/start-sdk`). Split into `base/` (core types, ABI definitions, effects interface, consumed by web as `@start9labs/start-sdk-base`) and `package/` (full SDK for service developers, consumed by container-runtime as `@start9labs/start-sdk`).
- **`patch-db/`** — Git submodule providing diff-based state synchronization. Uses CBOR encoding. Backend mutations produce diffs that are pushed to the frontend via WebSocket, enabling reactive UI updates without polling. See [patch-db repo](https://github.com/Start9Labs/patch-db).
## Build Pipeline
Components have a strict dependency chain. Changes flow in one direction:
```
Rust (core/)
→ cargo test exports ts-rs types to core/bindings/
→ rsync copies to sdk/base/lib/osBindings/
→ SDK build produces baseDist/ and dist/
→ web/ consumes baseDist/ (via @start9labs/start-sdk-base)
→ container-runtime/ consumes dist/ (via @start9labs/start-sdk)
```
Key make targets along this chain:
| Step | Command | What it does |
|---|---|---|
| 1 | `cargo check -p start-os` | Verify Rust compiles |
| 2 | `make ts-bindings` | Export ts-rs types → rsync to SDK |
| 3 | `cd sdk && make baseDist dist` | Build SDK packages |
| 4 | `cd web && npm run check` | Type-check Angular projects |
| 5 | `cd container-runtime && npm run check` | Type-check 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.
## Cross-Layer Verification
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
## Data Flow: Backend to Frontend
StartOS uses Patch-DB for reactive state synchronization:
1. The backend mutates state via `db.mutate()`, producing CBOR diffs
2. Diffs are pushed to the frontend over a persistent WebSocket connection
3. The frontend applies diffs to its local state copy and notifies observers
4. Components watch specific database paths via `PatchDB.watch$()`, receiving updates reactively
This means the UI is always eventually consistent with the backend — after any mutating API call, the frontend waits for the corresponding PatchDB diff before resolving, so the UI reflects the result immediately.
## Further Reading
- [core/ARCHITECTURE.md](core/ARCHITECTURE.md) — Rust backend architecture
- [web/ARCHITECTURE.md](web/ARCHITECTURE.md) — Angular frontend architecture
- [container-runtime/CLAUDE.md](container-runtime/CLAUDE.md) — Container runtime details
- [core/rpc-toolkit.md](core/rpc-toolkit.md) — JSON-RPC handler patterns
- [core/s9pk-structure.md](core/s9pk-structure.md) — S9PK package format
- [docs/exver.md](docs/exver.md) — Extended versioning format
- [docs/VERSION_BUMP.md](docs/VERSION_BUMP.md) — Version bumping guide

View File

@@ -2,11 +2,17 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Architecture
## Project Overview
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full system architecture, component map, build pipeline, and cross-layer verification order.
StartOS is an open-source Linux distribution for running personal servers. It manages discovery, installation, network configuration, backups, and health monitoring of self-hosted services.
Each major component has its own `CLAUDE.md` with detailed guidance: `core/`, `web/`, `container-runtime/`, `sdk/`.
**Tech Stack:**
- Backend: Rust (async/Tokio, Axum web framework)
- Frontend: Angular 20 + TypeScript + TaigaUI
- Container runtime: Node.js/TypeScript with LXC
- Database/State: Patch-DB (git submodule) - storage layer with reactive frontend sync
- API: JSON-RPC via rpc-toolkit (see `core/rpc-toolkit.md`)
- Auth: Password + session cookie, public/private key signatures, local authcookie (see `core/src/middleware/auth/`)
## Build & Development
@@ -23,11 +29,33 @@ make update-startbox REMOTE=start9@<ip> # Fastest iteration (binary + UI)
make test-core # Run Rust tests
```
## Operating Rules
### Verifying code changes
- Always verify cross-layer changes using the order described in [ARCHITECTURE.md](ARCHITECTURE.md#cross-layer-verification)
- Check component-level CLAUDE.md files for component-specific conventions
- Follow existing patterns before inventing new ones
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
Each major component has its own `CLAUDE.md` with detailed guidance.
- **`core/`** — Rust backend daemon (startbox, start-cli, start-container, registrybox, tunnelbox)
- **`web/`** — Angular frontend workspace (admin UI, setup wizard, marketplace, shared library)
- **`container-runtime/`** — Node.js runtime managing service containers via JSON-RPC
- **`sdk/`** — TypeScript SDK for packaging services (`@start9labs/start-sdk`)
- **`patch-db/`** — Git submodule providing diff-based state synchronization
## Supplementary Documentation

View File

@@ -6,7 +6,27 @@ This guide is for contributing to the StartOS. If you are interested in packagin
- [Matrix](https://matrix.to/#/#dev-startos:matrix.start9labs.com)
For project structure and system architecture, see [ARCHITECTURE.md](ARCHITECTURE.md).
## Project Structure
```bash
/
├── assets/ # Screenshots for README
├── build/ # Auxiliary files and scripts for deployed images
├── container-runtime/ # Node.js program managing package containers
├── core/ # Rust backend: API, daemon (startd), CLI (start-cli)
├── debian/ # Debian package maintainer scripts
├── image-recipe/ # Scripts for building StartOS images
├── patch-db/ # (submodule) Diff-based data store for frontend sync
├── sdk/ # TypeScript SDK for building StartOS packages
└── web/ # Web UIs (Angular)
```
See component READMEs for details:
- [`core`](core/README.md)
- [`web`](web/README.md)
- [`build`](build/README.md)
- [`patch-db`](https://github.com/Start9Labs/patch-db)
## Environment Setup

80
TODO.md
View File

@@ -21,6 +21,7 @@ Pending tasks for AI agents. Remove items when completed.
### Design
**Key distinction**: There are two separate concepts for SSL port usage:
1. **Port ownership** (`assigned_ssl_port`) — A port exclusively owned by a binding, allocated from
`AvailablePorts`. Used for server hostnames (`.local`, mDNS, etc.) and iptables forwards.
2. **Domain SSL port** — The port used for domain-based vhost entries. A binding does NOT need to own
@@ -61,6 +62,7 @@ Pending tasks for AI agents. Remove items when completed.
`server.host.binding` and `package.host.binding`).
**How disabling works per address type** (enforcement deferred to Section 3):
- **WAN/LAN IP:port**: Will be enforced via **source-IP gating** in the vhost layer (Section 3).
- **Hostname-based addresses** (`.local`, domains): Disabled by **not creating the vhost/SNI
entry** for that hostname.
@@ -71,7 +73,7 @@ Pending tasks for AI agents. Remove items when completed.
`net_controller.rs`) creates a bespoke dual-vhost setup: port 5443 for private-only access and port
443 for public (or public+private). This exists because both public and private traffic arrive on the
same port 443 listener, and the current `InterfaceFilter`/`PublicFilter` model distinguishes
public/private by which _network interface_ the connection arrived on — which doesn't work when both
public/private by which *network interface* the connection arrived on — which doesn't work when both
traffic types share a listener.
**Solution**: Determine public vs private based on **source IP** at the vhost level. Traffic arriving
@@ -79,6 +81,7 @@ Pending tasks for AI agents. Remove items when completed.
anything from the gateway is potentially public). Traffic from LAN IPs is private.
This applies to **all** vhost targets, not just port 443:
- **Add a `public` field to `ProxyTarget`** (or an enum: `Public`, `Private`, `Both`) indicating
what traffic this target accepts, derived from the binding's user-controlled `public` field.
- **Modify `VHostTarget::filter()`** (`vhost.rs:342`): Instead of (or in addition to) checking the
@@ -106,6 +109,7 @@ Pending tasks for AI agents. Remove items when completed.
#### 5. Simplify `update()` Domain Vhost Logic (`net_controller.rs`)
With source-IP gating in the vhost controller:
- **Remove the `== 443` special case** and the 5443 secondary vhost.
- For **server hostnames** (`.local`, mDNS, embassy, startos, localhost): use `assigned_ssl_port`
(the port the binding owns).
@@ -118,18 +122,60 @@ Pending tasks for AI agents. Remove items when completed.
`ssl_port: assigned_ssl_port`. For domains, report `ssl_port: preferred_external_port` if it was
successfully used for the domain vhost, otherwise report `ssl_port: assigned_ssl_port`.
#### 6. Reachability Test Endpoint
#### 6. Frontend: Interfaces Page Overhaul (View/Manage Split)
The current interfaces page is a single page showing gateways (with toggle), addresses, public
domains, and private domains. It gets split into two pages: **View** and **Manage**.
**SDK**: `preferredExternalPort` is already exposed. No additional SDK changes needed.
##### View Page
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),
gateway name, SSL indicator, enable/disable state, port forward info for public addresses, and a test button
for reachability (see Section 7).
No gateway-level toggles. The old `gateways.component.ts` toggle UI is removed.
**Note**: Exact UI element placement (where toggles, buttons, info badges go) is sensitive.
Prompt the user for specific placement decisions during implementation.
##### Manage Page
Simple CRUD interface for configuring which addresses exist. Two sections:
- **Public domains**: Add/remove. Uses existing RPC endpoints:
- `{server,package}.host.address.domain.public.add`
- `{server,package}.host.address.domain.public.remove`
- **Private domains**: Add/remove. Uses existing RPC endpoints:
- `{server,package}.host.address.domain.private.add`
- `{server,package}.host.address.domain.private.remove`
##### Key Frontend Files to Modify
| File | Change |
|------|--------|
| `web/projects/ui/src/app/routes/portal/components/interfaces/` | Overhaul: split into view/manage |
| `web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts` | Remove (replaced by per-address toggles on View page) |
| `web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts` | Update `MappedServiceInterface` to compute enabled addresses from `DerivedAddressInfo` |
| `web/projects/ui/src/app/routes/portal/components/interfaces/addresses/` | Refactor for View page with overflow menu (enable/disable) and test buttons |
| `web/projects/ui/src/app/routes/portal/routes/services/services.routes.ts` | Add routes for view/manage sub-pages |
| `web/projects/ui/src/app/routes/portal/routes/system/system.routes.ts` | Add routes for view/manage sub-pages |
#### 7. Reachability Test Endpoint
New RPC endpoint that tests whether an address is actually reachable, with diagnostic info on
failure.
**RPC endpoint** (`binding.rs` or new file):
- **`test-address`** — Test reachability of a specific address.
```ts
interface BindingTestAddressParams {
internalPort: number;
address: HostnameInfo;
internalPort: number
address: HostnameInfo
}
```
@@ -139,8 +185,8 @@ Pending tasks for AI agents. Remove items when completed.
```ts
interface TestAddressResult {
dns: string[] | null; // resolved IPs, null if not a domain address or lookup failed
portOpen: boolean | null; // TCP connect result, null if not applicable
dns: string[] | null // resolved IPs, null if not a domain address or lookup failed
portOpen: boolean | null // TCP connect result, null if not applicable
}
```
@@ -159,17 +205,17 @@ Pending tasks for AI agents. Remove items when completed.
### Key Files
| File | Role |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `core/src/net/forward.rs` | `AvailablePorts` — port pool allocation, `try_alloc()` for preferred ports |
| `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()` — 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/gateway.rs` | `InterfaceFilter` trait and filter types (`AddressFilter`, `PublicFilter`, etc.) |
| `core/src/net/service_interface.rs` | `HostnameInfo` — derives `Ord` for `BTreeSet` usage |
| `core/src/net/host/address.rs` | `HostAddress` (flattened struct), domain CRUD endpoints |
| `sdk/base/lib/interfaces/Host.ts` | SDK `MultiHost.bindPort()` — no changes needed |
| `core/src/db/model/public.rs` | Public DB model — port forward mapping |
| File | Role |
|------|------|
| `core/src/net/forward.rs` | `AvailablePorts` — port pool allocation, `try_alloc()` for preferred ports |
| `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()` — 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/gateway.rs` | `InterfaceFilter` trait and filter types (`AddressFilter`, `PublicFilter`, etc.) |
| `core/src/net/service_interface.rs` | `HostnameInfo` — derives `Ord` for `BTreeSet` usage |
| `core/src/net/host/address.rs` | `HostAddress` (flattened struct), domain CRUD endpoints |
| `sdk/base/lib/interfaces/Host.ts` | SDK `MultiHost.bindPort()` — no changes needed |
| `core/src/db/model/public.rs` | Public DB model — port forward mapping |
- [ ] Extract TS-exported types into a lightweight sub-crate for fast binding generation

View File

@@ -16,16 +16,16 @@ The container runtime communicates with the host via JSON-RPC over Unix socket.
## `/media/startos/` Directory (mounted by host into container)
| Path | Description |
| -------------------- | ----------------------------------------------------- |
| `volumes/<name>/` | Package data volumes (id-mapped, persistent) |
| `assets/` | Read-only assets from s9pk `assets.squashfs` |
| `images/<name>/` | Container images (squashfs, used for subcontainers) |
| `images/<name>.env` | Environment variables for image |
| `images/<name>.json` | Image metadata |
| `backup/` | Backup mount point (mounted during backup operations) |
| `rpc/service.sock` | RPC socket (container runtime listens here) |
| `rpc/host.sock` | Host RPC socket (for effects callbacks to host) |
| Path | Description |
|------|-------------|
| `volumes/<name>/` | Package data volumes (id-mapped, persistent) |
| `assets/` | Read-only assets from s9pk `assets.squashfs` |
| `images/<name>/` | Container images (squashfs, used for subcontainers) |
| `images/<name>.env` | Environment variables for image |
| `images/<name>.json` | Image metadata |
| `backup/` | Backup mount point (mounted during backup operations) |
| `rpc/service.sock` | RPC socket (container runtime listens here) |
| `rpc/host.sock` | Host RPC socket (for effects callbacks to host) |
## S9PK Structure

View File

@@ -89,8 +89,8 @@ export class DockerProcedureContainer extends Drop {
`${packageId}.embassy`,
...new Set(
Object.values(hostInfo?.bindings || {})
.flatMap((b) => b.addresses.available)
.map((h) => h.host),
.flatMap((b) => b.addresses.possible)
.map((h) => h.hostname.value),
).values(),
]
const certChain = await effects.getSslCertificate({

View File

@@ -1245,7 +1245,7 @@ async function updateConfig(
: catchFn(
() =>
filled.addressInfo!.filter({ kind: "mdns" })!.hostnames[0]
.host,
.hostname.value,
) || ""
mutConfigValue[key] = url
}

View File

@@ -1,69 +0,0 @@
# Core Architecture
The Rust backend daemon for StartOS.
## Binaries
The crate produces a single binary `startbox` that is symlinked under different names for different behavior:
- `startbox` / `startd` — Main daemon
- `start-cli` — CLI interface
- `start-container` — Runs inside LXC containers; communicates with host and manages subcontainers
- `registrybox` — Registry daemon
- `tunnelbox` — VPN/tunnel daemon
## Crate Structure
- `startos` — Core library that supports building `startbox`
- `helpers` — Utility functions used across both `startos` and `js-engine`
- `models` — Types shared across `startos`, `js-engine`, and `helpers`
## Key Modules
- `src/context/` — Context types (RpcContext, CliContext, InitContext, DiagnosticContext)
- `src/service/` — Service lifecycle management with actor pattern (`service_actor.rs`)
- `src/db/model/` — Patch-DB models (`public.rs` synced to frontend, `private.rs` backend-only)
- `src/net/` — Networking (DNS, ACME, WiFi, Tor via Arti, WireGuard)
- `src/s9pk/` — S9PK package format (merkle archive)
- `src/registry/` — Package registry management
## RPC Pattern
The API is JSON-RPC (not REST). All endpoints are RPC methods organized in a hierarchical command structure using [rpc-toolkit](https://github.com/Start9Labs/rpc-toolkit). Handlers are registered in a tree of `ParentHandler` nodes, with four handler types: `from_fn_async` (standard), `from_fn_async_local` (non-Send), `from_fn` (sync), and `from_fn_blocking` (blocking). Metadata like `.with_about()` drives middleware and documentation.
See [rpc-toolkit.md](rpc-toolkit.md) for full handler patterns and configuration.
## Patch-DB Patterns
Patch-DB provides diff-based state synchronization. Changes to `db/model/public.rs` automatically sync to the frontend.
**Key patterns:**
- `db.peek().await` — Get a read-only snapshot of the database state
- `db.mutate(|db| { ... }).await` — Apply mutations atomically, returns `MutateResult`
- `#[derive(HasModel)]` — Derive macro for types stored in the database, generates typed accessors
**Generated accessor types** (from `HasModel` derive):
- `as_field()` — Immutable reference: `&Model<T>`
- `as_field_mut()` — Mutable reference: `&mut Model<T>`
- `into_field()` — Owned value: `Model<T>`
**`Model<T>` APIs** (from `db/prelude.rs`):
- `.de()` — Deserialize to `T`
- `.ser(&value)` — Serialize from `T`
- `.mutate(|v| ...)` — Deserialize, mutate, reserialize
- For maps: `.keys()`, `.as_idx(&key)`, `.as_idx_mut(&key)`, `.insert()`, `.remove()`, `.contains_key()`
## i18n
See [i18n-patterns.md](i18n-patterns.md) for internationalization key conventions and the `t!()` macro.
## Rust Utilities & Patterns
See [core-rust-patterns.md](core-rust-patterns.md) for common utilities (Invoke trait, Guard pattern, mount guards, Apply trait, etc.).
## Related Documentation
- [rpc-toolkit.md](rpc-toolkit.md) — JSON-RPC handler patterns
- [i18n-patterns.md](i18n-patterns.md) — Internationalization conventions
- [core-rust-patterns.md](core-rust-patterns.md) — Common Rust utilities
- [s9pk-structure.md](s9pk-structure.md) — S9PK package format

View File

@@ -2,24 +2,51 @@
The Rust backend daemon for StartOS.
## Architecture
## Binaries
See [ARCHITECTURE.md](ARCHITECTURE.md) for binaries, modules, Patch-DB patterns, and related documentation.
- `startbox` — Main daemon (runs as `startd`)
- `start-cli` — CLI interface
- `start-container` — Runs inside LXC containers; communicates with host and manages subcontainers
- `registrybox` — Registry daemon
- `tunnelbox` — VPN/tunnel daemon
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add RPC endpoints, TS-exported types, and i18n keys.
## Key Modules
## Quick Reference
- `src/context/` — Context types (RpcContext, CliContext, InitContext, DiagnosticContext)
- `src/service/` — Service lifecycle management with actor pattern (`service_actor.rs`)
- `src/db/model/` — Patch-DB models (`public.rs` synced to frontend, `private.rs` backend-only)
- `src/net/` — Networking (DNS, ACME, WiFi, Tor via Arti, WireGuard)
- `src/s9pk/` — S9PK package format (merkle archive)
- `src/registry/` — Package registry management
```bash
cargo check -p start-os # Type check
make test-core # Run tests
make ts-bindings # Regenerate TS types after changing #[ts(export)] structs
cd sdk && make baseDist dist # Rebuild SDK after ts-bindings
```
## RPC Pattern
## Operating Rules
See `rpc-toolkit.md` for JSON-RPC handler patterns and configuration.
- Always run `cargo check -p start-os` after modifying Rust code
- When adding RPC endpoints, follow the patterns in [rpc-toolkit.md](rpc-toolkit.md)
- When modifying `#[ts(export)]` types, regenerate bindings and rebuild the SDK (see [ARCHITECTURE.md](../ARCHITECTURE.md#build-pipeline))
- When adding i18n keys, add all 5 locales in `core/locales/i18n.yaml` (see [i18n-patterns.md](i18n-patterns.md))
## Patch-DB Patterns
Patch-DB provides diff-based state synchronization. Changes to `db/model/public.rs` automatically sync to the frontend.
**Key patterns:**
- `db.peek().await` — Get a read-only snapshot of the database state
- `db.mutate(|db| { ... }).await` — Apply mutations atomically, returns `MutateResult`
- `#[derive(HasModel)]` — Derive macro for types stored in the database, generates typed accessors
**Generated accessor types** (from `HasModel` derive):
- `as_field()` — Immutable reference: `&Model<T>`
- `as_field_mut()` — Mutable reference: `&mut Model<T>`
- `into_field()` — Owned value: `Model<T>`
**`Model<T>` APIs** (from `db/prelude.rs`):
- `.de()` — Deserialize to `T`
- `.ser(&value)` — Serialize from `T`
- `.mutate(|v| ...)` — Deserialize, mutate, reserialize
- For maps: `.keys()`, `.as_idx(&key)`, `.as_idx_mut(&key)`, `.insert()`, `.remove()`, `.contains_key()`
## i18n
See `i18n-patterns.md` for internationalization key conventions and the `t!()` macro.
## Rust Utilities & Patterns
See `core-rust-patterns.md` for common utilities (Invoke trait, Guard pattern, mount guards, Apply trait, etc.).

View File

@@ -1,49 +0,0 @@
# Contributing to Core
For general environment setup, cloning, and build system, see the root [CONTRIBUTING.md](../CONTRIBUTING.md).
## Prerequisites
- [Rust](https://rustup.rs) (nightly for formatting)
- [rust-analyzer](https://rust-analyzer.github.io/) recommended
- [Docker](https://docs.docker.com/get-docker/) (for cross-compilation via `rust-zig-builder` container)
## Common Commands
```bash
cargo check -p start-os # Type check
cargo test --features=test # Run tests (or: make test-core)
make format # Format with nightly rustfmt
cd core && cargo test <test_name> --features=test # Run a specific test
```
## Adding a New RPC Endpoint
1. Define a params struct with `#[derive(Deserialize, Serialize)]`
2. Choose a handler type (`from_fn_async` for most cases)
3. Write the handler function: `async fn my_handler(ctx: RpcContext, params: MyParams) -> Result<MyResponse, Error>`
4. Register it in the appropriate `ParentHandler` tree
5. If params/response should be available in TypeScript, add `#[derive(TS)]` and `#[ts(export)]`
See [rpc-toolkit.md](rpc-toolkit.md) for full handler patterns and all four handler types.
## Adding TS-Exported Types
When a Rust type needs to be available in TypeScript (for the web frontend or SDK):
1. Add `ts_rs::TS` to the derive list and `#[ts(export)]` to the struct/enum
2. Use `#[serde(rename_all = "camelCase")]` for JS-friendly field names
3. For types that don't implement TS (like `DateTime<Utc>`, `exver::Version`), use `#[ts(type = "string")]` overrides
4. For `u64` fields that should be JS `number` (not `bigint`), use `#[ts(type = "number")]`
5. Run `make ts-bindings` to regenerate — files appear in `core/bindings/` then sync to `sdk/base/lib/osBindings/`
6. Rebuild the SDK: `cd sdk && make baseDist dist`
## Adding i18n Keys
1. Add the key to `core/locales/i18n.yaml` with all 5 language translations
2. Use the `t!("your.key.name")` macro in Rust code
3. Follow existing namespace conventions — match the module path where the key is used
4. Use kebab-case for multi-word segments
5. Translations are validated at compile time
See [i18n-patterns.md](i18n-patterns.md) for full conventions.

View File

@@ -22,7 +22,9 @@ several different names for different behavior:
- `start-sdk`: This is a CLI tool that aids in building and packaging services
you wish to deploy to StartOS
## Documentation
## Questions
- [ARCHITECTURE.md](ARCHITECTURE.md) — Backend architecture, modules, and patterns
- [CONTRIBUTING.md](CONTRIBUTING.md) — How to contribute to core
If you have questions about how various pieces of the backend system work. Open
an issue and tag the following people
- dr-bonez

View File

@@ -271,7 +271,6 @@ pub fn display_action_result<T: Serialize>(
}
#[derive(Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct RunActionParams {
pub package_id: PackageId,
@@ -363,7 +362,6 @@ pub async fn run_action(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct ClearTaskParams {

View File

@@ -418,7 +418,6 @@ impl AsLogoutSessionId for KillSessionId {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct KillParams {
@@ -436,7 +435,6 @@ pub async fn kill<C: SessionAuthContext>(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct ResetPasswordParams {

View File

@@ -30,7 +30,6 @@ use crate::util::serde::IoFormat;
use crate::version::VersionT;
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct BackupParams {

View File

@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::PackageId;
use crate::context::CliContext;
@@ -14,22 +13,19 @@ pub mod os;
pub mod restore;
pub mod target;
#[derive(Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Debug, Deserialize, Serialize)]
pub struct BackupReport {
server: ServerBackupReport,
packages: BTreeMap<PackageId, PackageBackupReport>,
}
#[derive(Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ServerBackupReport {
attempted: bool,
error: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Debug, Deserialize, Serialize)]
pub struct PackageBackupReport {
pub error: Option<String>,
}

View File

@@ -30,7 +30,6 @@ use crate::{PLATFORM, PackageId};
#[derive(Deserialize, Serialize, Parser, TS)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
#[ts(export)]
pub struct RestorePackageParams {
#[arg(help = "help.arg.package-ids")]
pub ids: Vec<PackageId>,

View File

@@ -36,8 +36,7 @@ impl Map for CifsTargets {
}
}
#[derive(Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CifsBackupTarget {
hostname: String,
@@ -73,10 +72,9 @@ pub fn cifs<C: Context>() -> ParentHandler<C> {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct CifsAddParams {
pub struct AddParams {
#[arg(help = "help.arg.cifs-hostname")]
pub hostname: String,
#[arg(help = "help.arg.cifs-path")]
@@ -89,12 +87,12 @@ pub struct CifsAddParams {
pub async fn add(
ctx: RpcContext,
CifsAddParams {
AddParams {
hostname,
path,
username,
password,
}: CifsAddParams,
}: AddParams,
) -> Result<KeyVal<BackupTargetId, BackupTarget>, Error> {
let cifs = Cifs {
hostname,
@@ -133,10 +131,9 @@ pub async fn add(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct CifsUpdateParams {
pub struct UpdateParams {
#[arg(help = "help.arg.backup-target-id")]
pub id: BackupTargetId,
#[arg(help = "help.arg.cifs-hostname")]
@@ -151,13 +148,13 @@ pub struct CifsUpdateParams {
pub async fn update(
ctx: RpcContext,
CifsUpdateParams {
UpdateParams {
id,
hostname,
path,
username,
password,
}: CifsUpdateParams,
}: UpdateParams,
) -> Result<KeyVal<BackupTargetId, BackupTarget>, Error> {
let id = if let BackupTargetId::Cifs { id } = id {
id
@@ -210,15 +207,14 @@ pub async fn update(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct CifsRemoveParams {
pub struct RemoveParams {
#[arg(help = "help.arg.backup-target-id")]
pub id: BackupTargetId,
}
pub async fn remove(ctx: RpcContext, CifsRemoveParams { id }: CifsRemoveParams) -> Result<(), Error> {
pub async fn remove(ctx: RpcContext, RemoveParams { id }: RemoveParams) -> Result<(), Error> {
let id = if let BackupTargetId::Cifs { id } = id {
id
} else {

View File

@@ -34,8 +34,7 @@ use crate::util::{FromStrParser, VersionString};
pub mod cifs;
#[derive(Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum BackupTarget {
@@ -50,7 +49,7 @@ pub enum BackupTarget {
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, TS)]
#[ts(export, type = "string")]
#[ts(type = "string")]
pub enum BackupTargetId {
Disk { logicalname: PathBuf },
Cifs { id: u32 },
@@ -112,7 +111,6 @@ impl Serialize for BackupTargetId {
}
#[derive(Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum BackupTargetFS {
@@ -212,26 +210,20 @@ pub async fn list(ctx: RpcContext) -> Result<BTreeMap<BackupTargetId, BackupTarg
.collect())
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BackupInfo {
#[ts(type = "string")]
pub version: Version,
#[ts(type = "string | null")]
pub timestamp: Option<DateTime<Utc>>,
pub package_backups: BTreeMap<PackageId, PackageBackupInfo>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PackageBackupInfo {
pub title: InternedString,
pub version: VersionString,
#[ts(type = "string")]
pub os_version: Version,
#[ts(type = "string")]
pub timestamp: DateTime<Utc>,
}
@@ -273,7 +265,6 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) -> Re
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct InfoParams {
@@ -396,7 +387,6 @@ pub async fn mount(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct UmountParams {

View File

@@ -8,7 +8,6 @@ use crate::prelude::*;
use crate::{Error, PackageId};
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct ControlParams {

View File

@@ -93,7 +93,6 @@ impl Public {
),
public_domains: BTreeMap::new(),
private_domains: BTreeMap::new(),
port_forwards: BTreeSet::new(),
},
wifi: WifiInfo {
enabled: true,

View File

@@ -43,28 +43,22 @@ pub struct DiskInfo {
pub guid: Option<InternedString>,
}
#[derive(Clone, Debug, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PartitionInfo {
pub logicalname: PathBuf,
pub label: Option<String>,
#[ts(type = "number")]
pub capacity: u64,
#[ts(type = "number | null")]
pub used: Option<u64>,
pub start_os: BTreeMap<String, StartOsRecoveryInfo>,
pub guid: Option<InternedString>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StartOsRecoveryInfo {
pub hostname: Hostname,
#[ts(type = "string")]
pub version: exver::Version,
#[ts(type = "string")]
pub timestamp: DateTime<Utc>,
pub password_hash: Option<String>,
pub wrapped_key: Option<String>,

View File

@@ -6,8 +6,7 @@ use tracing::instrument;
use crate::util::Invoke;
use crate::{Error, ErrorKind};
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, ts_rs::TS)]
#[ts(type = "string")]
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Hostname(pub InternedString);
lazy_static::lazy_static! {

View File

@@ -177,7 +177,6 @@ pub async fn install(
}
#[derive(Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SideloadParams {
#[ts(skip)]
@@ -186,7 +185,6 @@ pub struct SideloadParams {
}
#[derive(Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SideloadResponse {
pub upload: Guid,
@@ -286,7 +284,6 @@ pub async fn sideload(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct CancelInstallParams {
@@ -524,7 +521,6 @@ pub async fn cli_install(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct UninstallParams {

View File

@@ -24,7 +24,6 @@ use tokio::process::{Child, Command};
use tokio_stream::wrappers::LinesStream;
use tokio_tungstenite::tungstenite::Message;
use tracing::instrument;
use ts_rs::TS;
use crate::PackageId;
use crate::context::{CliContext, RpcContext};
@@ -110,28 +109,23 @@ async fn ws_handler(
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, TS)]
#[ts(export)]
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LogResponse {
#[ts(as = "Vec<LogEntry>")]
pub entries: Reversible<LogEntry>,
start_cursor: Option<String>,
end_cursor: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, TS)]
#[ts(export)]
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LogFollowResponse {
start_cursor: Option<String>,
guid: Guid,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, TS)]
#[ts(export)]
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LogEntry {
#[ts(type = "string")]
timestamp: DateTime<Utc>,
message: String,
boot_id: String,
@@ -327,17 +321,14 @@ impl From<BootIdentifier> for String {
}
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export, concrete(Extra = Empty), bound = "")]
#[derive(Deserialize, Serialize, Parser)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct LogsParams<Extra: FromArgMatches + Args = Empty> {
#[command(flatten)]
#[serde(flatten)]
#[ts(skip)]
extra: Extra,
#[arg(short = 'l', long = "limit", help = "help.arg.log-limit")]
#[ts(optional)]
limit: Option<usize>,
#[arg(
short = 'c',
@@ -345,11 +336,9 @@ pub struct LogsParams<Extra: FromArgMatches + Args = Empty> {
conflicts_with = "follow",
help = "help.arg.log-cursor"
)]
#[ts(optional)]
cursor: Option<String>,
#[arg(short = 'b', long = "boot", help = "help.arg.log-boot")]
#[serde(default)]
#[ts(optional, type = "number | string")]
boot: Option<BootIdentifier>,
#[arg(
short = 'B',

View File

@@ -461,8 +461,7 @@ impl ValueParserFactory for AcmeProvider {
}
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct InitAcmeParams {
#[arg(long, help = "help.arg.acme-provider")]
pub provider: AcmeProvider,
@@ -487,8 +486,7 @@ pub async fn init(
Ok(())
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct RemoveAcmeParams {
#[arg(long, help = "help.arg.acme-provider")]
pub provider: AcmeProvider,

View File

@@ -25,7 +25,6 @@ use serde::{Deserialize, Serialize};
use tokio::net::{TcpListener, UdpSocket};
use tokio::sync::RwLock;
use tracing::instrument;
use ts_rs::TS;
use crate::context::{CliContext, RpcContext};
use crate::db::model::Database;
@@ -94,8 +93,7 @@ pub fn dns_api<C: Context>() -> ParentHandler<C> {
)
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct QueryDnsParams {
#[arg(help = "help.arg.fqdn")]
pub fqdn: InternedString,
@@ -135,8 +133,7 @@ pub fn query_dns<C: Context>(
.map_err(Error::from)
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct SetStaticDnsParams {
#[arg(help = "help.arg.dns-servers")]
pub servers: Option<Vec<String>>,

View File

@@ -119,7 +119,6 @@ async fn list_interfaces(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct ForgetGatewayParams {
#[arg(help = "help.arg.gateway-id")]
gateway: GatewayId,
@@ -133,7 +132,6 @@ async fn forget_iface(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct RenameGatewayParams {
#[arg(help = "help.arg.gateway-id")]
id: GatewayId,
@@ -1007,10 +1005,9 @@ impl NetworkInterfaceController {
.as_network_mut()
.as_gateways_mut()
.ser(info)?;
let hostname = crate::hostname::Hostname(db.as_public().as_server_info().as_hostname().de()?);
let ports = db.as_private().as_available_ports().de()?;
for host in all_hosts(db) {
host?.update_addresses(&hostname, info, &ports)?;
host?.update_addresses(info, &ports)?;
}
Ok(())
})

View File

@@ -10,7 +10,6 @@ use ts_rs::TS;
use crate::GatewayId;
use crate::context::{CliContext, RpcContext};
use crate::db::model::DatabaseModel;
use crate::hostname::Hostname;
use crate::net::acme::AcmeProvider;
use crate::net::host::{HostApiKind, all_hosts};
use crate::prelude::*;
@@ -25,7 +24,6 @@ pub struct HostAddress {
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct PublicDomainConfig {
pub gateway: GatewayId,
pub acme: Option<AcmeProvider>,
@@ -159,8 +157,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
)
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct AddPublicDomainParams {
#[arg(help = "help.arg.fqdn")]
pub fqdn: InternedString,
@@ -197,10 +194,9 @@ pub async fn add_public_domain<Kind: HostApiKind>(
.as_public_domains_mut()
.insert(&fqdn, &PublicDomainConfig { acme, gateway })?;
handle_duplicates(db)?;
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
let ports = db.as_private().as_available_ports().de()?;
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
Kind::host_for(&inheritance, db)?.update_addresses(&gateways, &ports)
})
.await
.result?;
@@ -213,8 +209,7 @@ pub async fn add_public_domain<Kind: HostApiKind>(
.with_kind(ErrorKind::Unknown)?
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct RemoveDomainParams {
#[arg(help = "help.arg.fqdn")]
pub fqdn: InternedString,
@@ -230,10 +225,9 @@ pub async fn remove_public_domain<Kind: HostApiKind>(
Kind::host_for(&inheritance, db)?
.as_public_domains_mut()
.remove(&fqdn)?;
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
let ports = db.as_private().as_available_ports().de()?;
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
Kind::host_for(&inheritance, db)?.update_addresses(&gateways, &ports)
})
.await
.result?;
@@ -242,8 +236,7 @@ pub async fn remove_public_domain<Kind: HostApiKind>(
Ok(())
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[derive(Deserialize, Serialize, Parser)]
pub struct AddPrivateDomainParams {
#[arg(help = "help.arg.fqdn")]
pub fqdn: InternedString,
@@ -262,10 +255,9 @@ pub async fn add_private_domain<Kind: HostApiKind>(
.upsert(&fqdn, || Ok(BTreeSet::new()))?
.mutate(|d| Ok(d.insert(gateway)))?;
handle_duplicates(db)?;
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
let ports = db.as_private().as_available_ports().de()?;
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
Kind::host_for(&inheritance, db)?.update_addresses(&gateways, &ports)
})
.await
.result?;
@@ -284,10 +276,9 @@ pub async fn remove_private_domain<Kind: HostApiKind>(
Kind::host_for(&inheritance, db)?
.as_private_domains_mut()
.mutate(|d| Ok(d.remove(&domain)))?;
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
let ports = db.as_private().as_available_ports().de()?;
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
Kind::host_for(&inheritance, db)?.update_addresses(&gateways, &ports)
})
.await
.result?;

View File

@@ -1,6 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};
use std::future::Future;
use std::net::{IpAddr, SocketAddrV4};
use std::panic::RefUnwindSafe;
use clap::Parser;
@@ -14,8 +13,7 @@ use ts_rs::TS;
use crate::context::RpcContext;
use crate::db::model::DatabaseModel;
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
use crate::hostname::Hostname;
use crate::db::model::public::NetworkInterfaceInfo;
use crate::net::forward::AvailablePorts;
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
use crate::net::host::binding::{BindInfo, BindOptions, Bindings, binding};
@@ -34,20 +32,6 @@ pub struct Host {
pub bindings: Bindings,
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
pub private_domains: BTreeMap<InternedString, BTreeSet<GatewayId>>,
/// COMPUTED: port forwarding rules needed on gateways for public addresses to work.
#[serde(default)]
pub port_forwards: BTreeSet<PortForward>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct PortForward {
#[ts(type = "string")]
pub src: SocketAddrV4,
#[ts(type = "string")]
pub dst: SocketAddrV4,
pub gateway: GatewayId,
}
impl AsRef<Host> for Host {
@@ -82,13 +66,10 @@ impl Host {
impl Model<Host> {
pub fn update_addresses(
&mut self,
mdns: &Hostname,
gateways: &OrdMap<GatewayId, NetworkInterfaceInfo>,
available_ports: &AvailablePorts,
) -> Result<(), Error> {
let this = self.destructure_mut();
// ips
for (_, bind) in this.bindings.as_entries_mut()? {
let net = bind.as_net().de()?;
let opt = bind.as_options().de()?;
@@ -162,46 +143,6 @@ impl Model<Host> {
}
}
}
// mdns
let mdns_host = mdns.local_domain_name();
let mdns_gateways: BTreeSet<GatewayId> = gateways
.iter()
.filter(|(_, g)| {
matches!(
g.ip_info.as_ref().and_then(|i| i.device_type),
Some(NetworkInterfaceType::Ethernet | NetworkInterfaceType::Wireless)
)
})
.map(|(id, _)| id.clone())
.collect();
if let Some(port) = net.assigned_port.filter(|_| {
opt.secure
.map_or(true, |s| !(s.ssl && opt.add_ssl.is_some()))
}) {
available.insert(HostnameInfo {
ssl: opt.secure.map_or(false, |s| s.ssl),
public: false,
host: mdns_host.clone(),
port: Some(port),
metadata: HostnameMetadata::Mdns {
gateways: mdns_gateways.clone(),
},
});
}
if let Some(port) = net.assigned_ssl_port {
available.insert(HostnameInfo {
ssl: true,
public: false,
host: mdns_host,
port: Some(port),
metadata: HostnameMetadata::Mdns {
gateways: mdns_gateways,
},
});
}
// public domains
for (domain, info) in this.public_domains.de()? {
let metadata = HostnameMetadata::PublicDomain {
gateway: info.gateway.clone(),
@@ -232,14 +173,12 @@ impl Model<Host> {
available.insert(HostnameInfo {
ssl: true,
public: true,
host: domain,
host: domain.clone(),
port: Some(port),
metadata,
});
}
}
// private domains
for (domain, domain_gateways) in this.private_domains.de()? {
if let Some(port) = net.assigned_port.filter(|_| {
opt.secure
@@ -274,7 +213,7 @@ impl Model<Host> {
available.insert(HostnameInfo {
ssl: true,
public: true,
host: domain,
host: domain.clone(),
port: Some(port),
metadata: HostnameMetadata::PrivateDomain {
gateways: domain_gateways,
@@ -284,46 +223,6 @@ impl Model<Host> {
}
bind.as_addresses_mut().as_available_mut().ser(&available)?;
}
// compute port forwards from available public addresses
let bindings: Bindings = this.bindings.de()?;
let mut port_forwards = BTreeSet::new();
for bind in bindings.values() {
for addr in &bind.addresses.available {
if !addr.public {
continue;
}
let Some(port) = addr.port else {
continue;
};
let gw_id = match &addr.metadata {
HostnameMetadata::Ipv4 { gateway }
| HostnameMetadata::PublicDomain { gateway } => gateway,
_ => continue,
};
let Some(gw_info) = gateways.get(gw_id) else {
continue;
};
let Some(ip_info) = &gw_info.ip_info else {
continue;
};
let Some(wan_ip) = ip_info.wan_ip else {
continue;
};
for subnet in &ip_info.subnets {
let IpAddr::V4(addr) = subnet.addr() else {
continue;
};
port_forwards.insert(PortForward {
src: SocketAddrV4::new(wan_ip, port),
dst: SocketAddrV4::new(addr, port),
gateway: gw_id.clone(),
});
}
}
}
this.port_forwards.ser(&port_forwards)?;
Ok(())
}
}

View File

@@ -539,11 +539,10 @@ impl NetService {
.as_network()
.as_gateways()
.de()?;
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
let mut ports = db.as_private().as_available_ports().de()?;
let host = host_for(db, pkg_id.as_ref(), &id)?;
host.add_binding(&mut ports, internal_port, options)?;
host.update_addresses(&hostname, &gateways, &ports)?;
host.update_addresses(&gateways, &ports)?;
db.as_private_mut().as_available_ports_mut().ser(&ports)?;
Ok(())
})
@@ -564,7 +563,6 @@ impl NetService {
.as_network()
.as_gateways()
.de()?;
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
let ports = db.as_private().as_available_ports().de()?;
if let Some(ref pkg_id) = pkg_id {
for (host_id, host) in db
@@ -586,7 +584,7 @@ impl NetService {
}
Ok(())
})?;
host.update_addresses(&hostname, &gateways, &ports)?;
host.update_addresses(&gateways, &ports)?;
}
} else {
let host = db
@@ -605,7 +603,7 @@ impl NetService {
}
Ok(())
})?;
host.update_addresses(&hostname, &gateways, &ports)?;
host.update_addresses(&gateways, &ports)?;
}
Ok(())
})

View File

@@ -32,9 +32,6 @@ pub enum HostnameMetadata {
gateway: GatewayId,
scope_id: u32,
},
Mdns {
gateways: BTreeSet<GatewayId>,
},
PrivateDomain {
gateways: BTreeSet<GatewayId>,
},
@@ -70,9 +67,7 @@ impl HostnameMetadata {
Self::Ipv4 { gateway }
| Self::Ipv6 { gateway, .. }
| Self::PublicDomain { gateway } => Box::new(std::iter::once(gateway)),
Self::PrivateDomain { gateways } | Self::Mdns { gateways } => {
Box::new(gateways.iter())
}
Self::PrivateDomain { gateways } => Box::new(gateways.iter()),
Self::Plugin { .. } => Box::new(std::iter::empty()),
}
}

View File

@@ -175,14 +175,13 @@ pub async fn remove_tunnel(
ctx.db
.mutate(|db| {
let hostname = crate::hostname::Hostname(db.as_public().as_server_info().as_hostname().de()?);
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
let ports = db.as_private().as_available_ports().de()?;
for host in all_hosts(db) {
let host = host?;
host.as_public_domains_mut()
.mutate(|p| Ok(p.retain(|_, v| v.gateway != id)))?;
host.update_addresses(&hostname, &gateways, &ports)?;
host.update_addresses(&gateways, &ports)?;
}
Ok(())
@@ -194,7 +193,6 @@ pub async fn remove_tunnel(
ctx.db
.mutate(|db| {
let hostname = crate::hostname::Hostname(db.as_public().as_server_info().as_hostname().de()?);
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
let ports = db.as_private().as_available_ports().de()?;
for host in all_hosts(db) {
@@ -206,7 +204,7 @@ pub async fn remove_tunnel(
d.retain(|_, gateways| !gateways.is_empty());
Ok(())
})?;
host.update_addresses(&hostname, &gateways, &ports)?;
host.update_addresses(&gateways, &ports)?;
}
Ok(())

View File

@@ -278,7 +278,8 @@ impl Accept for VHostBindListener {
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
// Update listeners when ip_info or bind_reqs change
while self.ip_info.poll_changed(cx).is_ready() || self.bind_reqs.poll_changed(cx).is_ready()
while self.ip_info.poll_changed(cx).is_ready()
|| self.bind_reqs.poll_changed(cx).is_ready()
{
let reqs = self.bind_reqs.read_and_mark_seen();
let listeners = &mut self.listeners;
@@ -505,8 +506,10 @@ where
};
let src = tcp.peer_addr.ip();
// Public: source is outside all known subnets (direct internet)
let is_public = !ip_info.subnets.iter().any(|s| s.contains(&src));
// Public if: source is a gateway/router IP (NAT'd internet),
// or source is outside all known subnets (direct internet)
let is_public = ip_info.lan_ip.contains(&src)
|| !ip_info.subnets.iter().any(|s| s.contains(&src));
if is_public {
self.public.contains(&gw.id)
@@ -692,7 +695,6 @@ where
let (target, rc) = self.0.peek(|m| {
m.get(&hello.server_name().map(InternedString::from))
.or_else(|| m.get(&None))
.into_iter()
.flatten()
.filter(|(_, rc)| rc.strong_count() > 0)

View File

@@ -85,7 +85,6 @@ pub fn wifi<C: Context>() -> ParentHandler<C> {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct SetWifiEnabledParams {
@@ -151,17 +150,16 @@ pub fn country<C: Context>() -> ParentHandler<C> {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct WifiAddParams {
pub struct AddParams {
#[arg(help = "help.arg.wifi-ssid")]
ssid: String,
#[arg(help = "help.arg.wifi-password")]
password: String,
}
#[instrument(skip_all)]
pub async fn add(ctx: RpcContext, WifiAddParams { ssid, password }: WifiAddParams) -> Result<(), Error> {
pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Result<(), Error> {
let wifi_manager = ctx.wifi_manager.clone();
if !ssid.is_ascii() {
return Err(Error::new(
@@ -231,16 +229,15 @@ pub async fn add(ctx: RpcContext, WifiAddParams { ssid, password }: WifiAddParam
Ok(())
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct WifiSsidParams {
pub struct SsidParams {
#[arg(help = "help.arg.wifi-ssid")]
ssid: String,
}
#[instrument(skip_all)]
pub async fn connect(ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams) -> Result<(), Error> {
pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<(), Error> {
let wifi_manager = ctx.wifi_manager.clone();
if !ssid.is_ascii() {
return Err(Error::new(
@@ -314,7 +311,7 @@ pub async fn connect(ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams) -
}
#[instrument(skip_all)]
pub async fn remove(ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams) -> Result<(), Error> {
pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<(), Error> {
let wifi_manager = ctx.wifi_manager.clone();
if !ssid.is_ascii() {
return Err(Error::new(
@@ -362,13 +359,11 @@ pub async fn remove(ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams) ->
.result?;
Ok(())
}
#[derive(serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WifiListInfo {
ssids: HashMap<Ssid, SignalStrength>,
connected: Option<Ssid>,
#[ts(type = "string | null")]
country: Option<CountryCode>,
ethernet: bool,
available_wifi: Vec<WifiListOut>,
@@ -379,8 +374,7 @@ pub struct WifiListInfoLow {
strength: SignalStrength,
security: Vec<String>,
}
#[derive(serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WifiListOut {
ssid: Ssid,
@@ -566,7 +560,6 @@ pub async fn get_available(ctx: RpcContext, _: Empty) -> Result<Vec<WifiListOut>
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct SetCountryParams {
@@ -612,7 +605,7 @@ pub struct NetworkId(String);
/// Ssid are the names of the wifis, usually human readable.
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, TS,
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct Ssid(String);
@@ -629,7 +622,6 @@ pub struct Ssid(String);
Hash,
serde::Serialize,
serde::Deserialize,
TS,
)]
pub struct SignalStrength(u8);

View File

@@ -75,7 +75,6 @@ pub fn notification<C: Context>() -> ParentHandler<C> {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct ListNotificationParams {
@@ -141,7 +140,6 @@ pub async fn list(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct ModifyNotificationParams {
@@ -177,7 +175,6 @@ pub async fn remove(
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct ModifyNotificationBeforeParams {
@@ -329,7 +326,6 @@ pub async fn create(
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum NotificationLevel {
Success,
@@ -400,31 +396,26 @@ impl Map for Notifications {
}
}
#[derive(Debug, Serialize, Deserialize, HasModel, TS)]
#[ts(export)]
#[derive(Debug, Serialize, Deserialize, HasModel)]
#[serde(rename_all = "camelCase")]
#[model = "Model<Self>"]
pub struct Notification {
pub package_id: Option<PackageId>,
#[ts(type = "string")]
pub created_at: DateTime<Utc>,
pub code: u32,
pub level: NotificationLevel,
pub title: String,
pub message: String,
#[ts(type = "any")]
pub data: Value,
#[serde(default = "const_true")]
pub seen: bool,
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[ts(export)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotificationWithId {
id: u32,
#[serde(flatten)]
#[ts(flatten)]
notification: Notification,
}

View File

@@ -240,7 +240,7 @@ impl LocaleString {
pub fn localize(&mut self) {
self.localize_for(&*rust_i18n::locale());
}
pub fn localized(self) -> String {
pub fn localized(mut self) -> String {
self.localized_for(&*rust_i18n::locale())
}
}

View File

@@ -151,7 +151,7 @@ async fn get_action_input(
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
#[serde(rename_all = "camelCase")]
#[ts(export, rename = "EffectsRunActionParams")]
#[ts(export)]
pub struct RunActionParams {
#[serde(default)]
#[ts(skip)]

View File

@@ -701,7 +701,6 @@ struct ServiceActorSeed {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
pub struct RebuildParams {
#[arg(help = "help.arg.package-id")]
pub id: PackageId,

View File

@@ -58,8 +58,7 @@ impl ValueParserFactory for SshPubKey {
}
}
#[derive(serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SshKeyResponse {
pub alg: String,
@@ -116,16 +115,15 @@ pub fn ssh<C: Context>() -> ParentHandler<C> {
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct SshAddParams {
pub struct AddParams {
#[arg(help = "help.arg.ssh-public-key")]
key: SshPubKey,
}
#[instrument(skip_all)]
pub async fn add(ctx: RpcContext, SshAddParams { key }: SshAddParams) -> Result<SshKeyResponse, Error> {
pub async fn add(ctx: RpcContext, AddParams { key }: AddParams) -> Result<SshKeyResponse, Error> {
let mut key = WithTimeData::new(key);
let fingerprint = InternedString::intern(key.0.fingerprint_md5());
let (keys, res) = ctx
@@ -152,10 +150,9 @@ pub async fn add(ctx: RpcContext, SshAddParams { key }: SshAddParams) -> Result<
}
#[derive(Deserialize, Serialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct SshDeleteParams {
pub struct DeleteParams {
#[arg(help = "help.arg.ssh-fingerprint")]
#[ts(type = "string")]
fingerprint: InternedString,
@@ -164,7 +161,7 @@ pub struct SshDeleteParams {
#[instrument(skip_all)]
pub async fn remove(
ctx: RpcContext,
SshDeleteParams { fingerprint }: SshDeleteParams,
DeleteParams { fingerprint }: DeleteParams,
) -> Result<(), Error> {
let keys = ctx
.db

View File

@@ -191,9 +191,7 @@ pub async fn governor(
Ok(GovernorInfo { current, available })
}
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize)]
pub struct TimeInfo {
now: String,
uptime: u64,
@@ -333,7 +331,6 @@ pub struct MetricLeaf<T> {
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, TS)]
#[ts(type = "{ value: string, unit: string }")]
pub struct Celsius(f64);
impl fmt::Display for Celsius {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -362,7 +359,6 @@ impl<'de> Deserialize<'de> for Celsius {
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, TS)]
#[ts(type = "{ value: string, unit: string }")]
pub struct Percentage(f64);
impl Serialize for Percentage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -389,7 +385,6 @@ impl<'de> Deserialize<'de> for Percentage {
}
#[derive(Clone, Debug, TS)]
#[ts(type = "{ value: string, unit: string }")]
pub struct MebiBytes(pub f64);
impl Serialize for MebiBytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -416,7 +411,6 @@ impl<'de> Deserialize<'de> for MebiBytes {
}
#[derive(Clone, Debug, PartialEq, PartialOrd, TS)]
#[ts(type = "{ value: string, unit: string }")]
pub struct GigaBytes(f64);
impl Serialize for GigaBytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -496,7 +490,6 @@ pub async fn metrics(ctx: RpcContext) -> Result<Metrics, Error> {
#[derive(Deserialize, Serialize, Clone, Debug, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct MetricsFollowResponse {
pub guid: Guid,
pub metrics: Metrics,
@@ -1218,7 +1211,6 @@ pub async fn set_keyboard(ctx: RpcContext, options: KeyboardOptions) -> Result<(
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, Parser)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SetLanguageParams {
#[arg(help = "help.arg.language-code")]

View File

@@ -1,4 +0,0 @@
// 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 AddPrivateDomainParams = { fqdn: string; gateway: GatewayId }

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AcmeProvider } from './AcmeProvider'
import type { GatewayId } from './GatewayId'
export type AddPublicDomainParams = {
fqdn: string
acme: AcmeProvider | null
gateway: GatewayId
}

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageBackupInfo } from './PackageBackupInfo'
import type { PackageId } from './PackageId'
export type BackupInfo = {
version: string
timestamp: string | null
packageBackups: { [key: PackageId]: PackageBackupInfo }
}

View File

@@ -1,11 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BackupTargetId } from './BackupTargetId'
import type { PackageId } from './PackageId'
import type { PasswordType } from './PasswordType'
export type BackupParams = {
targetId: BackupTargetId
oldPassword: PasswordType | null
packageIds: Array<PackageId> | null
password: PasswordType
}

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageBackupReport } from './PackageBackupReport'
import type { PackageId } from './PackageId'
import type { ServerBackupReport } from './ServerBackupReport'
export type BackupReport = {
server: ServerBackupReport
packages: { [key: PackageId]: PackageBackupReport }
}

View File

@@ -1,17 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CifsBackupTarget } from './CifsBackupTarget'
import type { StartOsRecoveryInfo } from './StartOsRecoveryInfo'
export type BackupTarget =
| {
type: 'disk'
vendor: string | null
model: string | null
logicalname: string
label: string | null
capacity: number
used: number | null
startOs: { [key: string]: StartOsRecoveryInfo }
guid: string | null
}
| ({ type: 'cifs' } & CifsBackupTarget)

View File

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

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from './PackageId'
export type CancelInstallParams = { id: PackageId }

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Celsius = { value: string; unit: string }
export type Celsius = number

View File

@@ -1,8 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CifsAddParams = {
hostname: string
path: string
username: string
password: string | null
}

View File

@@ -1,10 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { StartOsRecoveryInfo } from './StartOsRecoveryInfo'
export type CifsBackupTarget = {
hostname: string
path: string
username: string
mountable: boolean
startOs: { [key: string]: StartOsRecoveryInfo }
}

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BackupTargetId } from './BackupTargetId'
export type CifsRemoveParams = { id: BackupTargetId }

View File

@@ -1,10 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BackupTargetId } from './BackupTargetId'
export type CifsUpdateParams = {
id: BackupTargetId
hostname: string
path: string
username: string
password: string | null
}

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from './PackageId'
import type { ReplayId } from './ReplayId'
export type ClearTaskParams = {
packageId: PackageId
replayId: ReplayId
force: boolean
}

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from './PackageId'
export type ControlParams = { id: PackageId }

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionId } from './ActionId'
import type { PackageId } from './PackageId'
export type EffectsRunActionParams = {
packageId?: PackageId
actionId: ActionId
input: any
}

View File

@@ -1,4 +0,0 @@
// 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 ForgetGatewayParams = { gateway: GatewayId }

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type GigaBytes = { value: string; unit: string }
export type GigaBytes = number

View File

@@ -1,15 +1,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Bindings } from './Bindings'
import type { GatewayId } from './GatewayId'
import type { PortForward } from './PortForward'
import type { PublicDomainConfig } from './PublicDomainConfig'
export type Host = {
bindings: Bindings
publicDomains: { [key: string]: PublicDomainConfig }
privateDomains: { [key: string]: Array<GatewayId> }
/**
* COMPUTED: port forwarding rules needed on gateways for public addresses to work.
*/
portForwards: Array<PortForward>
}

View File

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

View File

@@ -5,7 +5,6 @@ import type { PackageId } from './PackageId'
export type HostnameMetadata =
| { kind: 'ipv4'; gateway: GatewayId }
| { kind: 'ipv6'; gateway: GatewayId; scopeId: number }
| { kind: 'mdns'; gateways: Array<GatewayId> }
| { kind: 'private-domain'; gateways: Array<GatewayId> }
| { kind: 'public-domain'; gateway: GatewayId }
| { kind: 'plugin'; package: PackageId }

View File

@@ -1,8 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BackupTargetId } from './BackupTargetId'
export type InfoParams = {
targetId: BackupTargetId
serverId: string
password: string
}

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AcmeProvider } from './AcmeProvider'
export type InitAcmeParams = { provider: AcmeProvider; contact: Array<string> }

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type KillParams = { ids: Array<string> }

View File

@@ -1,6 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ListNotificationParams = {
before: number | null
limit: number | null
}

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LogEntry = { timestamp: string; message: string; bootId: string }

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from './Guid'
export type LogFollowResponse = { startCursor: string | null; guid: Guid }

View File

@@ -1,8 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { LogEntry } from './LogEntry'
export type LogResponse = {
entries: Array<LogEntry>
startCursor: string | null
endCursor: string | null
}

View File

@@ -1,8 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LogsParams = {
limit?: number
cursor?: string
boot?: number | string
before: boolean
}

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type MebiBytes = { value: string; unit: string }
export type MebiBytes = number

View File

@@ -1,5 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from './Guid'
import type { Metrics } from './Metrics'
export type MetricsFollowResponse = { guid: Guid; metrics: Metrics }

View File

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

View File

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

View File

@@ -1,14 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { NotificationLevel } from './NotificationLevel'
import type { PackageId } from './PackageId'
export type Notification = {
packageId: PackageId | null
createdAt: string
code: number
level: NotificationLevel
title: string
message: string
data: any
seen: boolean
}

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type NotificationLevel = 'success' | 'info' | 'warning' | 'error'

View File

@@ -1,15 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { NotificationLevel } from './NotificationLevel'
import type { PackageId } from './PackageId'
export type NotificationWithId = {
id: number
packageId: PackageId | null
createdAt: string
code: number
level: NotificationLevel
title: string
message: string
data: any
seen: boolean
}

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Version } from './Version'
export type PackageBackupInfo = {
title: string
version: Version
osVersion: string
timestamp: string
}

View File

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

View File

@@ -1,11 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { StartOsRecoveryInfo } from './StartOsRecoveryInfo'
export type PartitionInfo = {
logicalname: string
label: string | null
capacity: number
used: number | null
startOs: { [key: string]: StartOsRecoveryInfo }
guid: string | null
}

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Percentage = { value: string; unit: string }
export type Percentage = number

View File

@@ -1,4 +0,0 @@
// 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 PortForward = { src: string; dst: string; gateway: GatewayId }

View File

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

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from './PackageId'
export type RebuildParams = { id: PackageId }

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AcmeProvider } from './AcmeProvider'
export type RemoveAcmeParams = { provider: AcmeProvider }

View File

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

View File

@@ -1,4 +0,0 @@
// 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 RenameGatewayParams = { id: GatewayId; name: string }

View File

@@ -1,7 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PasswordType } from './PasswordType'
export type ResetPasswordParams = {
oldPassword: PasswordType | null
newPassword: PasswordType | null
}

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BackupTargetId } from './BackupTargetId'
import type { PackageId } from './PackageId'
export type RestorePackageParams = {
ids: Array<PackageId>
targetId: BackupTargetId
password: string
}

View File

@@ -1,11 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionId } from './ActionId'
import type { Guid } from './Guid'
import type { PackageId } from './PackageId'
export type RunActionParams = {
packageId: PackageId
eventId: Guid | null
packageId?: PackageId
actionId: ActionId
input?: any
input: any
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from './Guid'
export type SideloadResponse = { upload: Guid; progress: Guid }

View File

@@ -1,6 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* So a signal strength is a number between 0-100, I want the null option to be 0 since there is no signal
*/
export type SignalStrength = number

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SshPubKey } from './SshPubKey'
export type SshAddParams = { key: SshPubKey }

View File

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

Some files were not shown because too many files have changed in this diff Show More