- Rust 36.8%
- HTML 34.6%
- TypeScript 20.6%
- CSS 8%
machineconfig is canonical in the machined repo: https://code.aopc.cloud/CloudNebulaProject/machined Removed crates/machineconfig/ and switched the path dep in crates/installer-ui/Cargo.toml to a git dep on the machined repo (cargo finds the package by name in that workspace). The local workspace now contains only installer-ui. |
||
|---|---|---|
| crates/installer-ui | ||
| proto | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
installer-ui
A Rust web application that provides a minimal UI and HTTP API for the
illumos installer. It serves a small web page, exposes HTTP endpoints,
and acts as a client of the
machined gRPC
API. It can also validate/generate installer configs using the local
machineconfig crate.
Repository layout
Single-crate Cargo workspace with a snapshot of the machined.proto
API contract:
crates/installer-ui/ the axum + Angular web app (this README)
proto/machined.proto snapshot of the machined gRPC API
The proto file is consumed at build time by tonic-build to generate
a gRPC client. Sync it manually from
https://code.aopc.cloud/CloudNebulaProject/machined when machined's
API changes.
machineconfig (the install-spec parser) lives in the machined repo
at https://code.aopc.cloud/CloudNebulaProject/machined and is pulled
in here as a git dependency.
Why a Rust webapp for the UI?
Comprehensive research and rationale:
- Server framework: Axum (Tokio-based, modular, excellent ergonomics, widespread adoption). Alternatives considered:
- Actix Web: High performance, mature; Axum was preferred for simpler tower-based middleware and alignment with Tokio and tonic client patterns.
- Rocket: Batteries-included but heavier, and less commonly paired with tonic gRPC clients.
- Static assets:
tower-http::ServeDirfor serving a simple HTML interface. Alternatives:- SPA built with React/Vite or Svelte and reverse-proxied. Heavier toolchain, but viable for richer UIs.
- Full Rust/WASM UIs (Yew, Leptos, Dioxus): Compelling but adds WASM toolchain and SSR complexity. For a minimal installer UI, server-rendered/basic static page is sufficient.
- gRPC client:
tonicis the de facto Rust gRPC implementation and already used bymachined. Reusing the same.protoensures schema parity. Alternatives:- REST translation layer (grpc-gateway). Overhead and extra code; not needed as we can call gRPC directly from the server.
- Config generation: Reuse the local
machineconfiglibrary to parse/validate installer KDL and summarize output. This ensures consistent validation logic with the installer. - Security/auth:
machineduses a claim workflow returning a JWT. The UI server provides an HTTP endpoint to claim, then attaches the token to gRPC metadata asAuthorizationwhen needed.- For production, deploy behind TLS (e.g., terminated by a reverse proxy like Nginx/Traefik/Caddy) and restrict network exposure. Consider CSRF and CORS if enabling cross-origin usage.
- Streaming/long-running ops:
Installis a server stream in gRPC. The UI server could expose Server-Sent Events (SSE) or WebSocket to stream progress to the browser by bridging from gRPC stream. The current MVP omits this but leaves a clear path to add it.
Features (MVP)
- Serves a static index page at
/with basic controls, including a Pool Setup section to compose vdevs from available devices (defaults to disks). Includes a “Generate Pool KDL” action that produces a KDL snippet for the configured pool. - Multi-machine HTTP API:
GET /health→{ "status": "ok" }GET /api/machines→ list registered machines[ { id, endpoint, name?, has_token } ]POST /api/machineswith{ endpoint: string, name?: string }→{ id: string }(adds a machine to the registry)POST /api/machines/{id}/claimwith{ claim_password?: string, claim_payload?: string }→{ claim_token: string }(also stored server-side for subsequent calls)GET /api/machines/{id}/system-info?token=...→ Protobuf-encoded bytes ofSystemInfoResponse(content-typeapplication/octet-stream). Iftokenis omitted, the stored token is used if present.GET /api/machines/{id}/storage?include_partitions=0|1→ JSON{ disks: [...], partitions: [...] }(partitions may be empty; disks are returned by default).- Backward-compat:
POST /api/claimandGET /api/system-infooperate only when exactly one machine is registered; otherwise they return a 400 with guidance. POST /api/generate-configwith{ filename?: string, content: string }→ summary JSON of parsed config.POST /api/pool/configwith{ pool: { name: string, vdevs: [ { type: string, devices: string[] } ] } }→{ kdl: string, warnings: string[] }. Note:type: "stripe"isn’t a native machineconfig vdev; it’s mapped with warnings (<=1 device →mirror, >1 →raidz).
Note: SystemInfoResponse is returned as protobuf bytes since the prost-generated types don’t implement serde::Serialize. A future extension can map the fields into a serializable DTO.
Configuration
UI_BIND(default127.0.0.1:8080) – Address for the HTTP server.- Machines are added at runtime via the API/UI (e.g.,
POST /api/machines).
Building and running
cargo build --manifest-path installer-ui/Cargo.toml
UI_BIND=127.0.0.1:8080 cargo run --manifest-path installer-ui/Cargo.toml
Visit http://127.0.0.1:8080/ to use the demo UI.
Future roadmap
- Add an endpoint to call
Installand bridge the gRPC progress stream to the browser via SSE/WebSocket. - Provide JSON-friendly DTOs for system info instead of raw protobuf bytes.
- Add templating (e.g., Askama or Tera) or integrate a richer SPA if needed.
- Harden auth/session handling and add TLS.
- Package the UI as a service and integrate with the overall installer deployment.
OpenAPI
GET /api/openapi.json→ OpenAPI JSON specification generated from source annotations using theutoipacrate.
Example:
# Run the server, then fetch the OpenAPI spec as JSON
UI_BIND=127.0.0.1:8080 cargo run --manifest-path installer-ui/Cargo.toml
curl -s http://127.0.0.1:8080/api/openapi.json | jq .