signalk-container 1.8.0-beta.1 → 1.8.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/README.md +25 -18
- package/dist/types.d.ts +15 -20
- package/dist/types.d.ts.map +1 -1
- package/doc/plugin-developer-guide.md +135 -1
- package/package.json +1 -1
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
|
|
@@ -144,28 +147,30 @@ See [doc/plugin-developer-guide.md](doc/plugin-developer-guide.md) for the full
|
|
|
144
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 |
|
|
145
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 |
|
|
146
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? }` |
|
|
147
151
|
|
|
148
152
|
## REST Endpoints
|
|
149
153
|
|
|
150
154
|
All mounted at `/plugins/signalk-container/api/`:
|
|
151
155
|
|
|
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.
|
|
156
|
+
| Method | Path | Description |
|
|
157
|
+
| ------ | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| GET | `/runtime` | Detected runtime info |
|
|
159
|
+
| GET | `/containers` | List managed containers |
|
|
160
|
+
| GET | `/containers/:name/state` | Container state |
|
|
161
|
+
| POST | `/containers/:name/start` | Start a stopped container |
|
|
162
|
+
| POST | `/containers/:name/stop` | Stop a running container |
|
|
163
|
+
| POST | `/containers/:name/remove` | Stop and remove a container |
|
|
164
|
+
| 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 |
|
|
165
|
+
| GET | `/containers/:name/logs/stream` | Server-Sent Events stream of live log lines. Closes when the container is removed or the client disconnects |
|
|
166
|
+
| POST | `/prune` | Prune dangling images |
|
|
167
|
+
| GET | `/updates` | List last update-check results |
|
|
168
|
+
| GET | `/updates/:pluginId` | Last update-check result for one plugin |
|
|
169
|
+
| POST | `/updates/:pluginId/check` | Force a fresh update check (HTTP 200 even when offline) |
|
|
170
|
+
| GET | `/containers/:name/resources` | Effective resource limits + user override |
|
|
171
|
+
| POST | `/containers/:name/resources` | Apply new resource limits (live or recreate). Body is a `ContainerResourceLimits` diff against the consumer plugin's default. |
|
|
172
|
+
| DELETE | `/containers/:name/resources` | Clear any user override and restore the consumer plugin's pristine default limits to the running container. |
|
|
173
|
+
| 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+). |
|
|
169
174
|
|
|
170
175
|
## Configuration
|
|
171
176
|
|
|
@@ -493,7 +498,9 @@ network namespace. This affects:
|
|
|
493
498
|
(add it externally or via the same compose file)
|
|
494
499
|
- `host.containers.internal` from spawned containers points to the host
|
|
495
500
|
itself, not the Signal K container — use Signal K's container name
|
|
496
|
-
for direct communication
|
|
501
|
+
for direct communication. signalk-container 1.8.0+ adds this hostname
|
|
502
|
+
to Docker containers automatically (Podman already provides it); set
|
|
503
|
+
`ContainerConfig.extraHosts` to override it or to add other hostnames.
|
|
497
504
|
|
|
498
505
|
### Recommended setup
|
|
499
506
|
|
package/dist/types.d.ts
CHANGED
|
@@ -204,19 +204,17 @@ export interface ContainerConfig {
|
|
|
204
204
|
* identity on the host — no recursive `chmod` sweeps needed.
|
|
205
205
|
*
|
|
206
206
|
* - omitted (default): emit a uid mapping that aligns the in-container
|
|
207
|
-
* process with the host caller.
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* `inImageUid`/`inImageGid` default to 0 (i.e. the image's root)
|
|
207
|
+
* process with the host caller. Rootless Podman uses user-namespace
|
|
208
|
+
* remapping; Docker and rootful Podman use direct UID/GID translation.
|
|
209
|
+
* The `inImageUid`/`inImageGid` default to 0 (i.e. the image's root)
|
|
211
210
|
* unless the consumer sets them to match the image's `USER`.
|
|
212
211
|
* - `{ inImageUid, inImageGid }`: same logic but with explicit
|
|
213
212
|
* in-image UID/GID. Use this when the image declares a non-root
|
|
214
|
-
* `USER` (e.g. `USER 1001`) so the
|
|
215
|
-
* starting point for translation.
|
|
216
|
-
* - `false`: opt out. No
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* user model.
|
|
213
|
+
* `USER` (e.g. `USER 1001`) so the rootless-Podman remap picks the
|
|
214
|
+
* right starting point for translation.
|
|
215
|
+
* - `false`: opt out. No uid-mapping flag emitted. The container
|
|
216
|
+
* runs with whatever the image's `USER` directive specifies.
|
|
217
|
+
* Use when the image requires root or manages its own user model.
|
|
220
218
|
*
|
|
221
219
|
* Mirrors `ContainerJobConfig.user` so the same translator drives both
|
|
222
220
|
* long-running managed containers and one-shot helper jobs.
|
|
@@ -339,13 +337,11 @@ export interface ContainerJobConfig {
|
|
|
339
337
|
* files written into bind-mounted output dirs land owned by the
|
|
340
338
|
* host signalk-server process, not by an unrelated container UID.
|
|
341
339
|
*
|
|
342
|
-
* The auto path
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* (
|
|
347
|
-
* `--userns=keep-id` is rootless-Podman-only — it errors out under
|
|
348
|
-
* rootful — which is why the runtime detection matters.)
|
|
340
|
+
* The auto path picks the right mechanism per runtime: Docker (any
|
|
341
|
+
* flavour) and rootful Podman use direct UID/GID translation; rootless
|
|
342
|
+
* Podman uses user-namespace remapping. The two forms achieve the same
|
|
343
|
+
* end via different mechanisms, which is why the runtime detection
|
|
344
|
+
* matters (the rootless-Podman flag errors out under rootful Podman).
|
|
349
345
|
*
|
|
350
346
|
* - Default (`undefined`): auto-align using `process.getuid()` /
|
|
351
347
|
* `process.getgid()`, assuming the image's USER directive is
|
|
@@ -770,9 +766,8 @@ export interface ContainerManagerApi {
|
|
|
770
766
|
export interface DoctorApi {
|
|
771
767
|
/**
|
|
772
768
|
* Run `image:tag` under the same uid mapping `ensureRunning` would
|
|
773
|
-
* use
|
|
774
|
-
*
|
|
775
|
-
* container can `touch /tmp/x` as the host caller.
|
|
769
|
+
* use for this `user` value and verify that the container can
|
|
770
|
+
* `touch /tmp/x` as the host caller.
|
|
776
771
|
*
|
|
777
772
|
* Returns `{ ok: true }` when the probe exits 0 and prints `"ok"`.
|
|
778
773
|
* Never throws — failure modes (non-zero exit, exec error, missing
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACb,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9C,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,WAAW,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC;IAC9C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,IAAI,GAAG,gBAAgB,GAAG,QAAQ,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -324,6 +324,8 @@ Volumes accept either a bare host-path string (auto-create — runtime creates t
|
|
|
324
324
|
|
|
325
325
|
For opt-in digest pinning, pass `digest` (`sha256:<64-hex>`) on the config and `pluginId` / `pluginVersion` on the options — see [Image Pinning Manifest](#image-pinning-manifest).
|
|
326
326
|
|
|
327
|
+
`ContainerConfig.user` controls the host-UID mapping for files the container creates on bind mounts — see [Host-UID Ownership](#host-uid-ownership). `ContainerConfig.extraHosts` lets you add hostname → IP entries to `/etc/hosts`; signalk-container automatically maps `host.containers.internal` to `host-gateway` on Docker (Podman has it natively), and a user value passed in `extraHosts` is respected as an override.
|
|
328
|
+
|
|
327
329
|
```typescript
|
|
328
330
|
await containers.ensureRunning("my-db", {
|
|
329
331
|
image: "postgres",
|
|
@@ -560,6 +562,121 @@ const history = await containers.manifest.getContainerHistory("questdb");
|
|
|
560
562
|
|
|
561
563
|
Returns `[]` if the container has no manifest entry (e.g. it predates the manifest layer or `ensureRunning` has never been called for it in this Signal K session).
|
|
562
564
|
|
|
565
|
+
### `containers.doctor.imageRunsAsUser(image, user?): Promise<ImageProbeResult>`
|
|
566
|
+
|
|
567
|
+
Probe whether `image` can run cleanly under the host-UID mapping signalk-container will emit for managed containers — i.e. that `/tmp` is writable for the host caller and the image doesn't depend on a writable `~/.<x>` or a root-only path. Use this _before_ adopting an unfamiliar image, instead of debugging a wedged container after the fact.
|
|
568
|
+
|
|
569
|
+
The probe starts the image with the same `--user` / `--userns` flags `ensureRunning` would emit for the given `user` value, executes `touch /tmp/x && echo ok`, and reports the result. Never throws — non-zero exit, missing binary, and exec failures all surface as `{ ok: false, error }`.
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
const probe = await containers.doctor.imageRunsAsUser("myorg/worker:1.2.3");
|
|
573
|
+
if (!probe.ok) {
|
|
574
|
+
app.setPluginError(`image not UID-compatible: ${probe.error}`);
|
|
575
|
+
app.debug(probe.output); // combined stdout/stderr from the probe run
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Pass `user` to test a specific mapping — e.g. an image whose `USER` directive sets `1001`:
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
await containers.doctor.imageRunsAsUser("myorg/worker:1.2.3", {
|
|
583
|
+
inImageUid: 1001,
|
|
584
|
+
inImageGid: 1001,
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Returns `{ ok: true, output: "ok\n" }` on success; `{ ok: false, output, error }` on failure. Available in signalk-container 1.8.0+.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Host-UID Ownership
|
|
593
|
+
|
|
594
|
+
Managed containers run by default under the **Signal K host user's UID/GID**, so files the container creates on bind-mounted host paths are owned by the same identity that runs Signal K. No recursive `chmod` sweeps, no "root-owned files in `~/.signalk`" surprises.
|
|
595
|
+
|
|
596
|
+
How the translator achieves that varies by runtime:
|
|
597
|
+
|
|
598
|
+
| Runtime | Mechanism |
|
|
599
|
+
| ---------------------------- | -------------------------------------------------------------------------------------- |
|
|
600
|
+
| Rootless Podman | User-namespace remapping that translates the in-image UID/GID back to the host caller. |
|
|
601
|
+
| Docker (rootless or rootful) | Direct UID/GID translation — the in-container process runs as the host caller's IDs. |
|
|
602
|
+
| Rootful Podman | Same direct UID/GID translation as Docker. |
|
|
603
|
+
| Windows | No translation — Docker Desktop handles UID/GID mapping internally. |
|
|
604
|
+
|
|
605
|
+
Consumer plugins do not have to call anything special — the default mapping just works for the typical case (image runs as root, container writes files that Signal K then reads). The exact CLI flags emitted for each runtime are an implementation detail of `userMappingFlags` in `src/runtime.ts`; consult it (and the matching tests) if you need to see the literal form for a given variant.
|
|
606
|
+
|
|
607
|
+
### When to set `ContainerConfig.user`
|
|
608
|
+
|
|
609
|
+
Only when the image declares a **non-root `USER`** directive. The `inImageUid` / `inImageGid` defaults are `0` (root); for an image with `USER 1001` you must tell the translator so the keep-id mapping picks the right starting point:
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
await containers.ensureRunning("my-worker", {
|
|
613
|
+
image: "ghcr.io/myorg/worker", // image declares USER 1001
|
|
614
|
+
tag: "1.2.3",
|
|
615
|
+
user: { inImageUid: 1001, inImageGid: 1001 },
|
|
616
|
+
// ...
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Get this wrong and rootless Podman will translate the in-image UID to the wrong host UID; the container will look fine until it writes a file and you discover the bind-mounted host path is owned by some random subuid.
|
|
621
|
+
|
|
622
|
+
### Opting out: `user: false`
|
|
623
|
+
|
|
624
|
+
Pass `false` if the image must run as root (or manages its own user model entirely) and you don't need host-aligned ownership on bind mounts:
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
await containers.ensureRunning("legacy-worker", {
|
|
628
|
+
image: "legacy/needs-root",
|
|
629
|
+
tag: "v1",
|
|
630
|
+
user: false, // no --user, no --userns
|
|
631
|
+
// ...
|
|
632
|
+
});
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
The container then runs with whatever the image's `USER` directive specifies, with no flag emitted by signalk-container.
|
|
636
|
+
|
|
637
|
+
### Image requirements for UID-aligned containers
|
|
638
|
+
|
|
639
|
+
An image that signalk-container starts under the default ownership mapping must:
|
|
640
|
+
|
|
641
|
+
- **Run cleanly as a non-root UID** passed via the runtime's user/userns mechanism. The image cannot assume root privileges for setup; anything that needs root must happen at build time.
|
|
642
|
+
- **Have a writable `HOME` for the runtime UID.** Image entrypoints that touch `~/.config` or any dotfile in `$HOME` fail otherwise. The right pattern is a dedicated non-root user with its own home directory created at build time (see the Dockerfile example below). When you can't change the user — e.g. an upstream image that hardcodes `HOME=/root` — set `ENV HOME=/tmp` (or another per-UID-writable path) in a thin wrapper image instead. Do not work around this by making `/root` world-writable; that erases the security boundary the dedicated user provides.
|
|
643
|
+
- **Have a writable `/tmp` for the runtime UID.** Default `1777` is fine; the doctor probe checks exactly this.
|
|
644
|
+
- **Not depend on root-only paths at runtime.** `/root`, ownership of `/var/run`, `chown` on bind-mounted host paths in the entrypoint — all break under the mapping.
|
|
645
|
+
- **Not call `chmod` or `chown` against host-mounted paths in its entrypoint.** The whole point of the UID alignment is that files are already correctly owned at creation; an entrypoint that re-asserts ownership is fighting the model and will silently no-op on FAT/exFAT/NTFS bind sources anyway.
|
|
646
|
+
|
|
647
|
+
If you control the image's Dockerfile, the cleanest pattern is a dedicated non-root user with a real home directory:
|
|
648
|
+
|
|
649
|
+
```dockerfile
|
|
650
|
+
RUN useradd -m -u 1001 -s /bin/sh worker
|
|
651
|
+
USER worker
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Then declare `user: { inImageUid: 1001, inImageGid: 1001 }` on the `ContainerConfig` so the rootless-Podman remap picks the right starting point.
|
|
655
|
+
|
|
656
|
+
If you're adopting a third-party image and it doesn't meet these criteria, options in rough order of preference:
|
|
657
|
+
|
|
658
|
+
1. Run the doctor probe (next subsection) to confirm the breakage and the failure mode.
|
|
659
|
+
2. File an upstream fix if the image is maintained — most popular images are happy to accept a non-root patch.
|
|
660
|
+
3. Use `user: false` to opt out of the mapping (the image runs as root, files on bind mounts are owned by root on the host — back to the pre-1.8.0 behavior).
|
|
661
|
+
4. Build a thin wrapper image that fixes the ownership requirements at build time.
|
|
662
|
+
|
|
663
|
+
### Verifying compatibility ahead of time
|
|
664
|
+
|
|
665
|
+
Before adopting an unfamiliar image, run `containers.doctor.imageRunsAsUser(image, user?)` (see API reference above). It catches the common failure modes (`/tmp` not writable for the host UID, image entrypoint touches `~` and `$HOME` isn't writable for that UID) up-front, instead of leaving you to debug a container stuck in a restart loop with a cryptic error.
|
|
666
|
+
|
|
667
|
+
### `ContainerJobConfig.user`
|
|
668
|
+
|
|
669
|
+
`runJob` accepts the same `user` field with identical semantics. Both code paths share the flag translator, so a probe that passes for `ensureRunning` also passes for `runJob`.
|
|
670
|
+
|
|
671
|
+
### Reading the resolved host identity
|
|
672
|
+
|
|
673
|
+
`getRuntime()` returns `hostUser: { uid, gid } | null` (null on Windows). Most plugins don't need it — the translator handles the mapping internally — but it's available for diagnostics:
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
const rt = containers.getRuntime();
|
|
677
|
+
app.debug(`Signal K runs as ${rt?.hostUser?.uid}:${rt?.hostUser?.gid}`);
|
|
678
|
+
```
|
|
679
|
+
|
|
563
680
|
---
|
|
564
681
|
|
|
565
682
|
## Image Pinning Manifest
|
|
@@ -948,6 +1065,11 @@ interface ContainerConfig {
|
|
|
948
1065
|
tag: string;
|
|
949
1066
|
digest?: string; // "sha256:<64-hex>" — pulls `image@digest`
|
|
950
1067
|
updateChannel?: string; // "tag:<pattern>" | "tag:latest" | "digest:explicit"
|
|
1068
|
+
extraHosts?: Record<string, string>; // hostname → IP or "host-gateway"
|
|
1069
|
+
// Host-UID mapping. Omit for the default (align with Signal K host user,
|
|
1070
|
+
// assuming the image runs as root). Set { inImageUid, inImageGid } when
|
|
1071
|
+
// the image declares a non-root USER. Set false to opt out entirely.
|
|
1072
|
+
user?: { inImageUid?: number; inImageGid?: number } | false;
|
|
951
1073
|
// ...remaining fields (ports, volumes, env, networkMode, command, resources, ...)
|
|
952
1074
|
}
|
|
953
1075
|
interface ConsumerManifest {
|
|
@@ -981,7 +1103,11 @@ interface HistoryEntry {
|
|
|
981
1103
|
triggeredBy?: string;
|
|
982
1104
|
}
|
|
983
1105
|
interface ContainerManagerApi {
|
|
984
|
-
getRuntime: () => {
|
|
1106
|
+
getRuntime: () => {
|
|
1107
|
+
runtime: string;
|
|
1108
|
+
version: string;
|
|
1109
|
+
hostUser?: { uid: number; gid: number } | null; // null on Windows
|
|
1110
|
+
} | null;
|
|
985
1111
|
whenReady: () => Promise<void>;
|
|
986
1112
|
ensureRunning: (
|
|
987
1113
|
name: string,
|
|
@@ -1037,6 +1163,12 @@ interface ContainerManagerApi {
|
|
|
1037
1163
|
list: () => Promise<ConsumerManifest[]>;
|
|
1038
1164
|
getContainerHistory: (containerName: string) => Promise<HistoryEntry[]>;
|
|
1039
1165
|
};
|
|
1166
|
+
doctor: {
|
|
1167
|
+
imageRunsAsUser: (
|
|
1168
|
+
image: string,
|
|
1169
|
+
user?: { inImageUid?: number; inImageGid?: number } | false,
|
|
1170
|
+
) => Promise<{ ok: boolean; output: string; error?: string }>;
|
|
1171
|
+
};
|
|
1040
1172
|
}
|
|
1041
1173
|
```
|
|
1042
1174
|
|
|
@@ -1123,6 +1255,8 @@ if (isContainerized()) {
|
|
|
1123
1255
|
// - host runtime must be exposed (docker.sock + binary)
|
|
1124
1256
|
// - spawned containers are siblings, not nested
|
|
1125
1257
|
// - host.containers.internal points to the actual host
|
|
1258
|
+
// (signalk-container 1.8.0+ adds this mapping for Docker too;
|
|
1259
|
+
// Podman has it natively)
|
|
1126
1260
|
// - shared networks need explicit setup
|
|
1127
1261
|
}
|
|
1128
1262
|
```
|