mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 11:47:11 +00:00
feat(fleet): add remote node update management (#353)
Add the ability to check for outdated nodes and trigger over-the-air updates from Fleet View. Nodes self-update by pulling the latest Docker image and recreating their container via the "last breath" pattern. Backend: - SelfUpdateService: self-container identification via HOSTNAME + Docker Compose labels, triggers pull + force-recreate - CapabilityRegistry: runtime capability disabling via disableCapability() - POST /api/system/update (202 + deferred self-update) - GET /api/fleet/update-status (version comparison across fleet) - POST /api/fleet/nodes/:nodeId/update (single node) - POST /api/fleet/update-all (bulk remote update) - In-memory update tracker with 5-min timeout Frontend: - Node Updates modal with summary stats, search filter, table layout, per-node Update buttons, and bulk Update All - Version badges and update-available indicators on node cards - ReconnectingOverlay for local node updates (polls /api/health) - 5s fast-poll when any node is actively updating - UpdateStatusBadge shared component for consistent badge rendering Requires Skipper (Pro) tier. Nodes must be deployed via Docker Compose with Docker socket access.
This commit is contained in:
+6
-1
@@ -104,6 +104,7 @@
|
||||
"features/multi-node",
|
||||
"features/node-compatibility",
|
||||
"features/fleet-view",
|
||||
"features/remote-updates",
|
||||
"features/stack-labels",
|
||||
"features/alerts-notifications",
|
||||
"features/notification-routing",
|
||||
@@ -149,7 +150,8 @@
|
||||
"group": "Health & Meta",
|
||||
"pages": [
|
||||
"GET /api/health",
|
||||
"GET /api/meta"
|
||||
"GET /api/meta",
|
||||
"POST /api/system/update"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -222,6 +224,9 @@
|
||||
"GET /api/fleet/overview",
|
||||
"GET /api/fleet/node/{nodeId}/stacks",
|
||||
"GET /api/fleet/node/{nodeId}/stacks/{stackName}/containers",
|
||||
"GET /api/fleet/update-status",
|
||||
"POST /api/fleet/nodes/{nodeId}/update",
|
||||
"POST /api/fleet/update-all",
|
||||
"POST /api/fleet/snapshots",
|
||||
"GET /api/fleet/snapshots",
|
||||
"GET /api/fleet/snapshots/{id}",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Remote updates
|
||||
description: Check for outdated nodes and trigger over-the-air updates from the Fleet View.
|
||||
---
|
||||
|
||||
Sencho can update remote nodes directly from the dashboard. When the gateway is running a newer version than a remote node, a one-click update pulls the latest image and recreates the container automatically.
|
||||
|
||||
<Note>
|
||||
Remote updates require **Sencho Pro** (Skipper or Admiral tier).
|
||||
</Note>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Remote updates work when each node meets these conditions:
|
||||
|
||||
- Deployed via **Docker Compose** (the recommended method)
|
||||
- The Docker socket (`/var/run/docker.sock`) is mounted into the container
|
||||
- Running **Sencho v0.32.0** or later (the version that introduced the `self-update` capability)
|
||||
|
||||
Nodes deployed with `docker run` or orchestrators like Kubernetes do not support self-update. These nodes will show a "self-update unavailable" message.
|
||||
|
||||
## How it works
|
||||
|
||||
1. The gateway compares each node's version (from `/api/meta`) against its own version.
|
||||
2. If a node is running an older version, the Fleet View shows an **Update available** badge.
|
||||
3. Clicking **Update** sends a command to the remote node.
|
||||
4. The remote node pulls the latest `saelix/sencho` image and recreates its own container.
|
||||
5. The gateway polls the node until it comes back online with the new version.
|
||||
|
||||
The remote node briefly goes offline during the update (typically 10–30 seconds depending on image pull speed). The Fleet View tracks the progress and shows **Updating**, **Updated**, or **Timed out** status badges.
|
||||
|
||||
## Checking for updates
|
||||
|
||||
Open the **Fleet** tab and click **Check Updates** in the header. This opens the **Node Updates** dialog, which lists every node with its current version and update status.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/fleet/check-updates-modal.png" alt="Node Updates dialog showing update status for each node" />
|
||||
</Frame>
|
||||
|
||||
Each node row shows:
|
||||
|
||||
| Element | Meaning |
|
||||
|---------|---------|
|
||||
| Version label (e.g. `v0.31.0`) | The node's current Sencho version |
|
||||
| **Up to date** badge | Node is running the latest version |
|
||||
| **Update** button | A newer version is available — click to update |
|
||||
| **Updating** badge | The node is pulling and restarting |
|
||||
| **Updated** badge | The node came back online with the new version |
|
||||
| **Timed out** badge | The node did not come back within 5 minutes |
|
||||
|
||||
Nodes running a version too old to report their version will show `pre-X.Y.Z` as the current version.
|
||||
|
||||
## Updating a single node
|
||||
|
||||
Click the **Update** button next to any outdated node in the Node Updates dialog, or click the **Update to vX.Y.Z** button directly on the node card in Fleet View. For remote nodes, the update happens in the background. For the local (gateway) node, a confirmation dialog appears warning about the brief dashboard disconnection.
|
||||
|
||||
## Updating all nodes
|
||||
|
||||
Click **Update All (N)** in the Node Updates dialog to trigger updates on all outdated remote nodes simultaneously. The local node is intentionally excluded from bulk updates — update it separately to avoid losing dashboard connectivity.
|
||||
|
||||
## Local node updates
|
||||
|
||||
When updating the local (gateway) node:
|
||||
|
||||
1. A confirmation dialog explains that the dashboard will briefly disconnect.
|
||||
2. After confirming, the server pulls the new image and restarts.
|
||||
3. A reconnecting overlay appears and polls the server every 3 seconds.
|
||||
4. The page automatically reloads when the server comes back.
|
||||
|
||||
If the server does not return within 5 minutes, a timeout message appears with a manual reload option.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Node shows "self-update unavailable"
|
||||
|
||||
The node is not running inside a Docker Compose-managed container, or the Docker socket is not mounted. Check that your `docker-compose.yml` includes the socket volume mount:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
### Update times out
|
||||
|
||||
The image pull may be slow on the remote host, or the container failed to restart. SSH into the remote host and check `docker logs sencho` for errors. You can also manually update by running:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
### Old node does not show update button
|
||||
|
||||
Nodes running Sencho versions before v0.32.0 do not advertise the `self-update` capability. Update them manually first, then future updates can be triggered from the dashboard.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
@@ -570,6 +570,35 @@ paths:
|
||||
type: string
|
||||
example: ["stacks", "containers", "fleet", "auto-updates", "host-console"]
|
||||
|
||||
/api/system/update:
|
||||
post:
|
||||
operationId: triggerSelfUpdate
|
||||
tags: [Health]
|
||||
summary: Trigger self-update
|
||||
description: |
|
||||
Instructs this Sencho instance to pull the latest Docker image and recreate its own container.
|
||||
Returns 202 immediately; the actual update happens asynchronously after the response is sent.
|
||||
Requires the instance to be deployed via Docker Compose with the Docker socket mounted.
|
||||
responses:
|
||||
"202":
|
||||
description: Update initiated.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: "Update initiated. The server will restart shortly."
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"503":
|
||||
description: Self-update not available (not running in Docker Compose).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
# ── Stacks ──────────────────────────────────────────────
|
||||
/api/stacks:
|
||||
get:
|
||||
@@ -1923,6 +1952,122 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/fleet/update-status:
|
||||
get:
|
||||
operationId: getFleetUpdateStatus
|
||||
tags: [Fleet]
|
||||
summary: Fleet update status
|
||||
description: Returns version comparison and active update status for all nodes. Compares each node's version against the gateway's version.
|
||||
responses:
|
||||
"200":
|
||||
description: Update status for all nodes.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
nodes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
nodeId:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [local, remote]
|
||||
version:
|
||||
type: string
|
||||
nullable: true
|
||||
latestVersion:
|
||||
type: string
|
||||
updateAvailable:
|
||||
type: boolean
|
||||
updateStatus:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [updating, completed, timeout, failed, null]
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/ProRequired"
|
||||
|
||||
/api/fleet/nodes/{nodeId}/update:
|
||||
post:
|
||||
operationId: triggerNodeUpdate
|
||||
tags: [Fleet]
|
||||
summary: Trigger node update
|
||||
description: Initiates a self-update on the specified node. For remote nodes, sends the update command via the proxy. For local nodes, triggers the update directly.
|
||||
parameters:
|
||||
- name: nodeId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"202":
|
||||
description: Update initiated.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/ProRequired"
|
||||
"404":
|
||||
description: Node not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"409":
|
||||
description: Update already in progress.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"503":
|
||||
description: Self-update not available on the target node.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/fleet/update-all:
|
||||
post:
|
||||
operationId: triggerFleetUpdateAll
|
||||
tags: [Fleet]
|
||||
summary: Update all outdated nodes
|
||||
description: Triggers self-update on all remote nodes running an older version than the gateway. Local nodes are excluded from bulk updates.
|
||||
responses:
|
||||
"202":
|
||||
description: Bulk update initiated.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
updating:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Names of nodes where update was triggered.
|
||||
skipped:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Names of nodes that were skipped.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/ProRequired"
|
||||
|
||||
/api/fleet/snapshots:
|
||||
post:
|
||||
operationId: createFleetSnapshot
|
||||
|
||||
Reference in New Issue
Block a user