signalk-container 1.8.0 → 1.9.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +5 -0
- package/AGENTS.md +7 -5
- package/README.md +142 -75
- package/dist/containers.d.ts +9 -1
- package/dist/containers.d.ts.map +1 -1
- package/dist/containers.js +49 -2
- package/dist/containers.js.map +1 -1
- package/dist/doctor.d.ts +55 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +554 -2
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -13
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +140 -20
- package/dist/types.d.ts.map +1 -1
- package/doc/plugin-developer-guide.md +211 -2
- package/package.json +1 -1
package/.gitattributes
ADDED
package/AGENTS.md
CHANGED
|
@@ -10,7 +10,7 @@ Key components:
|
|
|
10
10
|
- **`src/resources.ts`** — cgroup-limit flag emission + live-update path via `podman/docker update`. The "Bug D" precedent for diff-on-already-running lives here.
|
|
11
11
|
- **`src/runtime.ts`** — Runtime detection (`podman` vs `docker`), version probing, `execRuntime`/`execRuntimeLong` dispatch, `isContainerized()` self-detection.
|
|
12
12
|
- **`src/updates/`** — Centralized image-update detection (digest drift for floating tags, version comparison for semver). Used by all consumer plugins via `containers.updates.register(...)`.
|
|
13
|
-
- **`
|
|
13
|
+
- **`src/configpanel/`** — React config panel source. Built with Vite + `@module-federation/vite` (see `vite.config.ts`); build artifacts land in `public/`, served via Module Federation into the Signal K Admin UI.
|
|
14
14
|
|
|
15
15
|
## Code Quality Principles
|
|
16
16
|
|
|
@@ -39,7 +39,7 @@ Do not add error handling, fallbacks, or validation for scenarios that cannot ha
|
|
|
39
39
|
- All new code requires tests. Test behavior at the function boundary, not internal control flow.
|
|
40
40
|
- Inject `exec: ExecFn = execRuntime` rather than calling the runtime directly. Tests stub via `fakeExec`. See `src/test/getLiveResources.test.ts` for the canonical pattern: synthetic `{stdout, stderr, exitCode}`, no real podman invocations.
|
|
41
41
|
- Container-integration tests (those that actually pull `alpine:3.19`) gate on `hasContainerRuntime()` which returns `null` on Windows. Do not add new tests that pull real Linux images without the same guard.
|
|
42
|
-
-
|
|
42
|
+
- All tests must pass on every commit. Run `npm test` after `npm run build` (or `npm run build:all` to do both); `node --test` runs the compiled `dist/test/**/*.test.js` glob, so a fresh build is required before testing.
|
|
43
43
|
|
|
44
44
|
## Runtime Invariants
|
|
45
45
|
|
|
@@ -137,11 +137,13 @@ This repo is maintained by Dirk Wahrheit. Workflow is deliberate; AI tools shoul
|
|
|
137
137
|
|
|
138
138
|
Before pushing or opening a PR:
|
|
139
139
|
|
|
140
|
-
1. `npm run format` — prettier write + eslint --fix
|
|
141
|
-
2. `npm run build:all` — `clean && tsc &&
|
|
142
|
-
3. `npm run ci-lint` — `eslint && prettier --check
|
|
140
|
+
1. `npm run format` — `prettier --write .` + `eslint --fix` (writes back fixes)
|
|
141
|
+
2. `npm run build:all` — `build && test`, where `build` = `clean && build:server (tsc) && build:configpanel (vite build)`. All tests must pass.
|
|
142
|
+
3. `npm run ci-lint` — `eslint && prettier --check .` — read-only verification of step 1's output; this is what CI runs and what catches uncommitted format/lint drift.
|
|
143
143
|
4. `cr review --plain | tee /tmp/cr-review-<branch>.txt` — local CodeRabbit pass. `cr` only sees committed changes, so commit first, then review. The CLI is rate-limited (~50min cooldown); pipe to a file so reruns aren't needed.
|
|
144
144
|
|
|
145
|
+
For iterative server-side work, `npm run watch` runs `tsc --watch`. It does **not** rebuild the configpanel and does **not** clean stale `dist/test/` — run a full `npm run build` before invoking `npm test`.
|
|
146
|
+
|
|
145
147
|
Only push after all four pass. **Never push without explicit approval.** `git push` always needs its own permission — commit/test/format/cr approval does not cover push.
|
|
146
148
|
|
|
147
149
|
### Release flow
|
package/README.md
CHANGED
|
@@ -20,7 +20,10 @@ Instead of each plugin implementing its own container orchestration, they delega
|
|
|
20
20
|
- **SELinux support** -- `:Z` volume flags for Podman bind mounts on Fedora/RHEL; named volumes are handled correctly (`:Z` is not applied)
|
|
21
21
|
- **Per-volume host-source policy** -- volumes accept `{ source, ifMissing: "skip" | "abort" }` for user-managed (USB drives, NFS) or deployment-required (TLS certs) mounts. Plugins subscribe to `onVolumeIssue` events for `'skipped'`, `'aborted'`, and `'recovered'` actions; signalk-container auto-recreates the container when a previously-missing source reappears. See the [developer guide](doc/plugin-developer-guide.md#optional-and-required-volumes).
|
|
22
22
|
- **Container log streaming** -- click **Logs** on any managed-container card to open a live-streaming popup of the container's stdout+stderr (combined, the same shape `podman logs <name>` produces). Plugin authors can also wire `onContainerLog` in `ensureRunning` options to forward the same stream into their plugin's `app.debug` channel — visible in the Signal K server log when debug is enabled. Multiple subscribers share a single underlying tail process. See the [developer guide](doc/plugin-developer-guide.md#streaming-container-logs-into-your-plugins-debug-channel).
|
|
23
|
+
- **Host-UID ownership alignment** -- managed containers run by default under the Signal K host user's UID/GID (via `--user host:host` on Docker/rootful Podman, `--userns=keep-id` on rootless Podman). Files created on bind mounts are owned by the same identity that runs Signal K, with no `chmod` sweeps. Override per container via `ContainerConfig.user` for images with a non-root `USER` directive, or `user: false` to opt out. See the [developer guide](doc/plugin-developer-guide.md#host-uid-ownership).
|
|
24
|
+
- **Image compliance probes** -- `containers.doctor.imageRunsAsUser(image, user?)` runs the image under the live UID mapping and verifies it can write `/tmp` as the host caller. Surfaces UID-compatibility problems _before_ a container wedges in a restart loop. See the [developer guide](doc/plugin-developer-guide.md#containersdoctorimagerunsasuserimage-user-promiseimageproberesult).
|
|
23
25
|
- **Podman image qualification** -- automatically prefixes `docker.io/` for short image names
|
|
26
|
+
- **Docker `host.containers.internal` parity** -- signalk-container adds the `host-gateway` mapping for Docker automatically (Podman has it natively). User-supplied `extraHosts` overrides are respected.
|
|
24
27
|
- **Cross-plugin API** -- other plugins use `globalThis.__signalk_containerManager`
|
|
25
28
|
|
|
26
29
|
## Config Panel
|
|
@@ -114,58 +117,64 @@ See [doc/plugin-developer-guide.md](doc/plugin-developer-guide.md) for the full
|
|
|
114
117
|
|
|
115
118
|
## API
|
|
116
119
|
|
|
117
|
-
| Method
|
|
118
|
-
|
|
|
119
|
-
| `getRuntime()`
|
|
120
|
-
| `whenReady()`
|
|
121
|
-
| `pullImage(image, onProgress?)`
|
|
122
|
-
| `imageExists(image)`
|
|
123
|
-
| `getImageDigest(imageOrContainer)`
|
|
124
|
-
| `ensureRunning(name, config, options?)`
|
|
125
|
-
| `start(name)`
|
|
126
|
-
| `stop(name)`
|
|
127
|
-
| `remove(name)`
|
|
128
|
-
| `getState(name)`
|
|
129
|
-
| `runJob(config)`
|
|
130
|
-
| `getLogs(name, options?)`
|
|
131
|
-
| `prune()`
|
|
132
|
-
| `listContainers()`
|
|
133
|
-
| `execInContainer(name, command)`
|
|
134
|
-
| `ensureNetwork(name)`
|
|
135
|
-
| `removeNetwork(name)`
|
|
136
|
-
| `connectToNetwork(container, network)`
|
|
137
|
-
| `disconnectFromNetwork(container, net)`
|
|
138
|
-
| `updates.register(reg)`
|
|
139
|
-
| `updates.unregister(pluginId)`
|
|
140
|
-
| `updates.checkOne(pluginId)`
|
|
141
|
-
| `updates.getLastResult(pluginId)`
|
|
142
|
-
| `updateResources(name, limits)`
|
|
143
|
-
| `getResources(name)`
|
|
144
|
-
| `resolveSignalkDataMount()`
|
|
145
|
-
| `resolveHostPath(absPath)`
|
|
146
|
-
| `resolveContainerAddress(name, port)`
|
|
120
|
+
| Method | Description |
|
|
121
|
+
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
122
|
+
| `getRuntime()` | Returns `{ runtime, version, isPodmanDockerShim }` or `null` |
|
|
123
|
+
| `whenReady()` | Resolves once runtime detection settles (success OR failure). Replaces the polling-loop pattern; check `getRuntime()` after the await |
|
|
124
|
+
| `pullImage(image, onProgress?)` | Pull a container image (auto-qualifies for Podman) |
|
|
125
|
+
| `imageExists(image)` | Check if image exists locally |
|
|
126
|
+
| `getImageDigest(imageOrContainer)` | Local image ID (sha256) for an image:tag or container |
|
|
127
|
+
| `ensureRunning(name, config, options?)` | Create and start container if not running; auto-recreates on config drift across `image`, `tag`, `command`, `networkMode`, `env`, `volumes`, `ports` |
|
|
128
|
+
| `start(name)` | Start a stopped container |
|
|
129
|
+
| `stop(name)` | Stop a running container |
|
|
130
|
+
| `remove(name)` | Stop and remove a container |
|
|
131
|
+
| `getState(name)` | Returns `running`, `stopped`, `missing`, or `no-runtime` |
|
|
132
|
+
| `runJob(config)` | Execute a one-shot container job |
|
|
133
|
+
| `getLogs(name, options?)` | One-shot fetch of the last N lines of a container's combined stdout+stderr log. `tail` defaults to 200, max 10000; `since` is unix-epoch seconds |
|
|
134
|
+
| `prune()` | Remove dangling images |
|
|
135
|
+
| `listContainers()` | List all `sk-` prefixed containers |
|
|
136
|
+
| `execInContainer(name, command)` | Run a command inside a running container |
|
|
137
|
+
| `ensureNetwork(name)` | Create a Podman/Docker network if it doesn't exist |
|
|
138
|
+
| `removeNetwork(name)` | Remove a network |
|
|
139
|
+
| `connectToNetwork(container, network)` | Add a container to a network (bridge mode only) |
|
|
140
|
+
| `disconnectFromNetwork(container, net)` | Remove a container from a network |
|
|
141
|
+
| `updates.register(reg)` | Register a container for update detection |
|
|
142
|
+
| `updates.unregister(pluginId)` | Stop tracking updates for a plugin |
|
|
143
|
+
| `updates.checkOne(pluginId)` | Force a fresh update check (or coalesce with in-flight) |
|
|
144
|
+
| `updates.getLastResult(pluginId)` | Cached last result, no network |
|
|
145
|
+
| `updateResources(name, limits)` | Apply new resource limits live, fall back to recreate |
|
|
146
|
+
| `getResources(name)` | Currently effective limits (plugin defaults ⊕ user override) |
|
|
147
|
+
| `resolveSignalkDataMount()` | Resolve the volume name or host path that backs `app.getDataDirPath()` in the current deployment; returns `null` if the runtime is not yet initialised |
|
|
148
|
+
| `resolveHostPath(absPath)` | Translate an arbitrary absolute path into the `{ source, subPath }` pair the runtime needs to mount it; handles bare-metal, bind, and named-volume topologies |
|
|
149
|
+
| `resolveContainerAddress(name, port)` | Return the `host:port` string to reach `port` on a managed container from the SignalK process; call after `ensureRunning()` with `signalkAccessiblePorts` set |
|
|
150
|
+
| `doctor.imageRunsAsUser(image, user?)` | Probe whether `image` runs cleanly under the host-UID mapping signalk-container will emit (1.8.0+). Never throws — returns `{ ok, output, error? }` |
|
|
151
|
+
| `doctor.selfDeployment()` | Diagnose the Signal K deployment itself: binary discovery, daemon reachability, rootless/rootful detection, and (when containerized) self-container ID. Returns `{ status, remediation, ... }` — see `SelfDeploymentResult` in `src/types.ts` |
|
|
152
|
+
| `doctor.generateSetupSnippet(format?, result?)` | Generate a ready-to-paste compose fragment (`format: "compose"`, default) or `podman/docker run` command (`format: "run"`) tailored to the detected runtime. Pure templating over `SelfDeploymentResult`; bundles a minimal Dockerfile sidecar and operator notes. |
|
|
147
153
|
|
|
148
154
|
## REST Endpoints
|
|
149
155
|
|
|
150
156
|
All mounted at `/plugins/signalk-container/api/`:
|
|
151
157
|
|
|
152
|
-
| Method | Path | Description
|
|
153
|
-
| ------ | ---------------------------------------- |
|
|
154
|
-
| GET | `/runtime` | Detected runtime info
|
|
155
|
-
| GET | `/containers` | List managed containers
|
|
156
|
-
| GET | `/containers/:name/state` | Container state
|
|
157
|
-
| POST | `/containers/:name/start` | Start a stopped container
|
|
158
|
-
| POST | `/containers/:name/stop` | Stop a running container
|
|
159
|
-
| POST | `/containers/:name/remove` | Stop and remove a container
|
|
160
|
-
| GET | `/containers/:name/logs?tail=N&since=ts` | Last N lines of the container's combined stdout+stderr log (one-shot). `tail` defaults 200, max 10000
|
|
161
|
-
| GET | `/containers/:name/logs/stream` | Server-Sent Events stream of live log lines. Closes when the container is removed or the client disconnects
|
|
162
|
-
| POST | `/prune` | Prune dangling images
|
|
163
|
-
| GET | `/updates` | List last update-check results
|
|
164
|
-
| GET | `/updates/:pluginId` | Last update-check result for one plugin
|
|
165
|
-
| POST | `/updates/:pluginId/check` | Force a fresh update check (HTTP 200 even when offline)
|
|
166
|
-
| GET | `/containers/:name/resources` | Effective resource limits + user override
|
|
167
|
-
| POST | `/containers/:name/resources` | Apply new resource limits (live or recreate). Body is a `ContainerResourceLimits` diff against the consumer plugin's default.
|
|
168
|
-
| DELETE | `/containers/:name/resources` | Clear any user override and restore the consumer plugin's pristine default limits to the running container.
|
|
158
|
+
| Method | Path | Description |
|
|
159
|
+
| ------ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
160
|
+
| GET | `/runtime` | Detected runtime info |
|
|
161
|
+
| GET | `/containers` | List managed containers |
|
|
162
|
+
| GET | `/containers/:name/state` | Container state |
|
|
163
|
+
| POST | `/containers/:name/start` | Start a stopped container |
|
|
164
|
+
| POST | `/containers/:name/stop` | Stop a running container |
|
|
165
|
+
| POST | `/containers/:name/remove` | Stop and remove a container |
|
|
166
|
+
| GET | `/containers/:name/logs?tail=N&since=ts` | Last N lines of the container's combined stdout+stderr log (one-shot). `tail` defaults 200, max 10000 |
|
|
167
|
+
| GET | `/containers/:name/logs/stream` | Server-Sent Events stream of live log lines. Closes when the container is removed or the client disconnects |
|
|
168
|
+
| POST | `/prune` | Prune dangling images |
|
|
169
|
+
| GET | `/updates` | List last update-check results |
|
|
170
|
+
| GET | `/updates/:pluginId` | Last update-check result for one plugin |
|
|
171
|
+
| POST | `/updates/:pluginId/check` | Force a fresh update check (HTTP 200 even when offline) |
|
|
172
|
+
| GET | `/containers/:name/resources` | Effective resource limits + user override |
|
|
173
|
+
| POST | `/containers/:name/resources` | Apply new resource limits (live or recreate). Body is a `ContainerResourceLimits` diff against the consumer plugin's default. |
|
|
174
|
+
| DELETE | `/containers/:name/resources` | Clear any user override and restore the consumer plugin's pristine default limits to the running container. |
|
|
175
|
+
| POST | `/doctor/image` | Probe whether an image runs cleanly under the live host-UID mapping. Body: `{ image, tag?, user? }`. Never 5xx for a failed probe — `{ ok: false, error }` is a successful response (1.8.0+). |
|
|
176
|
+
| GET | `/doctor/deployment` | Diagnose this Signal K deployment: binary discovery, daemon reachability, rootless/rootful detection, self-container ID cascade. Returns a `SelfDeploymentResult` with `status` and copy-pasteable `remediation` lines. |
|
|
177
|
+
| GET | `/doctor/snippet?format=compose\|run` | Generate a ready-to-paste compose fragment or shell command for setting up Signal K with this runtime. `text/plain` by default; pass `Accept: application/json` for the structured `SetupSnippetResult`. |
|
|
169
178
|
|
|
170
179
|
## Configuration
|
|
171
180
|
|
|
@@ -443,45 +452,107 @@ this plugin needs access to the host's container runtime to manage other
|
|
|
443
452
|
containers. The plugin auto-detects this scenario via `/.dockerenv` or
|
|
444
453
|
`/run/.containerenv` and prefixes the status with `(in-container)`.
|
|
445
454
|
|
|
446
|
-
For the plugin to work,
|
|
447
|
-
|
|
455
|
+
For the plugin to work, two things must be true inside the Signal K
|
|
456
|
+
container:
|
|
448
457
|
|
|
449
|
-
|
|
458
|
+
1. **The runtime CLI is installed.** Bake `podman` (or `podman-remote`)
|
|
459
|
+
into your Signal K image — this is the recommended path. As a quick
|
|
460
|
+
alternative you can bind-mount the host binary read-only, but that
|
|
461
|
+
couples the container to the host's exact runtime version.
|
|
462
|
+
2. **The runtime socket is bind-mounted** from the host (rootless or
|
|
463
|
+
rootful, podman or docker — see examples below).
|
|
464
|
+
|
|
465
|
+
### Quick check: `/api/doctor/deployment`
|
|
466
|
+
|
|
467
|
+
The plugin ships a self-diagnostic. After starting, hit:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
curl http://<signalk-host>:3000/plugins/signalk-container/api/doctor/deployment
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The response includes a `status` field (`ok` / `no-runtime` /
|
|
474
|
+
`socket-unreachable` / `permission-denied` / `self-id-unresolved`) and a
|
|
475
|
+
`remediation` array of copy-pasteable lines for whichever failure mode
|
|
476
|
+
applies. When startup detection fails, the same remediation is also
|
|
477
|
+
logged to the Signal K server log.
|
|
478
|
+
|
|
479
|
+
### Generate a starter snippet
|
|
480
|
+
|
|
481
|
+
To bootstrap a new deployment, ask the plugin for a ready-to-paste
|
|
482
|
+
compose fragment or shell command tailored to the detected runtime:
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
curl 'http://<signalk-host>:3000/plugins/signalk-container/api/doctor/snippet?format=compose' > docker-compose.yml
|
|
486
|
+
curl 'http://<signalk-host>:3000/plugins/signalk-container/api/doctor/snippet?format=run' > run-signalk.sh
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
The endpoint returns plain text by default; pass
|
|
490
|
+
`Accept: application/json` to get the structured
|
|
491
|
+
`SetupSnippetResult` (snippet + Dockerfile sidecar + operator notes)
|
|
492
|
+
for programmatic consumers.
|
|
493
|
+
|
|
494
|
+
### Rootless Podman (recommended)
|
|
495
|
+
|
|
496
|
+
The cleanest setup. Runs as your user, not root, so the security
|
|
497
|
+
exposure is limited to your user account rather than the entire host —
|
|
498
|
+
and matches signalk-container's default behaviour.
|
|
499
|
+
|
|
500
|
+
On the host, ensure the user-scoped podman socket is enabled:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
systemctl --user enable --now podman.socket
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Then in your compose / `podman run`:
|
|
450
507
|
|
|
451
508
|
```yaml
|
|
452
509
|
services:
|
|
453
510
|
signalk:
|
|
454
|
-
image: signalk
|
|
511
|
+
image: your-signalk-image-with-podman-remote
|
|
512
|
+
user: "${UID}:${GID}" # match the uid that owns the host's podman socket
|
|
455
513
|
volumes:
|
|
456
|
-
- /
|
|
457
|
-
|
|
514
|
+
- /run/user/${UID}/podman/podman.sock:/run/user/${UID}/podman/podman.sock
|
|
515
|
+
environment:
|
|
516
|
+
- CONTAINER_HOST=unix:///run/user/${UID}/podman/podman.sock
|
|
458
517
|
```
|
|
459
518
|
|
|
460
|
-
|
|
461
|
-
it from the host as shown above. Containers managed by the plugin
|
|
462
|
-
become **siblings** of the Signal K container, not nested.
|
|
519
|
+
Your image's Dockerfile should include `podman` or `podman-remote`:
|
|
463
520
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
521
|
+
```dockerfile
|
|
522
|
+
RUN apt-get update && apt-get install -y podman # Debian/Ubuntu
|
|
523
|
+
# or:
|
|
524
|
+
RUN dnf install -y podman-remote # Fedora/RHEL
|
|
525
|
+
```
|
|
469
526
|
|
|
470
|
-
### Podman
|
|
527
|
+
### Rootful Podman
|
|
471
528
|
|
|
472
529
|
```yaml
|
|
473
530
|
services:
|
|
474
531
|
signalk:
|
|
475
|
-
image: signalk
|
|
532
|
+
image: your-signalk-image-with-podman
|
|
476
533
|
volumes:
|
|
477
|
-
-
|
|
478
|
-
- /usr/bin/podman:/usr/bin/podman:ro
|
|
534
|
+
- /run/podman/podman.sock:/run/podman/podman.sock
|
|
479
535
|
environment:
|
|
480
|
-
-
|
|
536
|
+
- CONTAINER_HOST=unix:///run/podman/podman.sock
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Docker
|
|
540
|
+
|
|
541
|
+
```yaml
|
|
542
|
+
services:
|
|
543
|
+
signalk:
|
|
544
|
+
image: your-signalk-image-with-docker-cli
|
|
545
|
+
volumes:
|
|
546
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
547
|
+
group_add:
|
|
548
|
+
- "<docker-gid-from-host>" # `getent group docker | cut -d: -f3`
|
|
481
549
|
```
|
|
482
550
|
|
|
483
|
-
|
|
484
|
-
|
|
551
|
+
> [!warning]
|
|
552
|
+
> Mounting `/var/run/docker.sock` gives the container **root-equivalent
|
|
553
|
+
> access to the host**. Anyone who compromises Signal K (including via
|
|
554
|
+
> a malicious plugin) can take over the entire host. Prefer rootless
|
|
555
|
+
> Podman for production.
|
|
485
556
|
|
|
486
557
|
### Networking caveats
|
|
487
558
|
|
|
@@ -493,13 +564,9 @@ network namespace. This affects:
|
|
|
493
564
|
(add it externally or via the same compose file)
|
|
494
565
|
- `host.containers.internal` from spawned containers points to the host
|
|
495
566
|
itself, not the Signal K container — use Signal K's container name
|
|
496
|
-
for direct communication
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
For the simplest experience with managed containers, run **Signal K
|
|
501
|
-
natively on the host** rather than in a container. The plugin and its
|
|
502
|
-
ecosystem (signalk-questdb, signalk-grafana) are designed for this case.
|
|
567
|
+
for direct communication. signalk-container 1.8.0+ adds this hostname
|
|
568
|
+
to Docker containers automatically (Podman already provides it); set
|
|
569
|
+
`ContainerConfig.extraHosts` to override it or to add other hostnames.
|
|
503
570
|
|
|
504
571
|
## License
|
|
505
572
|
|
package/dist/containers.d.ts
CHANGED
|
@@ -363,6 +363,13 @@ export declare function getLiveContainerConfig(runtime: ContainerRuntimeInfo, na
|
|
|
363
363
|
export declare function diffContainerConfig(requested: ContainerConfig, live: LiveContainerConfig, runtime: ContainerRuntimeInfo, prior?: ContainerConfig): {
|
|
364
364
|
drifted: string[];
|
|
365
365
|
};
|
|
366
|
+
/**
|
|
367
|
+
* Pull function injected into `ensureRunning` for testability. Production
|
|
368
|
+
* uses the module-level `pullImage` (which shells out via `execRuntimeLong`,
|
|
369
|
+
* not the injectable `ExecFn`). Tests pass a stub to assert call counts
|
|
370
|
+
* and simulate offline failures without touching the network.
|
|
371
|
+
*/
|
|
372
|
+
type PullFn = (runtime: ContainerRuntimeInfo, image: string, onProgress?: (msg: string) => void) => Promise<void>;
|
|
366
373
|
export declare function ensureRunning(runtime: ContainerRuntimeInfo, name: string, config: ContainerConfig, debug: (msg: string) => void, options?: HealthCheckOptions, exec?: ExecFn,
|
|
367
374
|
/**
|
|
368
375
|
* Prior `ContainerConfig` from the previous `ensureRunning` call within
|
|
@@ -372,7 +379,7 @@ export declare function ensureRunning(runtime: ContainerRuntimeInfo, name: strin
|
|
|
372
379
|
* overwriting it; on the first call (or after a Signal K restart) this
|
|
373
380
|
* will be undefined and only positive drift is detected.
|
|
374
381
|
*/
|
|
375
|
-
prior?: ContainerConfig, _postRecreate?: boolean): Promise<void>;
|
|
382
|
+
prior?: ContainerConfig, _postRecreate?: boolean, _pull?: PullFn): Promise<void>;
|
|
376
383
|
export declare function startContainer(runtime: ContainerRuntimeInfo, name: string): Promise<void>;
|
|
377
384
|
export declare function stopContainer(runtime: ContainerRuntimeInfo, name: string): Promise<void>;
|
|
378
385
|
export declare function removeContainer(runtime: ContainerRuntimeInfo, name: string, exec?: ExecFn): Promise<void>;
|
|
@@ -593,4 +600,5 @@ export declare function findAvailablePort(preferred: number): Promise<number>;
|
|
|
593
600
|
*/
|
|
594
601
|
export declare function resolveSignalkNetworks(runtime: ContainerRuntimeInfo, debug?: (msg: string) => void): Promise<string[] | null>;
|
|
595
602
|
export declare function waitForReady(url: string, timeoutMs?: number, intervalMs?: number): Promise<void>;
|
|
603
|
+
export {};
|
|
596
604
|
//# sourceMappingURL=containers.d.ts.map
|
package/dist/containers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,sBAAsB,EAItB,qBAAqB,EAEtB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,sBAAsB,EAItB,qBAAqB,EAEtB,MAAM,cAAc,CAAC;AAatB;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,GAAE,OAAe,GACxB,MAAM,CAOR;AAcD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG,SAAS,EACxD,KAAK,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GAC5C;IACD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D,CAuCA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EACD;IACE,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D,GACD,SAAS,EACb,cAAc,EAAE,KAAK,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAChE,cAAc,EAAE,KAAK,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAChE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,KAAK,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBlD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EACnE,KAAK,EAAE,WAAW,EAClB,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAClC,IAAI,CAUN;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EAC7D,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAClC,IAAI,CAON;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC9B,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACtC,GACA,sBAAsB,CAgBxB;AAED,kEAAkE;AAClE,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAe9B;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,GACZ;IAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAqB/C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB;AAUD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CAwBR;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,oBAAoB,EAC7B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,oBAAoB,EAC7B,gBAAgB,EAAE,MAAM,EACxB,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,oBAAoB,EAC7B,aAAa,EAAE,MAAM,EACrB,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,oBAAoB,EAC7B,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,cAAc,CAAC,CAoCzB;AAED;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,CACnB,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,OAAO,YAAY,EAAE,uBAAuB,CAAC,CA2EvD;AAmBD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAkC1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAUrC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACzC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;;;;;;;OAUG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CA0JrC;AAqGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,eAAe,EAC1B,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,oBAAoB,EAC7B,KAAK,CAAC,EAAE,eAAe,GACtB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAyJvB;AAmFD;;;;;GAKG;AACH,KAAK,MAAM,GAAG,CACZ,OAAO,EAAE,oBAAoB,EAC7B,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAE5B,OAAO,CAAC,EAAE,kBAAkB,EAC5B,IAAI,GAAE,MAAoB;AAC1B;;;;;;;GAOG;AACH,KAAK,CAAC,EAAE,eAAe,EACvB,aAAa,GAAE,OAAe,EAC9B,KAAK,GAAE,MAAkB,GACxB,OAAO,CAAC,IAAI,CAAC,CA8Jf;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAMf;AA4BD,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC,CA6B1B;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CAY5D;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAG/D;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,EAC7B,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,oBAAoB,EAC7B,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAW7E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,8BAA8B,IAAI,MAAM,EAAE,CAQzD;AAgCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,oBAAoB,EAC7B,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,EAC7B,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACtC,OAAO,CAAC,MAAM,CAAC,CA2EjB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EAAE,GACvB,wBAAwB,GAAG,IAAI,CAgCjC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,EAC7B,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACtC,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CA6C1C;AAcD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEtD;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS1E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,oBAAoB,EAC7B,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACtC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAsC1B;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,MAAc,EACzB,UAAU,GAAE,MAAY,GACvB,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
package/dist/containers.js
CHANGED
|
@@ -2,6 +2,8 @@ import * as net from "node:net";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { execRuntime, execRuntimeLong, isContainerized, spawnRuntimeStreaming, userMappingFlags, } from "./runtime.js";
|
|
4
4
|
import { resourceFlagsForRun } from "./resources.js";
|
|
5
|
+
import { classifyTag } from "./updates/tagClassifier.js";
|
|
6
|
+
import { isOfflineError } from "./updates/offline.js";
|
|
5
7
|
const CONTAINER_PREFIX = "sk-";
|
|
6
8
|
function prefixedName(name) {
|
|
7
9
|
return name.startsWith(CONTAINER_PREFIX)
|
|
@@ -1147,7 +1149,7 @@ export async function ensureRunning(runtime, name, config, debug, options, exec
|
|
|
1147
1149
|
* overwriting it; on the first call (or after a Signal K restart) this
|
|
1148
1150
|
* will be undefined and only positive drift is detected.
|
|
1149
1151
|
*/
|
|
1150
|
-
prior, _postRecreate = false) {
|
|
1152
|
+
prior, _postRecreate = false, _pull = pullImage) {
|
|
1151
1153
|
const state = await getContainerState(runtime, name, exec);
|
|
1152
1154
|
const fullName = prefixedName(name);
|
|
1153
1155
|
const imageRef = qualifyImage(config.digest
|
|
@@ -1167,7 +1169,48 @@ prior, _postRecreate = false) {
|
|
|
1167
1169
|
return false;
|
|
1168
1170
|
debug(`Container ${fullName} config drift detected (${drifted.join(", ")}); recreating`);
|
|
1169
1171
|
await removeContainer(runtime, name, exec);
|
|
1170
|
-
await ensureRunning(runtime, name, config, debug, options, exec, prior, true);
|
|
1172
|
+
await ensureRunning(runtime, name, config, debug, options, exec, prior, true, _pull);
|
|
1173
|
+
return true;
|
|
1174
|
+
};
|
|
1175
|
+
// Floating-tag digest drift: pull the tag, compare the registry-fresh
|
|
1176
|
+
// image-id to the running container's image-id, treat a mismatch as drift.
|
|
1177
|
+
// Skipped silently on offline or any pull/inspect error — update probing
|
|
1178
|
+
// must never block startup. `config.digest` set means the caller already
|
|
1179
|
+
// pins to a digest; nothing to probe.
|
|
1180
|
+
const checkAndRecreateOnDigestDrift = async () => {
|
|
1181
|
+
if (!config.autoUpdateOnFloatingTag)
|
|
1182
|
+
return false;
|
|
1183
|
+
if (config.digest)
|
|
1184
|
+
return false;
|
|
1185
|
+
if (classifyTag(config.tag) !== "floating")
|
|
1186
|
+
return false;
|
|
1187
|
+
const fullImage = `${config.image}:${config.tag}`;
|
|
1188
|
+
try {
|
|
1189
|
+
await _pull(runtime, qualifyImage(fullImage, runtime), debug);
|
|
1190
|
+
}
|
|
1191
|
+
catch (err) {
|
|
1192
|
+
if (isOfflineError(err)) {
|
|
1193
|
+
debug(`Container ${fullName} floating-tag digest check skipped (offline)`);
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
debug(`Container ${fullName} floating-tag digest check skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
1197
|
+
}
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
// Compare image-ids (local content-store hash), not manifest digests.
|
|
1201
|
+
// `getImageDigest` returns the local `.Id` for both an `image:tag` ref
|
|
1202
|
+
// and a container name (via `.Image` on the container) — like-for-like.
|
|
1203
|
+
// Mixing in a `RepoDigest`-based identity here would always show drift
|
|
1204
|
+
// because RepoDigest and image-id are different namespaces. The same
|
|
1205
|
+
// image-id-vs-image-id comparison is what updates/service.ts uses.
|
|
1206
|
+
const remoteId = await getImageDigest(runtime, qualifyImage(fullImage, runtime), exec);
|
|
1207
|
+
const liveId = await getImageDigest(runtime, prefixedName(name), exec);
|
|
1208
|
+
if (!remoteId || !liveId || remoteId === liveId) {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
debug(`Container ${fullName} floating-tag digest drift detected (${liveId.slice(0, 19)}… → ${remoteId.slice(0, 19)}…); recreating`);
|
|
1212
|
+
await removeContainer(runtime, name, exec);
|
|
1213
|
+
await ensureRunning(runtime, name, config, debug, options, exec, prior, true, _pull);
|
|
1171
1214
|
return true;
|
|
1172
1215
|
};
|
|
1173
1216
|
switch (state) {
|
|
@@ -1180,6 +1223,8 @@ prior, _postRecreate = false) {
|
|
|
1180
1223
|
}
|
|
1181
1224
|
if (await checkAndRecreateOnDrift("already running"))
|
|
1182
1225
|
return;
|
|
1226
|
+
if (await checkAndRecreateOnDigestDrift())
|
|
1227
|
+
return;
|
|
1183
1228
|
debug(`Container ${fullName} already running`);
|
|
1184
1229
|
return;
|
|
1185
1230
|
}
|
|
@@ -1196,6 +1241,8 @@ prior, _postRecreate = false) {
|
|
|
1196
1241
|
}
|
|
1197
1242
|
if (await checkAndRecreateOnDrift("stopped"))
|
|
1198
1243
|
return;
|
|
1244
|
+
if (await checkAndRecreateOnDigestDrift())
|
|
1245
|
+
return;
|
|
1199
1246
|
debug(`Starting stopped container ${fullName}`);
|
|
1200
1247
|
const startResult = await exec(runtime, ["start", fullName]);
|
|
1201
1248
|
if (startResult.exitCode !== 0) {
|