signalk-container 1.5.0 → 1.6.0

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.
@@ -0,0 +1,161 @@
1
+ language: en-US
2
+
3
+ reviews:
4
+ profile: assertive
5
+ # dist/ is the TS+webpack build output; checked in for the App Store
6
+ # install path, but never authored. Exclude here so the review engine
7
+ # never opens it.
8
+ path_filters:
9
+ - "!dist/**"
10
+ auto_review:
11
+ enabled: true
12
+ drafts: false
13
+ high_level_summary_instructions: |
14
+ Write all summaries in present tense.
15
+ Describe what the code does, not what it did.
16
+ Example: "This PR adds feature X" instead of "This PR added feature X"
17
+
18
+ path_instructions:
19
+ - path: "**/*"
20
+ instructions: |
21
+ ## Repository workflow
22
+
23
+ signalk-container ships as an npm package consumed by Signal K
24
+ plugins. The maintainer owns the release process — version bumps
25
+ happen in their own `chore(release): X.Y.Z` PR, separate from
26
+ feature/fix work. **Do not include `package.json` version changes
27
+ in feature or fix PRs.** Flag any PR that mixes a `"version"` bump
28
+ with non-release changes.
29
+
30
+ Other workflow rules to enforce:
31
+ - Branch names use hyphens, not slashes (`fix-something`, not
32
+ `fix/something`). The Signal K server convention applies here.
33
+ - Angular conventional commits: `<type>(<scope>): <subject>`.
34
+ Subject ≤ 50 chars, imperative mood, no period.
35
+ - No `Co-Authored-By` lines and no "Generated with Claude Code"
36
+ attribution in commit messages or PR bodies.
37
+
38
+ ## Echo comments
39
+
40
+ Flag any comment that merely restates what the code already says.
41
+ Examples to flag:
42
+ - `// Sets the age` above a function named `setAge()`
43
+ - `// Loop through items` above a `for` loop
44
+ - `// returns null on failure` next to `return null` in an error
45
+ branch
46
+ These add noise without adding meaning. Request removal or
47
+ replacement with a comment explaining *why*, not *what*.
48
+
49
+ ## Leftover crumbs from intermediate commits
50
+
51
+ Check for references to things that existed in earlier commits of
52
+ this PR but are no longer present — removed variables, old function
53
+ names, deleted files, superseded approaches. These are confusing
54
+ to future readers. Flag any comments, docs, or code that refer to
55
+ something not present in the current state of the branch.
56
+
57
+ ## Documentation drift risk
58
+
59
+ Flag any `.md` file that contains detailed implementation steps,
60
+ specific API call sequences, code snippets, or configuration
61
+ values that are likely to fall out of sync as the code evolves.
62
+ Documentation should describe architecture and how things work
63
+ conceptually — not step-by-step instructions that duplicate or
64
+ shadow the code itself.
65
+
66
+ Specifically for this repo: the API reference in
67
+ `doc/plugin-developer-guide.md` and `README.md`'s API table must
68
+ stay aligned with `ContainerManagerApi` in `src/types.ts`. Flag
69
+ any new method on the type that is missing from either doc, or
70
+ any doc method whose JSDoc-style signature has drifted from the
71
+ type.
72
+
73
+ ## Implementation status in documentation
74
+
75
+ Flag any `.md` file that describes implementation progress,
76
+ status, or build steps (e.g. "Step 3: implement X", "TODO: add Y",
77
+ "currently implemented as Z"). This belongs in PR descriptions or
78
+ commit messages, not in documentation. Documentation should
79
+ describe how things work, not how they were built or what stage
80
+ they are in.
81
+
82
+ Architecture decisions and design rationale are fine. Build
83
+ narratives are not.
84
+
85
+ ## Unchecked items in test plans
86
+
87
+ If a PR description or any `.md` file contains a checklist with
88
+ unchecked items, flag it. Either the work is incomplete, or the
89
+ checklist should be removed before merge.
90
+
91
+ ## Runtime concerns
92
+
93
+ signalk-container shells out to `podman`/`docker` via
94
+ `execRuntime` (`src/runtime.ts`). When reviewing changes to
95
+ `src/containers.ts`, `src/jobs.ts`, `src/resources.ts`, or
96
+ `src/updates/`:
97
+ - Every new external command should accept an injectable `exec`
98
+ parameter so tests can stub it (see existing
99
+ `ExecFn = execRuntime` pattern). Flag new direct `execRuntime`
100
+ calls in exported helpers.
101
+ - Prefer Go-template format strings (`inspect --format '{{...}}'`)
102
+ for diff/state probes over JSON parsing of full inspect output —
103
+ they work uniformly across podman and docker. See
104
+ `getLiveResources` / `getLiveContainerConfig` for the canonical
105
+ pattern.
106
+ - Podman bind mounts get a `:Z` suffix for SELinux relabel;
107
+ named volumes (no leading `/`) must NOT receive `:Z` — podman
108
+ rejects it. Use `volumeArg` in `src/containers.ts` rather than
109
+ building bind strings inline.
110
+ - Docker reports `HostConfig.NetworkMode` as `"default"` or
111
+ `"bridge"`; podman rootless reports `"slirp4netns"` or
112
+ `"pasta"`. The diff path canonicalizes these. Do not introduce
113
+ new networkMode comparisons without going through
114
+ `canonicalNetworkMode`.
115
+
116
+ ## Tests
117
+
118
+ - The test runner is `node:test`. Tests live in `src/test/*.ts`,
119
+ compile to `dist/test/*.js`, run via `node --test
120
+ "dist/test/**/*.test.js"`. The glob must be double-quoted so
121
+ Windows actually expands it (single quotes are passed through
122
+ literally by cmd).
123
+ - Container-integration tests (`runJobCallbacks.test.ts`) gate
124
+ on `hasContainerRuntime()` which returns `null` on Windows.
125
+ Do not add new integration tests that pull real Linux images
126
+ without the same guard.
127
+ - Unit-style tests inject `fakeExec` rather than touching the
128
+ real runtime. New tests should follow that pattern — see
129
+ `src/test/getLiveResources.test.ts` and
130
+ `src/test/diffContainerConfig.test.ts` for the canonical
131
+ shape.
132
+
133
+ ## Cross-plugin API surface
134
+
135
+ Consumer plugins access signalk-container via
136
+ `(globalThis as any).__signalk_containerManager` and then call
137
+ `await containers.whenReady()` before any other API call. Any
138
+ change to `ContainerManagerApi` in `src/types.ts` is a public
139
+ contract change — flag missing JSDoc on new methods, removed
140
+ methods, or signature-breaking changes that are not called out
141
+ in the PR description.
142
+
143
+ ## What NOT to flag
144
+
145
+ Do not flag the following — they are intentional:
146
+ - Single-character variable names inside Go-template format
147
+ strings (e.g. `{{.Config.Image}}`). These are runtime syntax,
148
+ not JS code.
149
+ - The `_postRecreate` recursion-guard parameter on
150
+ `ensureRunning` — the underscore prefix is intentional, marks
151
+ it as an internal-use-only param.
152
+ - Tests skipping on `process.platform === "win32"` for
153
+ integration tests that pull Linux images. The Windows runner
154
+ has Docker Desktop in Windows-container mode; there is no
155
+ easy way around it.
156
+
157
+ ## Scope
158
+
159
+ Focus on files changed since master. Think carefully about how
160
+ changes interact with existing code and documentation — not just
161
+ the diff in isolation.
package/AGENTS.md ADDED
@@ -0,0 +1,150 @@
1
+ # signalk-container
2
+
3
+ Shared container runtime management (Podman/Docker) for Signal K plugins. This plugin runs _inside_ the Signal K server and exposes a cross-plugin API at `globalThis.__signalk_containerManager` so other plugins (questdb, grafana, mayara, etc.) can manage their own containers without each implementing their own dockerode integration.
4
+
5
+ Key components:
6
+
7
+ - **`src/index.ts`** — Signal K plugin entrypoint. Wires the runtime probe, exposes the `ContainerManagerApi` on `globalThis`, owns the REST endpoints (`/plugins/signalk-container/api/...`) and the React config panel mount.
8
+ - **`src/containers.ts`** — Thin runtime layer. Pure functions over `execRuntime` for lifecycle (`ensureRunning`, `removeContainer`, `getContainerState`), config-drift detection (`getLiveContainerConfig`, `diffContainerConfig`), and live-state probes (`getLiveResources`, `getActualPortBindings`).
9
+ - **`src/jobs.ts`** — One-shot helper containers via `runJob`. Used by chart-provider and similar plugins that need short-lived workers (GDAL, tippecanoe, etc.).
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
+ - **`src/runtime.ts`** — Runtime detection (`podman` vs `docker`), version probing, `execRuntime`/`execRuntimeLong` dispatch, `isContainerized()` self-detection.
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
+ - **`public/`** — React config panel served via Module Federation into the Signal K Admin UI.
14
+
15
+ ## Code Quality Principles
16
+
17
+ ### Scope and Complexity
18
+
19
+ Follow YAGNI, SOLID, DRY, and KISS. Only make changes that are directly requested or clearly necessary. A bug fix does not need surrounding code cleaned up. A simple feature does not need extra configurability.
20
+
21
+ Do not add error handling, fallbacks, or validation for scenarios that cannot happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input from the config panel, runtime command output, REST request bodies).
22
+
23
+ ### General Standards
24
+
25
+ - Self-documenting code; comments explain _why_, not _what_ — no echo comments restating what the code already says.
26
+ - Documentation describes current state, not development history. Avoid "previously this did X" or "added in PR #N" in source comments — that information belongs in git, not in the code.
27
+ - No magic numbers; use named constants. The `FIELDS_THAT_CANNOT_LIVE_UNSET` set in `src/resources.ts` is the canonical example.
28
+
29
+ ### Type Safety
30
+
31
+ - **All new code in TypeScript.** No new `.js` source files.
32
+ - Reuse types from `src/types.ts` rather than redefining. `ContainerConfig`, `ContainerRuntimeInfo`, `ContainerResourceLimits`, `LiveContainerConfig`, `PortBinding`, etc. are the public vocabulary — extend them rather than creating parallel shapes.
33
+ - Avoid `any` and equivalent escape hatches. The one allowed use is `(globalThis as any).__signalk_containerManager` because the consumer-plugin side cannot import `ContainerManagerApi` without taking a dependency.
34
+ - Validate external inputs at system boundaries — runtime command output, REST request bodies, plugin config schema. Internal calls trust their callers.
35
+
36
+ ### Testing
37
+
38
+ - Test runner is `node:test`. Tests in `src/test/*.ts`, compiled to `dist/test/*.js`, run via `node --test "dist/test/**/*.test.js"`. The glob **must** be double-quoted so Windows expands it.
39
+ - All new code requires tests. Test behavior at the function boundary, not internal control flow.
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
+ - 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
+ - 333 tests today across 59 suites; expect them all green on every commit.
43
+
44
+ ## Runtime Invariants
45
+
46
+ These are non-obvious rules baked into the runtime layer. Breaking them produces silent failures or runtime-specific bugs.
47
+
48
+ ### Podman SELinux flag
49
+
50
+ `volumeArg(hostPath, containerPath, runtime)` adds `:Z` for podman bind mounts (Fedora/RHEL SELinux relabel). Named volumes — host strings without a leading `/` or `.` — MUST NOT receive `:Z`; podman rejects them with `"invalid option z for named volume"`. Always go through `volumeArg`, never build `-v host:container[:flags]` strings inline.
51
+
52
+ ### Volume source policy
53
+
54
+ `ContainerConfig.volumes` accepts either a bare host-path string (auto-create — the runtime creates the host dir if missing) or `{ source, ifMissing: 'create' | 'skip' | 'abort' }` for per-volume policy. Classification happens in the API wrapper (`src/index.ts`) via `classifyVolumeSources` before `containers.ensureRunning` is called, so the diff and `buildRunArgs` both see the pre-filtered `Record<string, string>` map. The `lastConfigs` cache stores the post-filter shape — drift detection sees consistent state across calls.
55
+
56
+ `'skip'` and `'abort'` events fire `onVolumeIssue` in the options arg (`EnsureRunningOptions extends HealthCheckOptions`). Recovery events fire when a previously-missing source reappears and the container is recreated to include it; recovery tracking lives in a module-level `lastVolumeIssues: Map<name, ...>` in the wrapper. Handler errors are caught + logged at error level, never propagate.
57
+
58
+ Named volumes (source without leading `/`) always pass through; `ifMissing` only applies to host paths. `volumeSource()` in `containers.ts` is the single narrower from the union back to bare-string for the two call sites that consume `config.volumes` after classification (`buildRunArgs`, `diffContainerConfig`).
59
+
60
+ ### Podman image qualification
61
+
62
+ `qualifyImage("foo/bar:tag", podmanRuntime)` prefixes `docker.io/` when needed (podman requires fully qualified names unless `unqualified-search-registries` is set). Docker passes through. Use this everywhere we feed an image string to a runtime command.
63
+
64
+ ### Inspect-format diff pattern
65
+
66
+ When we need to read live container state, we use a single `inspect --format` call with a pipe-delimited Go-template format string:
67
+
68
+ ```gotemplate
69
+ {{.HostConfig.NanoCpus}}|{{.HostConfig.Memory}}|...
70
+ ```
71
+
72
+ This works uniformly across podman and docker, parses cheaply, and avoids the JSON-shape divergence between the two runtimes. `getLiveResources` and `getLiveContainerConfig` are the canonical examples. Do not introduce new live-state probes that parse full `inspect` JSON output.
73
+
74
+ ### networkMode canonicalization
75
+
76
+ Docker reports `HostConfig.NetworkMode` as `"default"` or `"bridge"` when no `--network` was passed. Podman rootless reports `"slirp4netns"` or `"pasta"`. These are runtime defaults equivalent to "user did not request a specific network." `canonicalNetworkMode()` in `src/containers.ts` normalizes all of them to `""` so comparison against a requested `undefined`/`""` is correct. Any new comparison of `networkMode` between requested and live state must go through this helper.
77
+
78
+ ### Auto-recreate on config drift
79
+
80
+ `ensureRunning` compares the requested `ContainerConfig` against the live container's effective config on every call. On drift across `image+tag`, `command`, `networkMode`, `env`, `volumes`, or `ports`, it removes and recreates the container transparently. `resources` follows the existing live-update path. Consumer plugins do not need (and should remove) per-plugin `${dataDir}.container-hash` files — this is centralized.
81
+
82
+ The diff has an optional `prior?: ContainerConfig` parameter for detecting "unset" drift (an env key previously set is now absent, a `command` previously set is now `undefined`). The wrapper in `src/index.ts` reads it from `lastConfigs` before overwriting.
83
+
84
+ ### Recursion guard in ensureRunning
85
+
86
+ After auto-recreate, `ensureRunning` recursively re-enters itself with `_postRecreate=true`. The underscore prefix marks this as an internal-use-only parameter — do not document it for consumer plugins, do not move it earlier in the signature. The guard breaks the loop if state somehow stays `running` or `stopped` after the `remove`.
87
+
88
+ ### Cross-plugin API surface
89
+
90
+ `ContainerManagerApi` in `src/types.ts` is the public contract. Adding methods is fine (additive). Removing or changing signatures is a semver-major change. Anything new on the interface must have a JSDoc comment so consumer plugins see it via TypeScript intellisense — the consumer side accesses it via `(globalThis as any).__signalk_containerManager` so JSDoc is its only documentation.
91
+
92
+ `whenReady()` (added in 1.6.0) is the canonical "wait for runtime detection to settle" call. Consumer plugins should use it instead of polling `getRuntime()` in a loop. Tests and code in this repo do not need it — they have direct access to the detection result.
93
+
94
+ ## Workflow Conventions
95
+
96
+ This repo is maintained by Dirk Wahrheit. Workflow is deliberate; AI tools should follow it strictly.
97
+
98
+ ### Branch and commit rules
99
+
100
+ - Branch names use **hyphens**, never slashes: `fix-something`, `feat-something`, `chore-release-1-6-0`. Signal K server maintainers reject slash names.
101
+ - Angular conventional commits: `<type>(<scope>): <subject>`. Types: `feat|fix|docs|style|refactor|test|chore|perf`. Subject ≤ 50 chars, imperative mood, no period.
102
+ - One logical change per commit. The history tells a story — each commit is a meaningful, self-contained step.
103
+ - No `Co-Authored-By` lines. No "Generated with Claude Code" attribution.
104
+
105
+ ### PR rules
106
+
107
+ - Never commit directly to `master`. Every change goes through a PR — including version bumps.
108
+ - Version bumps live in their own `chore(release): X.Y.Z` PR. Do not mix `package.json` version changes with feature/fix work.
109
+ - One logical change per PR. Refactors, behavior changes, and features belong in separate PRs. If a single change would produce multiple changelog entries, split it.
110
+ - PR titles describe what changes; PR bodies explain _why_ and summarize the approach, not the mechanics.
111
+ - No checkboxes in PR descriptions (Signal K maintainers convention). If you need a "Tested" section, list what was actually verified, not what's planned.
112
+ - PR descriptions must reflect reality. Never list speculative tests; only what actually ran.
113
+
114
+ ### Pre-PR checklist
115
+
116
+ Before pushing or opening a PR:
117
+
118
+ 1. `npm run format` — prettier write + eslint --fix
119
+ 2. `npm run build:all` — `clean && tsc && webpack && test`. All 333 tests must pass.
120
+ 3. `npm run ci-lint` — `eslint && prettier --check` (the strict-no-write variant CI runs)
121
+ 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.
122
+
123
+ 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.
124
+
125
+ ### Release flow
126
+
127
+ Tag-triggered (`.github/workflows/publish.yml` fires on `v*` tags):
128
+
129
+ 1. Branch `chore-release-X.Y.Z` off master.
130
+ 2. Bump `version` in `package.json`. There is no `package-lock.json` (the `~/.npmrc` setting disables it).
131
+ 3. Commit `chore(release): X.Y.Z`. Run the pre-PR checklist.
132
+ 4. Open PR, wait for explicit merge approval.
133
+ 5. After merge: `git checkout master && git pull --ff-only`, then `git tag vX.Y.Z && git push --tags`. The workflow creates the GitHub Release and runs `npm publish --provenance --access public` (prereleases use `--tag beta`).
134
+ 6. Never publish to npm without explicit approval.
135
+
136
+ Angular semver:
137
+
138
+ - `feat` → minor
139
+ - `fix` → patch
140
+ - `BREAKING CHANGE:` footer or `!` → major
141
+ - Pure `chore`/`docs`/`refactor` → patch (or skip release)
142
+
143
+ Dirk may override the bump rule (e.g. ship behavior change as minor even if technically API-compatible). Ask before assuming.
144
+
145
+ ## Common Pitfalls
146
+
147
+ - **Stale `dist/`**: TypeScript leaves prior compile output. After a branch switch that removes test files, `dist/test/` still holds old `.js` files and `node --test` runs them. The `build` script now starts with `rimraf dist` to avoid this; do not bypass with `tsc --watch` or partial rebuilds when running tests.
148
+ - **node_modules drift**: `node_modules/` in a long-running clone can lag the registry. After a `package.json` change involving a tooling dependency (prettier, typescript, eslint), run `npm install` before `npm run format` — formatter output between versions diverges and CI will reject the result.
149
+ - **cr review needs a commit**: cr only reviews committed changes. Run `git commit` first, then `cr review --plain`. Running it against the working tree produces "No files to review."
150
+ - **Windows runner is Windows containers**: GitHub-hosted Windows runners ship Docker Desktop in Windows-container mode. `docker --version` works; `docker pull alpine:3.19` does not. The `hasContainerRuntime()` helper returns `null` on Windows so integration tests skip cleanly. Do not try to "fix" the Windows runner — there's no Linux daemon available, only the skip.
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/README.md CHANGED
@@ -6,8 +6,9 @@ Instead of each plugin implementing its own container orchestration, they delega
6
6
 
