@katmer/core 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/cli/katmer.js +28 -0
- package/cli/run.ts +16 -0
- package/index.ts +5 -0
- package/lib/config.ts +82 -0
- package/lib/interfaces/config.interface.ts +113 -0
- package/lib/interfaces/executor.interface.ts +13 -0
- package/lib/interfaces/module.interface.ts +170 -0
- package/lib/interfaces/provider.interface.ts +214 -0
- package/lib/interfaces/task.interface.ts +100 -0
- package/lib/katmer.ts +126 -0
- package/lib/lookup/env.lookup.ts +13 -0
- package/lib/lookup/file.lookup.ts +23 -0
- package/lib/lookup/index.ts +46 -0
- package/lib/lookup/url.lookup.ts +21 -0
- package/lib/lookup/var.lookup.ts +13 -0
- package/lib/module.ts +560 -0
- package/lib/module_registry.ts +64 -0
- package/lib/modules/apt-repository/apt-repository.module.ts +435 -0
- package/lib/modules/apt-repository/apt-sources-list.ts +363 -0
- package/lib/modules/apt.module.ts +546 -0
- package/lib/modules/archive.module.ts +280 -0
- package/lib/modules/become.module.ts +119 -0
- package/lib/modules/copy.module.ts +807 -0
- package/lib/modules/cron.module.ts +541 -0
- package/lib/modules/debug.module.ts +231 -0
- package/lib/modules/gather_facts.module.ts +605 -0
- package/lib/modules/git.module.ts +243 -0
- package/lib/modules/hostname.module.ts +213 -0
- package/lib/modules/http/http.curl.module.ts +342 -0
- package/lib/modules/http/http.local.module.ts +253 -0
- package/lib/modules/http/http.module.ts +298 -0
- package/lib/modules/index.ts +14 -0
- package/lib/modules/package.module.ts +283 -0
- package/lib/modules/script.module.ts +121 -0
- package/lib/modules/set_fact.module.ts +171 -0
- package/lib/modules/systemd_service.module.ts +373 -0
- package/lib/modules/template.module.ts +478 -0
- package/lib/providers/local.provider.ts +336 -0
- package/lib/providers/provider_response.ts +20 -0
- package/lib/providers/ssh/ssh.provider.ts +420 -0
- package/lib/providers/ssh/ssh.utils.ts +31 -0
- package/lib/schemas/katmer_config.schema.json +358 -0
- package/lib/target_resolver.ts +298 -0
- package/lib/task/controls/environment.control.ts +42 -0
- package/lib/task/controls/index.ts +13 -0
- package/lib/task/controls/loop.control.ts +89 -0
- package/lib/task/controls/register.control.ts +23 -0
- package/lib/task/controls/until.control.ts +64 -0
- package/lib/task/controls/when.control.ts +25 -0
- package/lib/task/task.ts +225 -0
- package/lib/utils/ajv.utils.ts +24 -0
- package/lib/utils/cls.ts +4 -0
- package/lib/utils/datetime.utils.ts +15 -0
- package/lib/utils/errors.ts +25 -0
- package/lib/utils/execute-shell.ts +116 -0
- package/lib/utils/file.utils.ts +68 -0
- package/lib/utils/http.utils.ts +10 -0
- package/lib/utils/json.utils.ts +15 -0
- package/lib/utils/number.utils.ts +9 -0
- package/lib/utils/object.utils.ts +11 -0
- package/lib/utils/os.utils.ts +31 -0
- package/lib/utils/path.utils.ts +9 -0
- package/lib/utils/renderer/render_functions.ts +3 -0
- package/lib/utils/renderer/renderer.ts +89 -0
- package/lib/utils/renderer/twig.ts +191 -0
- package/lib/utils/string.utils.ts +33 -0
- package/lib/utils/typed-event-emitter.ts +26 -0
- package/lib/utils/unix.utils.ts +91 -0
- package/lib/utils/windows.utils.ts +92 -0
- package/package.json +67 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { type ModuleConstraints } from "../interfaces/module.interface"
|
|
2
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
3
|
+
import type { SSHProvider } from "../providers/ssh/ssh.provider"
|
|
4
|
+
import type { OsInfo } from "../interfaces/provider.interface"
|
|
5
|
+
import { KatmerModule } from "../module"
|
|
6
|
+
|
|
7
|
+
declare module "../interfaces/task.interface" {
|
|
8
|
+
export namespace Katmer {
|
|
9
|
+
export interface TaskActions {
|
|
10
|
+
apt?: AptModuleOptions
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* APT package manager: install, remove, and upgrade packages on Debian/Ubuntu.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* - Uses `apt-get` with noninteractive, idempotent flags where possible.
|
|
20
|
+
* - Can optionally refresh the cache (`apt-get update`) with retries/backoff.
|
|
21
|
+
* - Supports installing from package names or local `.deb` files.
|
|
22
|
+
* - When `upgrade` is set, a system-wide upgrade action runs before package
|
|
23
|
+
* state changes (e.g. `full-upgrade`), then requested package operations.
|
|
24
|
+
*
|
|
25
|
+
* @examples
|
|
26
|
+
* ```yaml
|
|
27
|
+
* # Refresh cache (if older than 10 minutes), install nginx, then autoremove
|
|
28
|
+
* - name: Install nginx and clean up
|
|
29
|
+
* apt:
|
|
30
|
+
* name: nginx
|
|
31
|
+
* state: present
|
|
32
|
+
* update_cache: true
|
|
33
|
+
* cache_valid_time: 600
|
|
34
|
+
* autoremove: true
|
|
35
|
+
*
|
|
36
|
+
* # Ensure latest versions for a list of packages
|
|
37
|
+
* - name: Bump packages to latest available versions
|
|
38
|
+
* apt:
|
|
39
|
+
* name: [curl, unzip, git]
|
|
40
|
+
* state: latest
|
|
41
|
+
*
|
|
42
|
+
* # Remove a package (purge configuration files too)
|
|
43
|
+
* - name: Remove nginx completely
|
|
44
|
+
* apt:
|
|
45
|
+
* name: nginx
|
|
46
|
+
* state: absent
|
|
47
|
+
* purge: true
|
|
48
|
+
*
|
|
49
|
+
* # Install from local .deb(s)
|
|
50
|
+
* - name: Install from local deb files
|
|
51
|
+
* apt:
|
|
52
|
+
* deb:
|
|
53
|
+
* - /tmp/custom_1.0.0_amd64.deb
|
|
54
|
+
* - /tmp/agent_2.3.1_amd64.deb
|
|
55
|
+
*
|
|
56
|
+
* # Run a safe upgrade first, then install a package
|
|
57
|
+
* - name: Safe upgrade then install htop
|
|
58
|
+
* apt:
|
|
59
|
+
* upgrade: safe
|
|
60
|
+
* name: htop
|
|
61
|
+
* state: present
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export class AptModule extends KatmerModule<
|
|
65
|
+
AptModuleOptions,
|
|
66
|
+
AptModuleResult,
|
|
67
|
+
SSHProvider
|
|
68
|
+
> {
|
|
69
|
+
constraints = {
|
|
70
|
+
platform: {
|
|
71
|
+
linux: {
|
|
72
|
+
packages: ["apt"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} satisfies ModuleConstraints
|
|
76
|
+
|
|
77
|
+
static name = "apt" as const
|
|
78
|
+
|
|
79
|
+
private aptCmd = "apt-get"
|
|
80
|
+
|
|
81
|
+
async check(ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {
|
|
82
|
+
// tools
|
|
83
|
+
const tools = ["apt-get", "dpkg", "apt-cache"]
|
|
84
|
+
for (const t of tools) {
|
|
85
|
+
const r = await ctx.exec(`command -v ${t} >/dev/null 2>&1; echo $?`)
|
|
86
|
+
if (String(r.stdout).trim() !== "0") {
|
|
87
|
+
throw new Error(`${t} is not available on the target system`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// resolve apt command preference
|
|
92
|
+
this.aptCmd = this.params.force_apt_get ? "apt-get" : "apt-get"
|
|
93
|
+
|
|
94
|
+
// basic validation
|
|
95
|
+
if (
|
|
96
|
+
this.params.state === "absent" &&
|
|
97
|
+
!this.params.name &&
|
|
98
|
+
!this.params.deb
|
|
99
|
+
) {
|
|
100
|
+
throw new Error("state=absent requires 'name' or 'deb'")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async initialize(_ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {}
|
|
105
|
+
async cleanup(_ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {}
|
|
106
|
+
|
|
107
|
+
private dpkgOptions(): string {
|
|
108
|
+
const opts = new Set<string>(this.params.dpkg_options || [])
|
|
109
|
+
if (this.params.dpkg_force_confnew) opts.add("--force-confnew")
|
|
110
|
+
if (this.params.dpkg_force_confdef) opts.add("--force-confdef")
|
|
111
|
+
if (opts.size === 0) return ""
|
|
112
|
+
return Array.from(opts)
|
|
113
|
+
.map((o) => `-o Dpkg::Options::=${quote(o)}`)
|
|
114
|
+
.join(" ")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private installRecommends(): string {
|
|
118
|
+
const v = this.params.install_recommends
|
|
119
|
+
if (typeof v === "boolean") {
|
|
120
|
+
return `-o APT::Install-Recommends=${v ? "true" : "false"}`
|
|
121
|
+
}
|
|
122
|
+
return "" // default apt behavior (true)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private lockTimeout(): string {
|
|
126
|
+
const t = this.params.lock_timeout
|
|
127
|
+
if (!t || t <= 0) return ""
|
|
128
|
+
// best-effort; different apt versions handle this differently
|
|
129
|
+
return `-o DPkg::Lock::Timeout=${t}`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private baseEnv(): string {
|
|
133
|
+
const env: string[] = []
|
|
134
|
+
if (typeof this.params.policy_rc_d === "number") {
|
|
135
|
+
env.push(`POLICY_RC_D=${this.params.policy_rc_d}`)
|
|
136
|
+
}
|
|
137
|
+
if (this.params.allow_unauthenticated) {
|
|
138
|
+
env.push("APT_LISTCHANGES_FRONTEND=none")
|
|
139
|
+
}
|
|
140
|
+
return env.join(" ")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async aptUpdateIfNeeded(
|
|
144
|
+
ctx: Katmer.TaskContext<SSHProvider>
|
|
145
|
+
): Promise<boolean> {
|
|
146
|
+
const update_cache = !!this.params.update_cache
|
|
147
|
+
if (!update_cache) return false
|
|
148
|
+
|
|
149
|
+
// cache_valid_time best-effort: check mtime of lists dir
|
|
150
|
+
const cv = this.params.cache_valid_time
|
|
151
|
+
if (cv && cv > 0) {
|
|
152
|
+
const stampCheck = await ctx.exec(
|
|
153
|
+
"test -d /var/lib/apt/lists && stat -c %Y /var/lib/apt/lists 2>/dev/null || echo 0"
|
|
154
|
+
)
|
|
155
|
+
const stamp = Number(String(stampCheck.stdout).trim() || "0")
|
|
156
|
+
if (stamp && nowSeconds() - stamp < cv) {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const retries = this.params.update_cache_retries ?? 5
|
|
162
|
+
const maxDelay = this.params.update_cache_retry_max_delay ?? 12
|
|
163
|
+
const randomize = Math.random()
|
|
164
|
+
const fatal = this.params.update_cache_error_fatal !== false
|
|
165
|
+
|
|
166
|
+
for (let retry = 0; retry < retries; retry++) {
|
|
167
|
+
const r = await ctx.exec(
|
|
168
|
+
`${this.baseEnv()} sudo ${this.aptCmd} update -y`
|
|
169
|
+
)
|
|
170
|
+
if (r.code === 0) return true
|
|
171
|
+
const lastErr = r.stderr || r.stdout || "unknown reason"
|
|
172
|
+
ctx.warn(
|
|
173
|
+
`apt-get update failed: ${lastErr}. Attempt ${retry + 1}/${retries}, retrying...`
|
|
174
|
+
)
|
|
175
|
+
let delay = 2 ** retry + randomize
|
|
176
|
+
if (delay > maxDelay) delay = maxDelay + randomize
|
|
177
|
+
await new Promise((res) => setTimeout(res, Math.round(delay * 1000)))
|
|
178
|
+
if (retry === retries - 1 && fatal) {
|
|
179
|
+
throw {
|
|
180
|
+
changed: false,
|
|
181
|
+
updated_cache: false,
|
|
182
|
+
msg: `Failed to update apt cache after ${retries} retries: ${lastErr}`
|
|
183
|
+
} as AptModuleResult
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private aptCommonFlags(): string {
|
|
190
|
+
const parts = [
|
|
191
|
+
"-y",
|
|
192
|
+
"-qq",
|
|
193
|
+
this.lockTimeout(),
|
|
194
|
+
this.dpkgOptions(),
|
|
195
|
+
this.installRecommends(),
|
|
196
|
+
this.params.only_upgrade ? "--only-upgrade" : "",
|
|
197
|
+
this.params.allow_unauthenticated ? "--allow-unauthenticated" : ""
|
|
198
|
+
].filter(Boolean)
|
|
199
|
+
return parts.join(" ")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private pkgListArg(): string {
|
|
203
|
+
const pkgs = joinPkgs(this.params.name).filter(Boolean) as string[]
|
|
204
|
+
return pkgs.map(quote).join(" ")
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private debListArg(): string {
|
|
208
|
+
const debs = joinPkgs(this.params.deb).filter(Boolean) as string[]
|
|
209
|
+
return debs.map(quote).join(" ")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async execute(
|
|
213
|
+
ctx: Katmer.TaskContext<SSHProvider>
|
|
214
|
+
): Promise<AptModuleResult> {
|
|
215
|
+
const state: PackageState = this.params.state ?? "present"
|
|
216
|
+
|
|
217
|
+
// update cache
|
|
218
|
+
const updated_cache = await this.aptUpdateIfNeeded(ctx)
|
|
219
|
+
|
|
220
|
+
// upgrades
|
|
221
|
+
if (this.params.upgrade && this.params.upgrade !== "no") {
|
|
222
|
+
const modeMap: Record<
|
|
223
|
+
NonNullable<AptModuleOptions["upgrade"]>,
|
|
224
|
+
string
|
|
225
|
+
> = {
|
|
226
|
+
no: "",
|
|
227
|
+
yes: "upgrade",
|
|
228
|
+
safe: "upgrade",
|
|
229
|
+
full: "full-upgrade",
|
|
230
|
+
dist: "dist-upgrade"
|
|
231
|
+
}
|
|
232
|
+
const sub = modeMap[this.params.upgrade]
|
|
233
|
+
if (sub) {
|
|
234
|
+
const cmd = `${this.baseEnv()} sudo ${this.aptCmd} ${sub} ${this.aptCommonFlags()}`
|
|
235
|
+
const r = await ctx.exec(cmd)
|
|
236
|
+
if (r.code !== 0) {
|
|
237
|
+
throw {
|
|
238
|
+
changed: false,
|
|
239
|
+
updated_cache,
|
|
240
|
+
upgraded: false,
|
|
241
|
+
msg: r.stderr || r.stdout || `${this.aptCmd} ${sub} failed`
|
|
242
|
+
} as AptModuleResult
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let changed = false
|
|
248
|
+
let stdout = ""
|
|
249
|
+
let stderr = ""
|
|
250
|
+
|
|
251
|
+
if (state === "present" || state === "latest" || state === "build-dep") {
|
|
252
|
+
const verb = state === "build-dep" ? "build-dep" : "install"
|
|
253
|
+
const pkgArgs = this.pkgListArg()
|
|
254
|
+
const debArgs = this.debListArg()
|
|
255
|
+
|
|
256
|
+
if (!pkgArgs && !debArgs) {
|
|
257
|
+
// nothing to do
|
|
258
|
+
} else {
|
|
259
|
+
const extra = state === "latest" ? "--only-upgrade" : ""
|
|
260
|
+
const targetArgs = debArgs || pkgArgs
|
|
261
|
+
const cmd =
|
|
262
|
+
`${this.baseEnv()} sudo ${this.aptCmd} ${verb} ${this.aptCommonFlags()} ${extra} ${targetArgs}`.trim()
|
|
263
|
+
const r = await ctx.exec(cmd)
|
|
264
|
+
stdout = r.stdout
|
|
265
|
+
stderr = r.stderr
|
|
266
|
+
if (r.code !== 0) {
|
|
267
|
+
throw {
|
|
268
|
+
changed: false,
|
|
269
|
+
updated_cache,
|
|
270
|
+
msg: r.stderr || r.stdout || `${this.aptCmd} ${verb} failed`
|
|
271
|
+
} as AptModuleResult
|
|
272
|
+
}
|
|
273
|
+
changed = true
|
|
274
|
+
}
|
|
275
|
+
} else if (state === "absent") {
|
|
276
|
+
const purge = this.params.purge ? "--purge" : ""
|
|
277
|
+
const pkgArgs = this.pkgListArg()
|
|
278
|
+
const debArgs = this.debListArg()
|
|
279
|
+
const targetArgs = debArgs || pkgArgs
|
|
280
|
+
if (targetArgs) {
|
|
281
|
+
const cmd = `${this.baseEnv()} sudo ${this.aptCmd} remove ${purge} ${this.aptCommonFlags()} ${targetArgs}`
|
|
282
|
+
const r = await ctx.exec(cmd)
|
|
283
|
+
stdout = r.stdout
|
|
284
|
+
stderr = r.stderr
|
|
285
|
+
if (r.code !== 0) {
|
|
286
|
+
throw {
|
|
287
|
+
changed: false,
|
|
288
|
+
updated_cache,
|
|
289
|
+
msg: r.stderr || r.stdout || `${this.aptCmd} remove failed`
|
|
290
|
+
} as AptModuleResult
|
|
291
|
+
}
|
|
292
|
+
changed = true
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// autoremove
|
|
297
|
+
let didAutoremove = false
|
|
298
|
+
if (this.params.autoremove) {
|
|
299
|
+
const r = await ctx.exec(
|
|
300
|
+
`${this.baseEnv()} sudo ${this.aptCmd} autoremove -y -qq ${this.lockTimeout()}`
|
|
301
|
+
)
|
|
302
|
+
if (r.code !== 0) {
|
|
303
|
+
throw {
|
|
304
|
+
changed,
|
|
305
|
+
updated_cache,
|
|
306
|
+
autoremove: false,
|
|
307
|
+
msg: r.stderr || r.stdout || `${this.aptCmd} autoremove failed`
|
|
308
|
+
} as AptModuleResult
|
|
309
|
+
}
|
|
310
|
+
didAutoremove = true
|
|
311
|
+
changed = true
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// clean
|
|
315
|
+
let didClean = false
|
|
316
|
+
if (this.params.clean) {
|
|
317
|
+
const r = await ctx.exec(`${this.baseEnv()} sudo ${this.aptCmd} clean`)
|
|
318
|
+
if (r.code !== 0) {
|
|
319
|
+
throw {
|
|
320
|
+
changed,
|
|
321
|
+
updated_cache,
|
|
322
|
+
cleaned: false,
|
|
323
|
+
msg: r.stderr || r.stdout || `${this.aptCmd} clean failed`
|
|
324
|
+
} as AptModuleResult
|
|
325
|
+
}
|
|
326
|
+
didClean = true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
changed,
|
|
331
|
+
stdout,
|
|
332
|
+
stderr,
|
|
333
|
+
updated_cache,
|
|
334
|
+
upgraded: this.params.upgrade ? this.params.upgrade !== "no" : false,
|
|
335
|
+
cleaned: didClean,
|
|
336
|
+
autoremove: didAutoremove
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
type PackageState = "present" | "absent" | "latest" | "build-dep"
|
|
342
|
+
/**
|
|
343
|
+
* Options for the **apt** module.
|
|
344
|
+
*/
|
|
345
|
+
export type AptModuleOptions = {
|
|
346
|
+
/**
|
|
347
|
+
* Name(s) of packages to manage.
|
|
348
|
+
* Accepts a single name or a list.
|
|
349
|
+
*
|
|
350
|
+
* Ignored if only {@link AptModuleOptions.deb | deb} is provided.
|
|
351
|
+
*/
|
|
352
|
+
name?: string | string[]
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Desired package state.
|
|
356
|
+
* - `present`: ensure installed (default).
|
|
357
|
+
* - `absent`: ensure removed (optionally `purge` config files).
|
|
358
|
+
* - `latest`: ensure installed at newest available version.
|
|
359
|
+
* - `build-dep`: install build-dependencies for the named source package(s).
|
|
360
|
+
* @defaultValue "present"
|
|
361
|
+
*/
|
|
362
|
+
state?: "present" | "absent" | "latest" | "build-dep"
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Run `apt-get update` before making changes.
|
|
366
|
+
*
|
|
367
|
+
* Honors {@link AptModuleOptions.cache_valid_time | cache_valid_time} to skip
|
|
368
|
+
* the update if the package lists are fresh.
|
|
369
|
+
*/
|
|
370
|
+
update_cache?: boolean
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Perform a system-wide upgrade action before package operations.
|
|
374
|
+
* - `no`: skip upgrade
|
|
375
|
+
* - `yes`/`safe`: `apt-get upgrade`
|
|
376
|
+
* - `full`: `apt-get full-upgrade`
|
|
377
|
+
* - `dist`: `apt-get dist-upgrade`
|
|
378
|
+
*/
|
|
379
|
+
upgrade?: "no" | "yes" | "safe" | "full" | "dist"
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* When removing (state=`absent`), purge configuration files as well.
|
|
383
|
+
*/
|
|
384
|
+
purge?: boolean
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* After changes, remove automatically installed packages that are no longer needed.
|
|
388
|
+
*/
|
|
389
|
+
autoremove?: boolean
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Allow unauthenticated packages (passes `--allow-unauthenticated`).
|
|
393
|
+
*/
|
|
394
|
+
allow_unauthenticated?: boolean
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Convenience toggle for dpkg conflict handling (minimal set).
|
|
398
|
+
* When `true`, acts like enabling `--force-confnew` for dpkg.
|
|
399
|
+
* Prefer {@link AptModuleOptions.dpkg_options | dpkg_options} for full control.
|
|
400
|
+
*/
|
|
401
|
+
force?: boolean
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Raw dpkg options; each entry is passed as `-o Dpkg::Options::[value]`.
|
|
405
|
+
*
|
|
406
|
+
* Example: `["--force-confnew","--force-confdef"]`.
|
|
407
|
+
*
|
|
408
|
+
* See also the convenience flags:
|
|
409
|
+
* {@link AptModuleOptions.dpkg_force_confnew | dpkg_force_confnew} and
|
|
410
|
+
* {@link AptModuleOptions.dpkg_force_confdef | dpkg_force_confdef}.
|
|
411
|
+
*/
|
|
412
|
+
dpkg_options?: string[]
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Whether to install recommended packages.
|
|
416
|
+
* Omitting keeps apt's default behavior (usually `true`).
|
|
417
|
+
*/
|
|
418
|
+
install_recommends?: boolean
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Max number of retry attempts for `apt-get update` when `update_cache` is enabled.
|
|
422
|
+
* @defaultValue 5
|
|
423
|
+
*/
|
|
424
|
+
update_cache_retries?: number
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Maximum backoff delay (seconds) between update retries.
|
|
428
|
+
* @defaultValue 12
|
|
429
|
+
*/
|
|
430
|
+
update_cache_retry_max_delay?: number
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Lock timeout for dpkg/apt operations (seconds).
|
|
434
|
+
* Best-effort via `-o DPkg::Lock::Timeout=[seconds]`.
|
|
435
|
+
*/
|
|
436
|
+
lock_timeout?: number
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Set `POLICY_RC_D` in the environment (e.g. `101`) to prevent service starts.
|
|
440
|
+
*/
|
|
441
|
+
policy_rc_d?: number
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Upgrade only existing packages; do not install new ones.
|
|
445
|
+
* Maps to `--only-upgrade` where applicable.
|
|
446
|
+
*/
|
|
447
|
+
only_upgrade?: boolean
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* If set (seconds), skip `apt-get update` when the apt lists directory mtime
|
|
451
|
+
* is newer than `now - cache_valid_time`. Best-effort heuristic.
|
|
452
|
+
*/
|
|
453
|
+
cache_valid_time?: number
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Local `.deb` path(s) to install directly. Accepts a single path or list.
|
|
457
|
+
* If provided, {@link AptModuleOptions.name | name} is optional.
|
|
458
|
+
*/
|
|
459
|
+
deb?: string | string[]
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Prefer `apt-get` explicitly. (Kept for parity; module already uses `apt-get`.)
|
|
463
|
+
*/
|
|
464
|
+
force_apt_get?: boolean
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Run `apt-get clean` at the end.
|
|
468
|
+
*/
|
|
469
|
+
clean?: boolean
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Shorthand to add `--force-confnew` to dpkg options.
|
|
473
|
+
* (Equivalent to including it in {@link AptModuleOptions.dpkg_options | dpkg_options}.)
|
|
474
|
+
*/
|
|
475
|
+
dpkg_force_confnew?: boolean
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Shorthand to add `--force-confdef` to dpkg options.
|
|
479
|
+
* (Equivalent to including it in {@link AptModuleOptions.dpkg_options | dpkg_options}.)
|
|
480
|
+
*/
|
|
481
|
+
dpkg_force_confdef?: boolean
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* If `true`, fail the task when `apt-get update` ultimately fails after retries.
|
|
485
|
+
* @defaultValue true
|
|
486
|
+
*/
|
|
487
|
+
update_cache_error_fatal?: boolean
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Result returned by the **apt** module.
|
|
492
|
+
*/
|
|
493
|
+
export type AptModuleResult = {
|
|
494
|
+
/**
|
|
495
|
+
* Whether any changes were made (install/remove/upgrade/autoremove/clean).
|
|
496
|
+
*/
|
|
497
|
+
changed: boolean
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Standard output from the last apt/dpkg command executed (best-effort).
|
|
501
|
+
*/
|
|
502
|
+
stdout?: string
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Standard error from the last apt/dpkg command executed (best-effort).
|
|
506
|
+
*/
|
|
507
|
+
stderr?: string
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Whether the package cache was updated during this run.
|
|
511
|
+
*/
|
|
512
|
+
updated_cache?: boolean
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Whether an upgrade action (per {@link AptModuleOptions.upgrade}) ran.
|
|
516
|
+
*/
|
|
517
|
+
upgraded?: boolean
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Whether `apt-get clean` ran.
|
|
521
|
+
*/
|
|
522
|
+
cleaned?: boolean
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Whether `apt-get autoremove` ran.
|
|
526
|
+
*/
|
|
527
|
+
autoremove?: boolean
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Human-readable message when the module surfaces an error condition.
|
|
531
|
+
*/
|
|
532
|
+
msg?: string
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function quote(v: string) {
|
|
536
|
+
return JSON.stringify(v)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function joinPkgs(pkgs?: string | string[]): string[] {
|
|
540
|
+
if (!pkgs) return []
|
|
541
|
+
return Array.isArray(pkgs) ? pkgs : [pkgs]
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function nowSeconds(): number {
|
|
545
|
+
return Math.floor(Date.now() / 1000)
|
|
546
|
+
}
|