Skip to content

Commit 1f54007

Browse files
Added the update feature (#22)
1 parent 951980d commit 1f54007

24 files changed

Lines changed: 1889 additions & 22 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ UDS source code is maintained for people first, including developers who are new
5757
- Document every function and method with `///`, regardless of visibility. This includes test functions, test helpers, and standard trait implementations. Explain the UDS-specific purpose, behavior, or guarantee instead of merely repeating the function name.
5858
- Document public and private modules, constants, fields, and enum variants as well. The Rust `missing_docs` lint covers the public API and Clippy's `missing_docs_in_private_items` covers internal code; both are required at deny level and must pass for every target.
5959
- Treat a documented or annotated struct field or enum variant as one visual block: keep its documentation, attributes, and declaration together, and separate it from the next block with a blank line. Undocumented and unannotated fields may remain compact.
60+
- `build.rs` enforces this visual block rule for Rust files below `src/` and `tests/` during Cargo builds.
6061
- Start every non-trivial module with a `//!` comment describing its responsibility and its place in UDS.
6162
- Prefer named intermediate values over deeply nested expressions when the names make the data flow easier to follow.
6263
- Keep each module focused on one responsibility. Treat roughly 500 lines of production code as a prompt to look for a useful responsibility boundary, not as a mechanical limit.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
## Initial version
44

5+
- Added a manual update feature for configuration-assistant-installed, systemd-managed Linux single nodes, with explicit regular or prerelease selection, signed staging, persistent operations, restart observation, and rollback.
56
- Provides the update delivery server and interactive administration client for publishing, withdrawing, copying, and managing MindWork AI Studio releases.
67
- Supports clustered and single-node operation, node discovery, replication, TLS, structured logging, download statistics, graceful shutdown, and an interactive configuration assistant.

Cargo.lock

Lines changed: 186 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ axum = { version = "0.8", features = ["macros", "multipart"] }
2525
axum-server = { version = "0.8", features = ["tls-rustls"] }
2626
bytes = "1"
2727
base64 = "0.22"
28+
ed25519-dalek = { version = "3", features = ["pkcs8", "pem"] }
29+
flate2 = "1"
2830
crossterm = "0.29"
2931
clap = { version = "4.5", features = ["derive"] }
3032
directories = "6"
@@ -34,11 +36,12 @@ hex = "0.4"
3436
getrandom = "0.4"
3537
inquire = "0.9"
3638
libc = "0.2"
37-
reqwest = { version = "0.13.4", default-features = false, features = ["blocking", "json", "multipart", "rustls", "stream"] }
39+
reqwest = { version = "0.13.4", default-features = false, features = ["blocking", "json", "multipart", "query", "rustls", "stream"] }
3840
semver = "1"
3941
serde = { version = "1", features = ["derive"] }
4042
serde_json = "1"
4143
sha2 = "0.11"
44+
tar = "0.4"
4245
tempfile = "3"
4346
thiserror = "2"
4447
time = { version = "0.3", features = ["formatting", "macros", "parsing", "serde"] }

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ This repository contains the initial Rust implementation. The single-node update
1010

1111
## Supported Platforms and Production Use
1212

13-
For production deployments, we strongly recommend running UDS on a maintained Linux server. The macOS and Windows builds are intended for development, testing, and evaluation. Automatic UDS updates are available only for supported Linux installations.
13+
For production deployments, we strongly recommend running UDS on a maintained Linux server. The macOS and Windows builds are intended for development, testing, and evaluation. The manual UDS Update Feature is available only for supported Linux installations.
1414

1515
Release binaries are provided for Linux on x86_64 and ARM64, macOS on Apple Silicon, and Windows on x86_64 and ARM64. Linux releases use the standard GNU target and require glibc; Alpine Linux and other systems that provide only musl are not supported. Each release documents the minimum glibc symbol version detected during its build.
1616

17-
Automatic updates are planned for systemd-managed Linux installations. Manually launched processes must be updated and restarted manually. Containerized deployments must use their image and orchestrator rollout instead of replacing the executable inside a running container.
17+
The UDS Update Feature supports only single nodes installed by the configuration assistant as `/usr/local/bin/uds` and managed by systemd. It never updates unattended or on a schedule: an administrator must select and confirm one exact version with `uds client updates`. Manually launched processes must be replaced and restarted by their administrator. Containerized deployments must use their image and orchestrator rollout instead of replacing the executable inside a running container.
1818

1919
## UDS Release Process
2020

@@ -254,21 +254,29 @@ uds client tokens disable <uuid>
254254

255255
The first client run offers to create a local profile. The profile stores the UDS base URL, one personal admin token, and the default channel in a user-local config file. Owner tokens are never stored in client profiles or any separate client configuration. UDS hardens this file so only the current user can read or write it on Linux and macOS, and uses `icacls` for equivalent best-effort ACL hardening on Windows.
256256

257-
### Planned UDS Self-Updates
257+
### UDS Update Feature
258258

259-
Self-updating UDS nodes are a planned Linux-only capability. The release pipeline already provides a signed `latest.json` manifest for this future workflow, but the client commands and server endpoints described here are not implemented yet.
259+
Run `uds client updates` to update a supported single node manually. The client initially lists only newer regular releases. The administrator may switch to a separate prerelease list, switch back, choose one exact version, review its version, build, and release notes, and then explicitly confirm or cancel. UDS never chooses a version, updates unattended, or runs updates on a schedule.
260260

261-
The planned `uds client updates` workflow keeps every administrator request on the Admin API behind the load balancer:
261+
The authenticated Admin API implements:
262262

263-
1. The client requests the fleet view through the load balancer. The receiving node becomes the coordinator and returns stable node IDs, versions, builds, update capability, health, and last-seen timestamps.
264-
2. The administrator selects one node, several nodes, or the entire fleet. Prereleases are excluded unless the administrator explicitly enables the Canary option.
265-
3. The client submits the update operation and selected node IDs through the load balancer. It never needs private node addresses.
266-
4. The receiving coordinator resolves each node ID to the private Fleet API URL learned through discovery. It executes a local target itself and forwards other targets directly through the Fleet API. Broadcast is used for discovery, not for update commands or release archives.
267-
5. Each target verifies the Ed25519 manifest signature, platform, architecture, and SHA-256 digest before staging the new executable. A systemd-managed Linux node atomically swaps the executable, retains the previous version for rollback, and asks systemd to restart the service.
263+
- `GET /admin/v1/updates/releases?kind=regular|prerelease` for local node identity, current version/build, capability, and newer releases in exactly one category.
264+
- `POST /admin/v1/updates` with a client-generated operation UUID, the local node ID, exact version, and explicit prerelease permission.
265+
- `GET /admin/v1/updates/{operation_id}` for durable status (`queued`, `downloading`, `staged`, `applying`, `boot_confirmed`, `succeeded`, `rolled_back`, or `failed`).
268266

269-
Update operations will have stable operation IDs, be idempotent, and expose fleet-wide status so polling through the load balancer does not require a sticky session. Fleet-wide rollouts will use configurable batches with health checks and a pause between batches. Automatic updates will not be offered on macOS or Windows; those builds remain intended for development, testing, and evaluation.
267+
Repeated identical POSTs are idempotent; reusing an operation UUID for different input returns `409 Conflict`, and a node accepts only one active operation. Polling tolerates the expected connection interruption while systemd restarts UDS.
270268

271-
This workflow depends on completing peer discovery. Nodes must receive presence broadcasts, expire stale peers, and retain the mapping from each stable node ID to its advertised private `fleet_base_url` before targeted updates can be implemented safely.
269+
UDS retrieves official GitHub releases page by page, excludes drafts and invalid or non-newer SemVer tags, and keeps regular releases separate from prereleases. Before staging, it verifies the Ed25519 signature over `latest.json` with the embedded public key and checks schema, tag, Linux platform, architecture, update support, archive size, and SHA-256. The unprivileged service atomically stores only the verified manifest, signature, archive, and operation data below `data_dir/self-update/operations`.
270+
271+
The configuration assistant installs a hardened readiness-aware `uds.service`, a root `uds-update.service` oneshot, and a bounded `uds-update.path` trigger. The helper verifies the signed inputs again, reads only the signed regular executable entry from the archive, rejects unsafe paths and links, retains the old executable as `/usr/local/bin/uds.previous`, and restarts UDS. If the new service does not remain active for 30 seconds, the helper restores the previous executable. A successful update retains `uds.previous` until the next successful update; a successful rollback restores the regular `uds` path and leaves no `uds.previous`.
272+
273+
Other executable locations, manually started services, containers, macOS, Windows, and fleet mode do not support this feature.
274+
275+
#### Planned Fleet Updates
276+
277+
Fleet updates remain planned. Administrators will select nodes through the load balancer, submit stable operations, configure batches, require health checks, and pause between batches. The coordinator will resolve stable node IDs to private Fleet API addresses and direct work to each target; clients will not need private addresses.
278+
279+
This requires complete peer discovery, stale-peer expiry, and real replication first. Until those prerequisites exist, fleet mode explicitly reports the UDS Update Feature as unavailable. The signed release manifest and signing process are the shared trust foundation for today's single-node workflow and this later fleet workflow.
272280

273281
### Configure the Client
274282

0 commit comments

Comments
 (0)