7
7
  ## Features
8
8
 
9
- - **Runtime detection** -- Podman preferred, Docker fallback, podman-shim aware
9
+ - **Runtime detection** -- Podman preferred, Docker fallback, podman-shim aware. Consumer plugins `await containers.whenReady()` once instead of polling in a loop — resolves when the probe settles in either direction.
10
10
  - **Container lifecycle** -- pull, create, start, stop, remove with `sk-` prefix naming
11
+ - **Automatic config-drift recreation** -- `ensureRunning` compares the requested `ContainerConfig` against the live container on every call. If `image`, `tag`, `command`, `networkMode`, `env`, `volumes`, or `ports` differ, the container is removed and recreated transparently. Consumer plugins no longer need a per-plugin hash file to detect "config changed since last start." See the [developer guide](doc/plugin-developer-guide.md#container-config-changes).
11
12
  - **One-shot jobs** -- run containers for batch tasks (export, conversion, etc.)
12
13
  - **Update detection** -- centralized "is there a newer image?" service for all consumer plugins. Auto-detects semver vs floating tags (`:latest`, `:main`), offline-tolerant with persistent cache, emits Signal K notifications, visible inline in the config panel. See the [developer guide](doc/plugin-developer-guide.md#update-detection).
13
14
  - **Resource limits editor** -- interactive UI in the config panel for setting CPU/memory/PID caps per container. Values are applied live via `podman update` when possible (no downtime), falls back to recreate when needed. Stored overrides are minimized against the consumer plugin's defaults so a future default bump flows through automatically. See the [developer guide](doc/plugin-developer-guide.md#resource-limits).
@@ -17,6 +18,7 @@ Instead of each plugin implementing its own container orchestration, they delega
17
18
  - **Zero-config config root sharing** -- `signalkConfigRootMount` mounts the entire SignalK installation config (`~/.signalk/`) — for backup, audit, or config-sync tools that need the whole tree, not the per-plugin subdirectory.
18
19
  - **Zero-config container service connectivity** -- `signalkAccessiblePorts` lets the SignalK process connect back to a service running inside a managed container (e.g. an HTTP or TCP server). signalk-container picks the right networking strategy automatically — port binding on the host loopback for bare-metal deployments, or a shared Docker network with DNS for containerised ones. No host ports are exposed unnecessarily.
19
20
  - **SELinux support** -- `:Z` volume flags for Podman bind mounts on Fedora/RHEL; named volumes are handled correctly (`:Z` is not applied)
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).
20
22
  - **Podman image qualification** -- automatically prefixes `docker.io/` for short image names
21
23
  - **Cross-plugin API** -- other plugins use `globalThis.__signalk_containerManager`
22
24
 
@@ -70,7 +72,16 @@ if (!containers) {
70
72
  return;
71
73
  }
72
74
 
73
- // Start a long-running service container
75
+ // Wait for runtime detection to settle, then verify a runtime was found.
76
+ await containers.whenReady();
77
+ if (!containers.getRuntime()) {
78
+ app.setPluginError("No container runtime detected");
79
+ return;
80
+ }
81
+
82
+ // Start a long-running service container. ensureRunning compares this
83
+ // config against the live container and recreates on drift — no per-
84
+ // plugin hash file or remove() dance needed.
74
85
  await containers.ensureRunning("my-service", {
75
86
  image: "myorg/myimage",
76
87
  tag: "latest",
@@ -104,10 +115,11 @@ See [doc/plugin-developer-guide.md](doc/plugin-developer-guide.md) for the full
104
115
  | Method | Description |
105
116
  | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
117
  | `getRuntime()` | Returns `{ runtime, version, isPodmanDockerShim }` or `null` |
118
+ | `whenReady()` | Resolves once runtime detection settles (success OR failure). Replaces the polling-loop pattern; check `getRuntime()` after the await |
107
119
  | `pullImage(image, onProgress?)` | Pull a container image (auto-qualifies for Podman) |
108
120
  | `imageExists(image)` | Check if image exists locally |
109
121
  | `getImageDigest(imageOrContainer)` | Local image ID (sha256) for an image:tag or container |
110
- | `ensureRunning(name, config, options?)` | Create and start container if not running |
122
+ | `ensureRunning(name, config, options?)` | Create and start container if not running; auto-recreates on config drift across `image`, `tag`, `command`, `networkMode`, `env`, `volumes`, `ports` |
111
123
  | `start(name)` | Start a stopped container |
112
124
  | `stop(name)` | Stop a running container |
113
125
  | `remove(name)` | Stop and remove a container |
@@ -1,4 +1,4 @@
1
- import { ContainerConfig, ContainerInfo, ContainerRuntimeInfo, ContainerState, HealthCheckOptions } from "./types";
1
+ import { ContainerConfig, ContainerInfo, ContainerRuntimeInfo, ContainerState, HealthCheckOptions, VolumeIssue, VolumeSpec } from "./types";
2
2
  /**
3
3
  * Build the value for a `-v <source>:<dest>[:flags]` argument with the
4
4
  * correct SELinux relabel suffix for the runtime.
@@ -11,8 +11,95 @@ import { ContainerConfig, ContainerInfo, ContainerRuntimeInfo, ContainerState, H
11
11
  * inputs/outputs (jobs.ts) so the named-volume guard stays in one place.
12
12
  */
13
13
  export declare function volumeArg(hostPath: string, containerPath: string, runtime: ContainerRuntimeInfo, readOnly?: boolean): string;
14
+ /**
15
+ * Classify each volume entry against host-source existence and the
16
+ * declared `ifMissing` policy. Returns:
17
+ *
18
+ * - `kept`: the volumes that should be passed to the runtime,
19
+ * normalized to bare-string form (the shape `buildRunArgs` and
20
+ * `diffContainerConfig` already consume).
21
+ * - `skipped`: host-path volumes whose source is missing AND whose
22
+ * policy is `'skip'`. Caller should emit `onVolumeIssue`
23
+ * events for each.
24
+ * - `aborted`: host-path volumes whose source is missing AND whose
25
+ * policy is `'abort'`. Caller should emit `onVolumeIssue`
26
+ * events then throw.
27
+ *
28
+ * Bare-string entries and `ifMissing: 'create'` entries always end
29
+ * up in `kept` (the runtime auto-creates the host dir on demand,
30
+ * matching today's behaviour for the bare-string form).
31
+ *
32
+ * Named volumes (source without a leading `/` or `.`) always end up
33
+ * in `kept` regardless of policy — the runtime owns their lifecycle.
34
+ *
35
+ * `probe` is the host-path existence check; defaults to `existsSync`.
36
+ * Tests inject a stub so they don't touch the filesystem.
37
+ */
38
+ export declare function classifyVolumeSources(volumes: Record<string, string | VolumeSpec> | undefined, probe?: (path: string) => boolean): {
39
+ kept: Record<string, string>;
40
+ skipped: Array<{
41
+ containerPath: string;
42
+ source: string;
43
+ }>;
44
+ aborted: Array<{
45
+ containerPath: string;
46
+ source: string;
47
+ }>;
48
+ };
49
+ /**
50
+ * Given the volume issues from the last `ensureRunning` call (both
51
+ * skipped and aborted entries) and the current call's classification,
52
+ * return the list of entries that are now present and applied — i.e.
53
+ * recovered. Used to fire `onVolumeIssue` with `action: 'recovered'`
54
+ * after the inner `ensureRunning` has recreated the container to
55
+ * include the recovered mount.
56
+ *
57
+ * A volume has "recovered" when:
58
+ * - it was in `prior.skipped` or `prior.aborted` (i.e. missing on
59
+ * the last call), AND
60
+ * - it is NOT in the current call's `currentSkipped` or
61
+ * `currentAborted` (i.e. it is no longer missing), AND
62
+ * - its `containerPath` is in `kept` (i.e. the runtime will actually
63
+ * mount it this time).
64
+ *
65
+ * Pure function — no I/O.
66
+ */
67
+ export declare function collectRecoveredVolumes(prior: {
68
+ skipped: Array<{
69
+ containerPath: string;
70
+ source: string;
71
+ }>;
72
+ aborted: Array<{
73
+ containerPath: string;
74
+ source: string;
75
+ }>;
76
+ } | undefined, currentSkipped: Array<{
77
+ containerPath: string;
78
+ source: string;
79
+ }>, currentAborted: Array<{
80
+ containerPath: string;
81
+ source: string;
82
+ }>, kept: Record<string, string>): Array<{
83
+ containerPath: string;
84
+ source: string;
85
+ }>;
86
+ /**
87
+ * Invoke an `onVolumeIssue` callback safely. Synchronous throws AND
88
+ * rejected promises both route to `reportError`, so handler bugs (in
89
+ * either flavour) never escape as unhandled rejections.
90
+ *
91
+ * The declared callback type is `(event) => void | Promise<void>`,
92
+ * but TS allows assigning a plain async function where `void` is
93
+ * expected — the eventual rejection bypasses a naive `try/catch`.
94
+ * Wrap the call in `Promise.resolve(...).catch(...)` so the same
95
+ * error path catches both shapes.
96
+ *
97
+ * Pure-by-design: `reportError` is injected so tests can capture
98
+ * the message instead of writing to `app.error`.
99
+ */
100
+ export declare function safeInvokeVolumeIssue(handler: ((event: VolumeIssue) => void | Promise<void>) | undefined, event: VolumeIssue, reportError: (err: unknown) => void): void;
14
101
  export declare function qualifyImage(image: string, runtime: ContainerRuntimeInfo): string;
15
- export declare function imageExists(runtime: ContainerRuntimeInfo, image: string): Promise<boolean>;
102
+ export declare function imageExists(runtime: ContainerRuntimeInfo, image: string, exec?: ExecFn): Promise<boolean>;
16
103
  /**
17
104
  * Return the local image ID (sha256 digest) for a given image reference,
18
105
  * or null if the image is not present locally. Used for digest-drift
@@ -95,10 +182,84 @@ export declare function parsePortBindings(json: string): Map<number, PortBinding
95
182
  * had pre-validation.
96
183
  */
97
184
  export declare function getActualPortBindings(runtime: ContainerRuntimeInfo, name: string, exec?: ExecFn): Promise<Map<number, PortBinding[]>>;
98
- export declare function ensureRunning(runtime: ContainerRuntimeInfo, name: string, config: ContainerConfig, debug: (msg: string) => void, options?: HealthCheckOptions): Promise<void>;
185
+ /**
186
+ * The recreate-requiring half of a live container's effective config, parsed
187
+ * from a single `inspect` call. Mirror of the recreate-requiring fields on
188
+ * `ContainerConfig` after `buildRunArgs` would have rendered them. Resources
189
+ * have their own `getLiveResources` reader and live-update path.
190
+ *
191
+ * `image+tag` come from `.Config.Image` (the as-passed reference like
192
+ * `questdb/questdb:latest`), not `.Image` (the resolved sha256 digest) —
193
+ * digest-drift detection is the update service's job (`src/updates/`).
194
+ */
195
+ export interface LiveContainerConfig {
196
+ image: string;
197
+ tag: string;
198
+ command: string[] | null;
199
+ networkMode: string;
200
+ env: Map<string, string>;
201
+ binds: Array<{
202
+ host: string;
203
+ container: string;
204
+ }>;
205
+ portBindings: Map<string, PortBinding[]>;
206
+ }
207
+ /**
208
+ * Read the live equivalent of `ContainerConfig`'s recreate-requiring fields
209
+ * for an already-running container. Returns `null` on inspect failure (the
210
+ * caller treats that as "can't diff, fall back to early-return" — fail-safe).
211
+ *
212
+ * Used by `ensureRunning` to detect drift between the requested config and
213
+ * what the container was actually created with, so a recreate can fire
214
+ * automatically instead of silently ignoring the new config until restart.
215
+ */
216
+ export declare function getLiveContainerConfig(runtime: ContainerRuntimeInfo, name: string, exec?: ExecFn): Promise<LiveContainerConfig | null>;
217
+ /**
218
+ * Compare a requested `ContainerConfig` against the live container's
219
+ * effective config and return the list of fields that have drifted.
220
+ *
221
+ * Pure function — no I/O. The caller (`ensureRunning`) decides what to do
222
+ * with a non-empty drift list (today: log + remove + recreate).
223
+ *
224
+ * Field semantics:
225
+ * - image+tag: tag-string equality only, never digest. Update detection
226
+ * for floating tags (`:latest` digest drift) is the update service's job.
227
+ * - command: explicit drift when `requested.command` is set and differs
228
+ * from `live.command`. When `requested.command` is undefined, drift is
229
+ * reported only if a `prior.command` was set (i.e. the user is now
230
+ * unsetting it). Without a `prior`, an undefined `requested.command`
231
+ * can't be told apart from "image's baked CMD" so we skip — the
232
+ * wrapper's prior-config cache (`lastConfigs`) closes that loop on the
233
+ * second-and-later calls within a single signalk-container lifetime.
234
+ * - networkMode: runtime defaults (`bridge`, `slirp4netns`, etc.) are
235
+ * normalized to `""` and compared as equivalent to requested undefined.
236
+ * - env: requested keys must match live values. Additionally, any key
237
+ * present in `prior.env` but absent from `requested.env` is treated
238
+ * as drift (the user is unsetting it). Image-baked env keys not in
239
+ * either `requested.env` or `prior.env` are ignored — they were never
240
+ * ours.
241
+ * - volumes: trailing slashes stripped on both sides; `(host, container)`
242
+ * tuples compared as a Map keyed by container path. Live binds have
243
+ * their `:Z`/`:ro` flags already stripped by `getLiveContainerConfig`.
244
+ * - ports: container-port key compared as runtime-emitted (`9000/tcp`).
245
+ * Multiple host bindings per container port compared as a sorted set.
246
+ */
247
+ export declare function diffContainerConfig(requested: ContainerConfig, live: LiveContainerConfig, runtime: ContainerRuntimeInfo, prior?: ContainerConfig): {
248
+ drifted: string[];
249
+ };
250
+ export declare function ensureRunning(runtime: ContainerRuntimeInfo, name: string, config: ContainerConfig, debug: (msg: string) => void, options?: HealthCheckOptions, exec?: ExecFn,
251
+ /**
252
+ * Prior `ContainerConfig` from the previous `ensureRunning` call within
253
+ * this signalk-container lifetime, if any. Used to detect "unset" drift
254
+ * — env keys removed, `command` previously set and now undefined. The
255
+ * wrapper in `index.ts` reads from its `lastConfigs` cache before
256
+ * overwriting it; on the first call (or after a Signal K restart) this
257
+ * will be undefined and only positive drift is detected.
258
+ */
259
+ prior?: ContainerConfig, _postRecreate?: boolean): Promise<void>;
99
260
  export declare function startContainer(runtime: ContainerRuntimeInfo, name: string): Promise<void>;
100
261
  export declare function stopContainer(runtime: ContainerRuntimeInfo, name: string): Promise<void>;
101
- export declare function removeContainer(runtime: ContainerRuntimeInfo, name: string): Promise<void>;
262
+ export declare function removeContainer(runtime: ContainerRuntimeInfo, name: string, exec?: ExecFn): Promise<void>;
102
263
  export declare function listContainers(runtime: ContainerRuntimeInfo): Promise<ContainerInfo[]>;
103
264
  export declare function pruneImages(runtime: ContainerRuntimeInfo): Promise<{
104
265
  imagesRemoved: number;
@@ -1 +1 @@
1
- {"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"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"}