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.
- package/.coderabbit.yaml +161 -0
- package/AGENTS.md +150 -0
- package/CLAUDE.md +1 -0
- package/README.md +15 -3
- package/dist/containers.d.ts +165 -4
- package/dist/containers.d.ts.map +1 -1
- package/dist/containers.js +537 -27
- package/dist/containers.js.map +1 -1
- package/dist/index.js +117 -10
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +138 -5
- package/dist/types.d.ts.map +1 -1
- package/doc/plugin-developer-guide.md +114 -44
- package/doc/screenshots/signalk-container-1.png +0 -0
- package/doc/screenshots/signalk-container-2.png +0 -0
- package/package.json +10 -4
package/.coderabbit.yaml
ADDED
|
@@ -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
|
-
//
|
|
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 |
|
package/dist/containers.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
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,
|
|
1
|
+
{"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"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"}
|