mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-21 07:36:40 +00:00
fix: repin semver compose tags during fleet self-update (#1587)
* fix: repin semver compose tags during fleet self-update Fleet updates failed when docker-compose.yml pinned a semver tag because recreate reused the on-disk pin. Pull the target image first, rewrite semver pins via the update helper, and block digest or unresolved pins with fast 409s. * fix: update OFFLINE_META shape in capability and node-registry meta tests
This commit is contained in:
@@ -59,21 +59,29 @@ Updating the gateway is special because the dashboard is hosted by the very cont
|
||||
After **Update & restart** is confirmed:
|
||||
|
||||
1. The browser captures the gateway's current boot timestamp from `/api/health`.
|
||||
2. The server pulls the latest image and spawns a short-lived helper container that runs `docker compose up -d --force-recreate` against the host's compose working directory.
|
||||
2. The server classifies the compose image pin. For semver pins it pulls the target release, stages a rewritten compose file, and spawns a short-lived helper container that copies the file onto the host and runs `docker compose up -d --force-recreate`.
|
||||
3. A full-screen **Updating Sencho...** overlay takes over the browser tab. The overlay polls `/api/health` every 3 seconds and keeps the page from reloading until a *new* boot timestamp comes back, even if the API briefly responds during the pull.
|
||||
4. Once a fresh boot timestamp is reported, the overlay reloads the page on the new version.
|
||||
|
||||
If the pull or compose rewrite fails before the container restarts, the overlay dismisses within a few seconds and surfaces the error in a toast instead of waiting for the full reconnect timeout.
|
||||
|
||||
If the new container does not come up within 5 minutes, the overlay surfaces an **Update timed out** message with a *Try Reloading* button so the page is never left waiting indefinitely. If the gateway can detect that the update did not even start (for example, the image pull failed before the helper container could spawn) it surfaces a **Failed** badge with the underlying error on the Local card. The badge appears as soon as the helper writes its error file, or by the 3-minute mark at the latest, instead of waiting for the full 5-minute timeout.
|
||||
|
||||
<Note>
|
||||
The self-update helper container inherits all bind mounts from the main Sencho container 1:1. If your `docker-compose.yml` references `env_file`, `configs`, or `secrets` outside the compose working directory, those host paths must be mounted into the Sencho container at the *same container path* as on the host. See [Troubleshooting](/operations/troubleshooting#local-self-update-fails-with-env-file-not-found) if you encounter `env file not found` errors during a local update.
|
||||
</Note>
|
||||
|
||||
|
||||
## Pinned image tags
|
||||
|
||||
Fleet self-update respects how each node compose file declares the Sencho image. Semver pins are rewritten to the target release before recreate. Floating tags such as latest are pulled without changing the compose file. Digest pins and unresolved interpolated values block automatic updates; those rows show a **Pinned** badge instead of an **Update** button.
|
||||
|
||||
The image pull always runs before any compose rewrite, so a failed pull never leaves the compose file half-updated.
|
||||
## What happens during an update
|
||||
|
||||
For both local and remote nodes, an update goes through the same three steps:
|
||||
|
||||
1. **Pull** the latest `saelix/sencho` image from the registry.
|
||||
1. **Pull** the target Sencho image from the registry (the compose-declared reference for floating tags, or the repinned semver tag for version pins).
|
||||
2. **Recreate** the container with the new image via `docker compose up -d --force-recreate`.
|
||||
3. **Restart** the Sencho process. The node is briefly offline during the swap.
|
||||
|
||||
|
||||
+57
-3
@@ -772,6 +772,17 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
example: ["stacks", "containers", "fleet", "auto-updates", "host-console"]
|
||||
imagePinKind:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [floating, semver, digest, unknown]
|
||||
description: How the Sencho service image is pinned in the operator's compose file. Omitted when self-update is unavailable or the compose file could not be read.
|
||||
updateBlocked:
|
||||
type: boolean
|
||||
description: True when the image is pinned in a way Fleet cannot repin automatically (digest or unresolved value).
|
||||
updateError:
|
||||
type: string
|
||||
description: Error from the last failed self-update attempt, when present.
|
||||
|
||||
/api/system/update:
|
||||
post:
|
||||
@@ -779,9 +790,21 @@ paths:
|
||||
tags: [Health]
|
||||
summary: Trigger self-update
|
||||
description: |
|
||||
Instructs this Sencho instance to pull the latest Docker image and recreate its own container.
|
||||
Instructs this Sencho instance to pull the update image and recreate its own container.
|
||||
An optional release version in the request body drives semver repinning; floating tags are pulled without rewriting the compose file.
|
||||
Digest and unresolved pins are rejected with 409 before any pull runs.
|
||||
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.
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
targetVersion:
|
||||
type: string
|
||||
description: Release version to update to (valid semver).
|
||||
responses:
|
||||
"202":
|
||||
description: Update initiated.
|
||||
@@ -795,6 +818,23 @@ paths:
|
||||
example: "Update initiated. The server will restart shortly."
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"400":
|
||||
description: Invalid release version in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"409":
|
||||
description: Update blocked because the compose image cannot be repinned automatically.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
code:
|
||||
type: string
|
||||
"503":
|
||||
description: Self-update not available (not running in Docker Compose).
|
||||
content:
|
||||
@@ -2594,7 +2634,7 @@ paths:
|
||||
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.
|
||||
description: Returns version comparison and active update status for all nodes. Compares each node's version against the gateway's version. Local rows include compose pin details; remote rows expose only the safe pin kind and blocked flag from each node's public meta.
|
||||
responses:
|
||||
"200":
|
||||
description: Update status for all nodes.
|
||||
@@ -2631,6 +2671,20 @@ paths:
|
||||
skippedVersion:
|
||||
type: string
|
||||
nullable: true
|
||||
imagePinKind:
|
||||
type: string
|
||||
nullable: true
|
||||
composeImageRef:
|
||||
type: string
|
||||
nullable: true
|
||||
targetImageRef:
|
||||
type: string
|
||||
nullable: true
|
||||
updateBlocked:
|
||||
type: boolean
|
||||
updateBlockedReason:
|
||||
type: string
|
||||
nullable: true
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
@@ -2766,7 +2820,7 @@ paths:
|
||||
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.
|
||||
description: Triggers self-update on all remote nodes running an older version than the gateway. Forwards the hub compare target as `targetVersion` so semver-pinned remotes can repin before recreate. Local nodes are excluded from bulk updates.
|
||||
responses:
|
||||
"202":
|
||||
description: Bulk update initiated.
|
||||
|
||||
@@ -429,6 +429,16 @@ After adding the missing volume mount, restart Sencho and retry the update.
|
||||
|
||||
---
|
||||
|
||||
## Fleet shows Pinned and will not update the node
|
||||
|
||||
**Symptom:** A node row in **Node updates** shows a **Pinned** badge instead of an **Update** button, or an update attempt returns an error about the image pin.
|
||||
|
||||
**Cause:** The node's compose file pins Sencho by digest or uses a variable for the image value. Fleet cannot rewrite those automatically.
|
||||
|
||||
**Fix:** Edit the compose file to use a semver tag (for example `saelix/sencho:0.94.0`) or a floating tag such as `latest`, redeploy once if needed, then retry the update from the Node updates sheet.
|
||||
|
||||
---
|
||||
|
||||
## First remote update always times out on old nodes
|
||||
|
||||
**Symptom:** After triggering a remote update on a node running a very old Sencho version (pre-v0.39.3), the node successfully restarts with the new version, but the dashboard shows **Timed out** or **Failed** instead of **Updated**.
|
||||
|
||||
@@ -69,6 +69,10 @@ If you prefer to control exactly which version you run, pin the image tag in you
|
||||
image: saelix/sencho:0.38.0
|
||||
```
|
||||
|
||||
|
||||
|
||||
Fleet can update semver pins directly from the Node updates sheet: it rewrites the tag to the selected release before recreating the container. Digest pins and compose values that use variable interpolation must be changed manually before Fleet can update the node.
|
||||
|
||||
Check [GitHub Releases](https://github.com/studio-saelix/sencho/releases) for available versions and changelogs.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user