signalk-container-helper 0.1.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/LICENSE +201 -0
- package/README.md +288 -0
- package/SPEC.md +309 -0
- package/dist/adopted-container.d.ts +55 -0
- package/dist/adopted-container.d.ts.map +1 -0
- package/dist/adopted-container.js +76 -0
- package/dist/adopted-container.js.map +1 -0
- package/dist/http.d.ts +66 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +76 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/managed-container.d.ts +203 -0
- package/dist/managed-container.d.ts.map +1 -0
- package/dist/managed-container.js +397 -0
- package/dist/managed-container.js.map +1 -0
- package/dist/manager.d.ts +44 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +50 -0
- package/dist/manager.js.map +1 -0
- package/dist/types.d.ts +493 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +36 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +56 -0
- package/dist/util.js.map +1 -0
- package/dist/version-source.d.ts +23 -0
- package/dist/version-source.d.ts.map +1 -0
- package/dist/version-source.js +12 -0
- package/dist/version-source.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { AppLike, ContainerConfig, ContainerManagerApi, ContainerState, EnsureRunningOptions, UpdateCheckResult } from "./types.js";
|
|
2
|
+
import { type VersionSourceSpec } from "./version-source.js";
|
|
3
|
+
import { type FetchLike } from "./http.js";
|
|
4
|
+
export interface ReadinessOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Container port to reach the app on — must also be declared in the
|
|
7
|
+
* config's `signalkAccessiblePorts` so signalk-container wires networking.
|
|
8
|
+
*/
|
|
9
|
+
port: number;
|
|
10
|
+
/** Health path polled on the resolved address. Default "/". */
|
|
11
|
+
path?: string;
|
|
12
|
+
/** Overall readiness deadline. Default 60_000. */
|
|
13
|
+
maxMs?: number;
|
|
14
|
+
/** Poll interval. Default 1_000. */
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
/** Per-request timeout. Default 2_000. */
|
|
17
|
+
requestTimeoutMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ManagedUpdateOptions {
|
|
20
|
+
versionSource: VersionSourceSpec;
|
|
21
|
+
/**
|
|
22
|
+
* Live getter for the currently-configured tag. Defaults to a getter of
|
|
23
|
+
* the tag most recently passed to start()/applyUpdate() (falling back to
|
|
24
|
+
* `defaultTag`). Provide your own when the user can edit the tag in the
|
|
25
|
+
* plugin options without restarting.
|
|
26
|
+
*/
|
|
27
|
+
currentTag?: () => string;
|
|
28
|
+
/** Query the running app for its version; preferred over currentTag. */
|
|
29
|
+
currentVersion?: () => Promise<string | null>;
|
|
30
|
+
/** e.g. "24h" (default), "1h" minimum. */
|
|
31
|
+
checkInterval?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ManagedContainerOptions {
|
|
34
|
+
app: AppLike;
|
|
35
|
+
/** Your plugin id (package.json name) — update-service registration key. */
|
|
36
|
+
pluginId: string;
|
|
37
|
+
/** Container name, unprefixed (signalk-container adds its namespace). */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Image repo without tag, e.g. "ghcr.io/questdb/questdb". */
|
|
40
|
+
image: string;
|
|
41
|
+
/**
|
|
42
|
+
* Build the full declarative ContainerConfig for a resolved tag. Called on
|
|
43
|
+
* every start/update — keep it pure so drift detection sees stable input.
|
|
44
|
+
*/
|
|
45
|
+
buildConfig: (tag: string) => ContainerConfig;
|
|
46
|
+
/** Tag used when start() is called without one. Default "latest". */
|
|
47
|
+
defaultTag?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Map a user-facing tag to the tag actually run, e.g. "auto" → a pinned
|
|
50
|
+
* tested version. Applied to start(), applyUpdate(), and route input.
|
|
51
|
+
*/
|
|
52
|
+
resolveTag?: (requested: string) => string;
|
|
53
|
+
/** Budget for waiting on signalk-container + runtime. Default 120_000. */
|
|
54
|
+
managerTimeoutMs?: number;
|
|
55
|
+
/** Poll interval while waiting for the manager global. Default 500. */
|
|
56
|
+
managerPollIntervalMs?: number;
|
|
57
|
+
/** HTTP readiness of the app inside the container (optional). */
|
|
58
|
+
readiness?: ReadinessOptions;
|
|
59
|
+
/** Register with signalk-container's update-detection service (optional). */
|
|
60
|
+
updates?: ManagedUpdateOptions;
|
|
61
|
+
/** Passed through to ensureRunning/recreate (onVolumeIssue, logs, …). */
|
|
62
|
+
ensureOptions?: EnsureRunningOptions;
|
|
63
|
+
/** Suppress setPluginStatus progress lines. Default false. */
|
|
64
|
+
quiet?: boolean;
|
|
65
|
+
/** Injectable fetch for tests. */
|
|
66
|
+
fetchImpl?: FetchLike;
|
|
67
|
+
}
|
|
68
|
+
export interface StartResult {
|
|
69
|
+
manager: ContainerManagerApi;
|
|
70
|
+
/** The resolved tag that was started. */
|
|
71
|
+
tag: string;
|
|
72
|
+
/** "http://host:port" when readiness was configured, else null. */
|
|
73
|
+
address: string | null;
|
|
74
|
+
}
|
|
75
|
+
/** Minimal router shape (Express-compatible) for registerUpdateRoutes. */
|
|
76
|
+
export interface RouterLike {
|
|
77
|
+
get(path: string, handler: (req: unknown, res: ResponseLike) => unknown): unknown;
|
|
78
|
+
post(path: string, handler: (req: unknown, res: ResponseLike) => unknown): unknown;
|
|
79
|
+
}
|
|
80
|
+
export interface ResponseLike {
|
|
81
|
+
status(code: number): ResponseLike;
|
|
82
|
+
json(body: unknown): unknown;
|
|
83
|
+
}
|
|
84
|
+
export interface UpdateRoutesOptions {
|
|
85
|
+
/** Route prefix. Default "/api/update" (→ GET …/check, POST …/apply). */
|
|
86
|
+
basePath?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Called after a successful apply with the tag as requested by the client
|
|
89
|
+
* (before resolveTag) and the tag actually run. Persist the REQUESTED tag
|
|
90
|
+
* (e.g. "auto") via app.savePluginOptions here so auto-tracking survives.
|
|
91
|
+
*/
|
|
92
|
+
onApplied?: (requestedTag: string, resolvedTag: string) => void | Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Full lifecycle wrapper for a plugin-owned ("managed") container — the
|
|
96
|
+
* signalk-backup / mayara archetype. One instance per container.
|
|
97
|
+
*/
|
|
98
|
+
export declare class ManagedContainer {
|
|
99
|
+
readonly options: ManagedContainerOptions;
|
|
100
|
+
/** Resolved after start(); also lazily re-read from the global. */
|
|
101
|
+
manager: ContainerManagerApi | undefined;
|
|
102
|
+
/** Tag most recently started or applied (resolved form). */
|
|
103
|
+
lastStartedTag: string | undefined;
|
|
104
|
+
/** "http://host:port" once readiness resolved an address. */
|
|
105
|
+
address: string | null;
|
|
106
|
+
private updatesRegistered;
|
|
107
|
+
constructor(options: ManagedContainerOptions);
|
|
108
|
+
private get app();
|
|
109
|
+
private status;
|
|
110
|
+
private fail;
|
|
111
|
+
private requireManager;
|
|
112
|
+
/** Validate a requested tag and apply the resolveTag mapping. */
|
|
113
|
+
private resolveTag;
|
|
114
|
+
/**
|
|
115
|
+
* Match this container in a listContainers() result.
|
|
116
|
+
*
|
|
117
|
+
* `unprefixedName` (supplied by signalk-container since the configurable
|
|
118
|
+
* namespace landed) is the correct, namespace-agnostic key — a container
|
|
119
|
+
* is `<namespace>-<name>`, and the namespace is `sk` only by default
|
|
120
|
+
* (a devcontainer runs `devpod-`, and operators can set any value via
|
|
121
|
+
* SIGNALK_CONTAINER_NAMESPACE). We must NOT hard-code `sk-`, or the helper
|
|
122
|
+
* would fail to find its own container under a non-default namespace.
|
|
123
|
+
*
|
|
124
|
+
* The fallback covers only a signalk-container old enough to predate
|
|
125
|
+
* `unprefixedName`. signalk-container validates the namespace as
|
|
126
|
+
* `[a-z0-9]+` (no hyphen), so the fallback matches `<namespace>-<name>`
|
|
127
|
+
* with a hyphen-free prefix rather than any `-<name>` suffix — otherwise
|
|
128
|
+
* a foreign container like `otherns-app-<name>` would false-match.
|
|
129
|
+
*/
|
|
130
|
+
private matchInfo;
|
|
131
|
+
/**
|
|
132
|
+
* True when `liveName` is this container under a legacy (pre-
|
|
133
|
+
* `unprefixedName`) signalk-container: either the bare name, or
|
|
134
|
+
* `<namespace>-<name>` where namespace is a single `[a-z0-9]+` token.
|
|
135
|
+
*/
|
|
136
|
+
private matchesLegacyName;
|
|
137
|
+
/**
|
|
138
|
+
* Full bring-up: wait for the manager and runtime, validate the tag,
|
|
139
|
+
* self-heal when the live image disagrees with the desired one, reconcile
|
|
140
|
+
* via ensureRunning, register for update detection, then (when configured)
|
|
141
|
+
* resolve the address and wait for the app's HTTP readiness.
|
|
142
|
+
*
|
|
143
|
+
* Emits progress via setPluginStatus but never sets a final "Running"
|
|
144
|
+
* status — that message belongs to the plugin. Throws ContainerHelperError
|
|
145
|
+
* (already reported via setPluginError) on fatal conditions; pair with
|
|
146
|
+
* `startSafely` in a synchronous plugin.start().
|
|
147
|
+
*/
|
|
148
|
+
start(tag?: string): Promise<StartResult>;
|
|
149
|
+
private registerUpdates;
|
|
150
|
+
private awaitReadiness;
|
|
151
|
+
/**
|
|
152
|
+
* host:port to reach a container port from the Signal K process.
|
|
153
|
+
* Tries resolveContainerAddress first, then falls back to parsing
|
|
154
|
+
* listContainers() port bindings — the resolver's process-local cache can
|
|
155
|
+
* return a stale port after recreates (seen in production by
|
|
156
|
+
* signalk-backup). Returns null when both fail; never throws.
|
|
157
|
+
*/
|
|
158
|
+
resolveAddress(port: number): Promise<string | null>;
|
|
159
|
+
/**
|
|
160
|
+
* Teardown for plugin.stop(): unregister from update detection, then stop
|
|
161
|
+
* (NOT remove) the container so re-enabling the plugin restarts it
|
|
162
|
+
* instantly without a pull. Never throws.
|
|
163
|
+
*/
|
|
164
|
+
stop(): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Apply a new tag now ("Update now" UX): recreate when available
|
|
167
|
+
* (signalk-container 1.12.0+), otherwise the legacy
|
|
168
|
+
* pull → remove → ensureRunning triplet. The legacy path can strand the
|
|
169
|
+
* plugin between remove and recreate — that failure surfaces as
|
|
170
|
+
* code "recreate-limbo" with a retry-able message.
|
|
171
|
+
*
|
|
172
|
+
* Returns the resolved tag. When readiness is configured the address is
|
|
173
|
+
* re-resolved afterwards (non-fatal on failure).
|
|
174
|
+
*/
|
|
175
|
+
applyUpdate(tag: string): Promise<{
|
|
176
|
+
tag: string;
|
|
177
|
+
}>;
|
|
178
|
+
/** updates.checkOne — null when the manager is unavailable. */
|
|
179
|
+
checkForUpdate(): Promise<UpdateCheckResult | null>;
|
|
180
|
+
/** Container state; 'no-runtime' when the manager is unavailable. Never throws. */
|
|
181
|
+
getState(): Promise<ContainerState>;
|
|
182
|
+
/** State + live image for /status routes. Never throws. */
|
|
183
|
+
getInfo(): Promise<{
|
|
184
|
+
state: ContainerState | "unknown";
|
|
185
|
+
image: string;
|
|
186
|
+
}>;
|
|
187
|
+
/** Last N log lines (signalk-container 1.7.0+); null when unsupported. */
|
|
188
|
+
getLogs(options?: {
|
|
189
|
+
tail?: number;
|
|
190
|
+
since?: number;
|
|
191
|
+
}): Promise<string[] | null>;
|
|
192
|
+
/**
|
|
193
|
+
* Mount the standard update endpoints on the plugin router:
|
|
194
|
+
*
|
|
195
|
+
* GET <basePath>/check → UpdateCheckResult (503 without a manager)
|
|
196
|
+
* POST <basePath>/apply → { success: true, tag } — body { tag?: string }
|
|
197
|
+
*
|
|
198
|
+
* Persist the requested tag from `onApplied` (e.g. via
|
|
199
|
+
* app.savePluginOptions) so the user's choice survives restarts.
|
|
200
|
+
*/
|
|
201
|
+
registerUpdateRoutes(router: RouterLike, options?: UpdateRoutesOptions): void;
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=managed-container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"managed-container.d.ts","sourceRoot":"","sources":["../src/managed-container.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { getContainerManager, waitForContainerManager } from "./manager.js";
|
|
2
|
+
import { buildVersionSource, } from "./version-source.js";
|
|
3
|
+
import { waitForHttpReady } from "./http.js";
|
|
4
|
+
import { ContainerHelperError, errMsg, isValidImageTag } from "./util.js";
|
|
5
|
+
/**
|
|
6
|
+
* Full lifecycle wrapper for a plugin-owned ("managed") container — the
|
|
7
|
+
* signalk-backup / mayara archetype. One instance per container.
|
|
8
|
+
*/
|
|
9
|
+
export class ManagedContainer {
|
|
10
|
+
options;
|
|
11
|
+
/** Resolved after start(); also lazily re-read from the global. */
|
|
12
|
+
manager;
|
|
13
|
+
/** Tag most recently started or applied (resolved form). */
|
|
14
|
+
lastStartedTag;
|
|
15
|
+
/** "http://host:port" once readiness resolved an address. */
|
|
16
|
+
address = null;
|
|
17
|
+
updatesRegistered = false;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
}
|
|
21
|
+
get app() {
|
|
22
|
+
return this.options.app;
|
|
23
|
+
}
|
|
24
|
+
status(msg) {
|
|
25
|
+
if (!this.options.quiet)
|
|
26
|
+
this.app.setPluginStatus(msg);
|
|
27
|
+
}
|
|
28
|
+
fail(code, message) {
|
|
29
|
+
this.app.setPluginError(message);
|
|
30
|
+
throw new ContainerHelperError(code, message, true);
|
|
31
|
+
}
|
|
32
|
+
requireManager() {
|
|
33
|
+
const manager = this.manager ?? getContainerManager();
|
|
34
|
+
if (!manager) {
|
|
35
|
+
this.fail("manager-unavailable", "signalk-container plugin not available. Install and enable it, then restart this plugin.");
|
|
36
|
+
}
|
|
37
|
+
this.manager = manager;
|
|
38
|
+
return manager;
|
|
39
|
+
}
|
|
40
|
+
/** Validate a requested tag and apply the resolveTag mapping. */
|
|
41
|
+
resolveTag(requested) {
|
|
42
|
+
if (!isValidImageTag(requested)) {
|
|
43
|
+
this.fail("invalid-tag", `Invalid image tag: ${JSON.stringify(requested)}`);
|
|
44
|
+
}
|
|
45
|
+
const resolved = this.options.resolveTag?.(requested) ?? requested;
|
|
46
|
+
if (!isValidImageTag(resolved)) {
|
|
47
|
+
this.fail("invalid-tag", `Invalid resolved image tag: ${JSON.stringify(resolved)}`);
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Match this container in a listContainers() result.
|
|
53
|
+
*
|
|
54
|
+
* `unprefixedName` (supplied by signalk-container since the configurable
|
|
55
|
+
* namespace landed) is the correct, namespace-agnostic key — a container
|
|
56
|
+
* is `<namespace>-<name>`, and the namespace is `sk` only by default
|
|
57
|
+
* (a devcontainer runs `devpod-`, and operators can set any value via
|
|
58
|
+
* SIGNALK_CONTAINER_NAMESPACE). We must NOT hard-code `sk-`, or the helper
|
|
59
|
+
* would fail to find its own container under a non-default namespace.
|
|
60
|
+
*
|
|
61
|
+
* The fallback covers only a signalk-container old enough to predate
|
|
62
|
+
* `unprefixedName`. signalk-container validates the namespace as
|
|
63
|
+
* `[a-z0-9]+` (no hyphen), so the fallback matches `<namespace>-<name>`
|
|
64
|
+
* with a hyphen-free prefix rather than any `-<name>` suffix — otherwise
|
|
65
|
+
* a foreign container like `otherns-app-<name>` would false-match.
|
|
66
|
+
*/
|
|
67
|
+
matchInfo(list) {
|
|
68
|
+
const { name } = this.options;
|
|
69
|
+
// Two passes so the reliable key wins regardless of list order: an
|
|
70
|
+
// exact unprefixedName match anywhere beats a legacy prefix match.
|
|
71
|
+
return (list.find((c) => c.unprefixedName === name) ??
|
|
72
|
+
list.find((c) => c.unprefixedName === undefined && this.matchesLegacyName(c.name)));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* True when `liveName` is this container under a legacy (pre-
|
|
76
|
+
* `unprefixedName`) signalk-container: either the bare name, or
|
|
77
|
+
* `<namespace>-<name>` where namespace is a single `[a-z0-9]+` token.
|
|
78
|
+
*/
|
|
79
|
+
matchesLegacyName(liveName) {
|
|
80
|
+
const { name } = this.options;
|
|
81
|
+
if (liveName === name)
|
|
82
|
+
return true;
|
|
83
|
+
const suffix = `-${name}`;
|
|
84
|
+
if (!liveName.endsWith(suffix))
|
|
85
|
+
return false;
|
|
86
|
+
const prefix = liveName.slice(0, -suffix.length);
|
|
87
|
+
return /^[a-z0-9]+$/.test(prefix);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Full bring-up: wait for the manager and runtime, validate the tag,
|
|
91
|
+
* self-heal when the live image disagrees with the desired one, reconcile
|
|
92
|
+
* via ensureRunning, register for update detection, then (when configured)
|
|
93
|
+
* resolve the address and wait for the app's HTTP readiness.
|
|
94
|
+
*
|
|
95
|
+
* Emits progress via setPluginStatus but never sets a final "Running"
|
|
96
|
+
* status — that message belongs to the plugin. Throws ContainerHelperError
|
|
97
|
+
* (already reported via setPluginError) on fatal conditions; pair with
|
|
98
|
+
* `startSafely` in a synchronous plugin.start().
|
|
99
|
+
*/
|
|
100
|
+
async start(tag) {
|
|
101
|
+
const { name, image, buildConfig, ensureOptions, managerTimeoutMs = 120_000, managerPollIntervalMs = 500, } = this.options;
|
|
102
|
+
this.status("Waiting for signalk-container plugin...");
|
|
103
|
+
const { manager, runtime } = await waitForContainerManager({
|
|
104
|
+
timeoutMs: managerTimeoutMs,
|
|
105
|
+
intervalMs: managerPollIntervalMs,
|
|
106
|
+
onWaiting: (phase) => {
|
|
107
|
+
if (phase === "runtime")
|
|
108
|
+
this.status("Waiting for container runtime detection...");
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
if (!manager) {
|
|
112
|
+
this.fail("manager-unavailable", `signalk-container plugin not available after ${Math.round(managerTimeoutMs / 1000)}s. Install and enable it, then restart this plugin.`);
|
|
113
|
+
}
|
|
114
|
+
if (!runtime) {
|
|
115
|
+
this.fail("no-runtime", "No container runtime detected (Podman or Docker). Install one and restart Signal K.");
|
|
116
|
+
}
|
|
117
|
+
this.manager = manager;
|
|
118
|
+
const resolved = this.resolveTag(tag ?? this.options.defaultTag ?? "latest");
|
|
119
|
+
const desiredImage = `${image}:${resolved}`;
|
|
120
|
+
const config = buildConfig(resolved);
|
|
121
|
+
// Startup self-heal: when the plugin bumped its pinned tag, the live
|
|
122
|
+
// container's image disagrees with the desired one. recreate (1.12.0+)
|
|
123
|
+
// applies it immediately instead of trusting drift detection.
|
|
124
|
+
let reconciled = false;
|
|
125
|
+
if (typeof manager.recreate === "function") {
|
|
126
|
+
try {
|
|
127
|
+
const found = this.matchInfo(await manager.listContainers());
|
|
128
|
+
if (found && found.image !== desiredImage) {
|
|
129
|
+
this.status(`Recreating ${found.image} → ${desiredImage}...`);
|
|
130
|
+
await manager.recreate(name, config, ensureOptions);
|
|
131
|
+
reconciled = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (probeErr) {
|
|
135
|
+
this.app.debug(`self-heal probe failed (non-fatal): ${errMsg(probeErr)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!reconciled) {
|
|
139
|
+
this.status(`Starting ${desiredImage}...`);
|
|
140
|
+
await manager.ensureRunning(name, config, ensureOptions);
|
|
141
|
+
}
|
|
142
|
+
this.lastStartedTag = resolved;
|
|
143
|
+
this.registerUpdates(manager);
|
|
144
|
+
this.address = null;
|
|
145
|
+
if (this.options.readiness) {
|
|
146
|
+
this.address = await this.awaitReadiness(resolved);
|
|
147
|
+
}
|
|
148
|
+
return { manager, tag: resolved, address: this.address };
|
|
149
|
+
}
|
|
150
|
+
registerUpdates(manager) {
|
|
151
|
+
const updates = this.options.updates;
|
|
152
|
+
if (!updates)
|
|
153
|
+
return;
|
|
154
|
+
try {
|
|
155
|
+
manager.updates.register({
|
|
156
|
+
pluginId: this.options.pluginId,
|
|
157
|
+
containerName: this.options.name,
|
|
158
|
+
image: this.options.image,
|
|
159
|
+
currentTag: updates.currentTag ??
|
|
160
|
+
(() => this.lastStartedTag ?? this.options.defaultTag ?? "latest"),
|
|
161
|
+
currentVersion: updates.currentVersion,
|
|
162
|
+
checkInterval: updates.checkInterval,
|
|
163
|
+
versionSource: buildVersionSource(manager.updates, updates.versionSource),
|
|
164
|
+
});
|
|
165
|
+
this.updatesRegistered = true;
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
// Non-fatal: the container is up; only update detection is missing.
|
|
169
|
+
this.app.debug(`updates.register failed (non-fatal): ${errMsg(err)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async awaitReadiness(tag) {
|
|
173
|
+
const readiness = this.options.readiness;
|
|
174
|
+
const addr = await this.resolveAddress(readiness.port);
|
|
175
|
+
if (!addr) {
|
|
176
|
+
this.fail("address-unresolved", `Could not resolve address for ${this.options.name} port ${readiness.port}. Declare the port in signalkAccessiblePorts.`);
|
|
177
|
+
}
|
|
178
|
+
const base = `http://${addr}`;
|
|
179
|
+
const url = `${base}${readiness.path ?? "/"}`;
|
|
180
|
+
this.status(`Waiting for ${this.options.name} to become ready...`);
|
|
181
|
+
try {
|
|
182
|
+
await waitForHttpReady(url, {
|
|
183
|
+
maxMs: readiness.maxMs,
|
|
184
|
+
intervalMs: readiness.intervalMs,
|
|
185
|
+
requestTimeoutMs: readiness.requestTimeoutMs,
|
|
186
|
+
fetchImpl: this.options.fetchImpl,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
this.fail("not-ready", `${this.options.image}:${tag} started but ${url} never became ready: ${errMsg(err)}`);
|
|
191
|
+
}
|
|
192
|
+
return base;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* host:port to reach a container port from the Signal K process.
|
|
196
|
+
* Tries resolveContainerAddress first, then falls back to parsing
|
|
197
|
+
* listContainers() port bindings — the resolver's process-local cache can
|
|
198
|
+
* return a stale port after recreates (seen in production by
|
|
199
|
+
* signalk-backup). Returns null when both fail; never throws.
|
|
200
|
+
*/
|
|
201
|
+
async resolveAddress(port) {
|
|
202
|
+
const manager = this.manager ?? getContainerManager();
|
|
203
|
+
if (!manager)
|
|
204
|
+
return null;
|
|
205
|
+
try {
|
|
206
|
+
const answer = await manager.resolveContainerAddress(this.options.name, port);
|
|
207
|
+
if (answer)
|
|
208
|
+
return answer;
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
this.app.debug(`resolveContainerAddress failed: ${errMsg(err)}`);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const found = this.matchInfo(await manager.listContainers());
|
|
215
|
+
const wanted = `->${port}/tcp`;
|
|
216
|
+
for (const entry of found?.ports ?? []) {
|
|
217
|
+
if (!entry.endsWith(wanted))
|
|
218
|
+
continue;
|
|
219
|
+
const hostPart = entry.slice(0, -wanted.length);
|
|
220
|
+
if (hostPart.includes(":"))
|
|
221
|
+
return hostPart;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
this.app.debug(`listContainers port fallback failed: ${errMsg(err)}`);
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Teardown for plugin.stop(): unregister from update detection, then stop
|
|
231
|
+
* (NOT remove) the container so re-enabling the plugin restarts it
|
|
232
|
+
* instantly without a pull. Never throws.
|
|
233
|
+
*/
|
|
234
|
+
async stop() {
|
|
235
|
+
this.address = null;
|
|
236
|
+
const manager = this.manager ?? getContainerManager();
|
|
237
|
+
if (!manager)
|
|
238
|
+
return;
|
|
239
|
+
if (this.updatesRegistered) {
|
|
240
|
+
try {
|
|
241
|
+
manager.updates.unregister(this.options.pluginId);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
this.app.debug(`updates.unregister failed: ${errMsg(err)}`);
|
|
245
|
+
}
|
|
246
|
+
this.updatesRegistered = false;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
await manager.stop(this.options.name);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
this.app.debug(`container stop failed (may already be stopped): ${errMsg(err)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Apply a new tag now ("Update now" UX): recreate when available
|
|
257
|
+
* (signalk-container 1.12.0+), otherwise the legacy
|
|
258
|
+
* pull → remove → ensureRunning triplet. The legacy path can strand the
|
|
259
|
+
* plugin between remove and recreate — that failure surfaces as
|
|
260
|
+
* code "recreate-limbo" with a retry-able message.
|
|
261
|
+
*
|
|
262
|
+
* Returns the resolved tag. When readiness is configured the address is
|
|
263
|
+
* re-resolved afterwards (non-fatal on failure).
|
|
264
|
+
*/
|
|
265
|
+
async applyUpdate(tag) {
|
|
266
|
+
const manager = this.requireManager();
|
|
267
|
+
const resolved = this.resolveTag(tag);
|
|
268
|
+
const { name, image, buildConfig, ensureOptions } = this.options;
|
|
269
|
+
const config = buildConfig(resolved);
|
|
270
|
+
this.status(`Recreating ${name} with ${image}:${resolved}...`);
|
|
271
|
+
if (typeof manager.recreate === "function") {
|
|
272
|
+
await manager.recreate(name, config, ensureOptions);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
await manager.pullImage(`${image}:${resolved}`);
|
|
276
|
+
await manager.remove(name);
|
|
277
|
+
try {
|
|
278
|
+
await manager.ensureRunning(name, config, ensureOptions);
|
|
279
|
+
}
|
|
280
|
+
catch (recreateErr) {
|
|
281
|
+
this.fail("recreate-limbo", `Container removed but recreation failed: ${errMsg(recreateErr)}. Apply the update again to retry.`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
this.lastStartedTag = resolved;
|
|
285
|
+
if (this.options.readiness) {
|
|
286
|
+
const addr = await this.resolveAddress(this.options.readiness.port);
|
|
287
|
+
this.address = addr ? `http://${addr}` : this.address;
|
|
288
|
+
}
|
|
289
|
+
this.status(`Updated to ${image}:${resolved}`);
|
|
290
|
+
return { tag: resolved };
|
|
291
|
+
}
|
|
292
|
+
/** updates.checkOne — null when the manager is unavailable. */
|
|
293
|
+
async checkForUpdate() {
|
|
294
|
+
const manager = this.manager ?? getContainerManager();
|
|
295
|
+
if (!manager)
|
|
296
|
+
return null;
|
|
297
|
+
return manager.updates.checkOne(this.options.pluginId);
|
|
298
|
+
}
|
|
299
|
+
/** Container state; 'no-runtime' when the manager is unavailable. Never throws. */
|
|
300
|
+
async getState() {
|
|
301
|
+
const manager = this.manager ?? getContainerManager();
|
|
302
|
+
if (!manager)
|
|
303
|
+
return "no-runtime";
|
|
304
|
+
try {
|
|
305
|
+
return await manager.getState(this.options.name);
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
this.app.debug(`getState failed: ${errMsg(err)}`);
|
|
309
|
+
return "no-runtime";
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** State + live image for /status routes. Never throws. */
|
|
313
|
+
async getInfo() {
|
|
314
|
+
const manager = this.manager ?? getContainerManager();
|
|
315
|
+
let state = "unknown";
|
|
316
|
+
let image = "";
|
|
317
|
+
if (manager) {
|
|
318
|
+
try {
|
|
319
|
+
state = await manager.getState(this.options.name);
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
this.app.debug(`status: getState failed: ${errMsg(err)}`);
|
|
323
|
+
}
|
|
324
|
+
if (manager.getRuntime()) {
|
|
325
|
+
try {
|
|
326
|
+
const found = this.matchInfo(await manager.listContainers());
|
|
327
|
+
if (found)
|
|
328
|
+
image = found.image;
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
this.app.debug(`status: listContainers failed: ${errMsg(err)}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { state, image };
|
|
336
|
+
}
|
|
337
|
+
/** Last N log lines (signalk-container 1.7.0+); null when unsupported. */
|
|
338
|
+
async getLogs(options) {
|
|
339
|
+
const manager = this.manager ?? getContainerManager();
|
|
340
|
+
if (!manager || typeof manager.getLogs !== "function")
|
|
341
|
+
return null;
|
|
342
|
+
return manager.getLogs(this.options.name, options);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Mount the standard update endpoints on the plugin router:
|
|
346
|
+
*
|
|
347
|
+
* GET <basePath>/check → UpdateCheckResult (503 without a manager)
|
|
348
|
+
* POST <basePath>/apply → { success: true, tag } — body { tag?: string }
|
|
349
|
+
*
|
|
350
|
+
* Persist the requested tag from `onApplied` (e.g. via
|
|
351
|
+
* app.savePluginOptions) so the user's choice survives restarts.
|
|
352
|
+
*/
|
|
353
|
+
registerUpdateRoutes(router, options = {}) {
|
|
354
|
+
const { basePath = "/api/update", onApplied } = options;
|
|
355
|
+
router.get(`${basePath}/check`, async (_req, res) => {
|
|
356
|
+
const manager = this.manager ?? getContainerManager();
|
|
357
|
+
if (!manager) {
|
|
358
|
+
res.status(503).json({ error: "signalk-container not available" });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
res.json(await manager.updates.checkOne(this.options.pluginId));
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
res.status(500).json({ error: errMsg(err) });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
router.post(`${basePath}/apply`, async (req, res) => {
|
|
369
|
+
const manager = this.manager ?? getContainerManager();
|
|
370
|
+
if (!manager) {
|
|
371
|
+
res.status(503).json({ error: "signalk-container not available" });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const body = req.body;
|
|
375
|
+
const requested = typeof body?.tag === "string" && body.tag.length > 0
|
|
376
|
+
? body.tag
|
|
377
|
+
: (this.lastStartedTag ?? this.options.defaultTag ?? "latest");
|
|
378
|
+
if (!isValidImageTag(requested)) {
|
|
379
|
+
res
|
|
380
|
+
.status(400)
|
|
381
|
+
.json({ error: `Invalid tag: ${JSON.stringify(requested)}` });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
const { tag } = await this.applyUpdate(requested);
|
|
386
|
+
await onApplied?.(requested, tag);
|
|
387
|
+
res.json({ success: true, tag });
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
const msg = errMsg(err);
|
|
391
|
+
this.app.setPluginError(`Update failed: ${msg}`);
|
|
392
|
+
res.status(500).json({ error: msg });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=managed-container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"managed-container.js","sourceRoot":"","sources":["../src/managed-container.ts"],"names":[],"mappings":"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"}
|