@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.
Files changed (71) hide show
  1. package/README.md +1 -0
  2. package/cli/katmer.js +28 -0
  3. package/cli/run.ts +16 -0
  4. package/index.ts +5 -0
  5. package/lib/config.ts +82 -0
  6. package/lib/interfaces/config.interface.ts +113 -0
  7. package/lib/interfaces/executor.interface.ts +13 -0
  8. package/lib/interfaces/module.interface.ts +170 -0
  9. package/lib/interfaces/provider.interface.ts +214 -0
  10. package/lib/interfaces/task.interface.ts +100 -0
  11. package/lib/katmer.ts +126 -0
  12. package/lib/lookup/env.lookup.ts +13 -0
  13. package/lib/lookup/file.lookup.ts +23 -0
  14. package/lib/lookup/index.ts +46 -0
  15. package/lib/lookup/url.lookup.ts +21 -0
  16. package/lib/lookup/var.lookup.ts +13 -0
  17. package/lib/module.ts +560 -0
  18. package/lib/module_registry.ts +64 -0
  19. package/lib/modules/apt-repository/apt-repository.module.ts +435 -0
  20. package/lib/modules/apt-repository/apt-sources-list.ts +363 -0
  21. package/lib/modules/apt.module.ts +546 -0
  22. package/lib/modules/archive.module.ts +280 -0
  23. package/lib/modules/become.module.ts +119 -0
  24. package/lib/modules/copy.module.ts +807 -0
  25. package/lib/modules/cron.module.ts +541 -0
  26. package/lib/modules/debug.module.ts +231 -0
  27. package/lib/modules/gather_facts.module.ts +605 -0
  28. package/lib/modules/git.module.ts +243 -0
  29. package/lib/modules/hostname.module.ts +213 -0
  30. package/lib/modules/http/http.curl.module.ts +342 -0
  31. package/lib/modules/http/http.local.module.ts +253 -0
  32. package/lib/modules/http/http.module.ts +298 -0
  33. package/lib/modules/index.ts +14 -0
  34. package/lib/modules/package.module.ts +283 -0
  35. package/lib/modules/script.module.ts +121 -0
  36. package/lib/modules/set_fact.module.ts +171 -0
  37. package/lib/modules/systemd_service.module.ts +373 -0
  38. package/lib/modules/template.module.ts +478 -0
  39. package/lib/providers/local.provider.ts +336 -0
  40. package/lib/providers/provider_response.ts +20 -0
  41. package/lib/providers/ssh/ssh.provider.ts +420 -0
  42. package/lib/providers/ssh/ssh.utils.ts +31 -0
  43. package/lib/schemas/katmer_config.schema.json +358 -0
  44. package/lib/target_resolver.ts +298 -0
  45. package/lib/task/controls/environment.control.ts +42 -0
  46. package/lib/task/controls/index.ts +13 -0
  47. package/lib/task/controls/loop.control.ts +89 -0
  48. package/lib/task/controls/register.control.ts +23 -0
  49. package/lib/task/controls/until.control.ts +64 -0
  50. package/lib/task/controls/when.control.ts +25 -0
  51. package/lib/task/task.ts +225 -0
  52. package/lib/utils/ajv.utils.ts +24 -0
  53. package/lib/utils/cls.ts +4 -0
  54. package/lib/utils/datetime.utils.ts +15 -0
  55. package/lib/utils/errors.ts +25 -0
  56. package/lib/utils/execute-shell.ts +116 -0
  57. package/lib/utils/file.utils.ts +68 -0
  58. package/lib/utils/http.utils.ts +10 -0
  59. package/lib/utils/json.utils.ts +15 -0
  60. package/lib/utils/number.utils.ts +9 -0
  61. package/lib/utils/object.utils.ts +11 -0
  62. package/lib/utils/os.utils.ts +31 -0
  63. package/lib/utils/path.utils.ts +9 -0
  64. package/lib/utils/renderer/render_functions.ts +3 -0
  65. package/lib/utils/renderer/renderer.ts +89 -0
  66. package/lib/utils/renderer/twig.ts +191 -0
  67. package/lib/utils/string.utils.ts +33 -0
  68. package/lib/utils/typed-event-emitter.ts +26 -0
  69. package/lib/utils/unix.utils.ts +91 -0
  70. package/lib/utils/windows.utils.ts +92 -0
  71. package/package.json +67 -0
@@ -0,0 +1,280 @@
1
+ import { type ModuleCommonReturn } from "../interfaces/module.interface"
2
+ import type { Katmer } from "../interfaces/task.interface"
3
+ import type { KatmerProvider } from "../interfaces/provider.interface"
4
+ import { KatmerModule } from "../module"
5
+
6
+ declare module "../interfaces/task.interface" {
7
+ export namespace Katmer {
8
+ export interface TaskActions {
9
+ archive?: ArchiveModuleOptions
10
+ }
11
+ }
12
+ }
13
+ /**
14
+ * Archive/create/extract/list files using the system tar on all OSes.
15
+ *
16
+ * @remarks
17
+ * - Supports formats: tar, tar.gz, tar.bz2, tar.xz, tar.zst, zip (via bsdtar).
18
+ * - Works on SSHProvider and LocalProvider.
19
+ * - Matches flags available in bsdtar(1) with sensible defaults.
20
+ *
21
+ * @example Create a gzip archive:
22
+ * ```yaml
23
+ * - name: Create a gzip archive
24
+ * archive:
25
+ * path:
26
+ * - /var/log
27
+ * - /etc/nginx
28
+ * dest: /tmp/system.tar.gz
29
+ * gzip: true
30
+ * verbose: true
31
+ * ```
32
+ *
33
+ * @example Extract with strip components:
34
+ * ```yaml
35
+ * - name: Extract release
36
+ * archive:
37
+ * src: /tmp/release.tar.gz
38
+ * dest: /opt/app
39
+ * strip_components: 1
40
+ * ```
41
+ *
42
+ * @example List archive contents:
43
+ * ```yaml
44
+ * - name: List archive
45
+ * archive:
46
+ * src: /tmp/archive.tar.xz
47
+ * list: true
48
+ * ```
49
+ */
50
+ export class ArchiveModule extends KatmerModule<
51
+ ArchiveModuleOptions,
52
+ ArchiveModuleResult,
53
+ KatmerProvider
54
+ > {
55
+ static name = "archive" as const
56
+
57
+ constraints = {
58
+ platform: { any: { packages: ["tar"] } }
59
+ }
60
+
61
+ async check(): Promise<void> {
62
+ const p = this.params
63
+ if (!p) throw new Error("archive: options are required")
64
+ if (!p.src && !p.path)
65
+ throw new Error("archive: one of 'src' or 'path' is required")
66
+ if (!p.dest && !p.list && !p.options)
67
+ throw new Error(
68
+ "archive: 'dest' is required unless listing or raw options"
69
+ )
70
+ }
71
+
72
+ async initialize(): Promise<void> {}
73
+ async cleanup(): Promise<void> {}
74
+
75
+ async execute(ctx: Katmer.TaskContext): Promise<ArchiveModuleResult> {
76
+ const p = normalizeOptions(this.params)
77
+ const sh = (v: string) => JSON.stringify(v)
78
+
79
+ const tarCmd = await this.detectTar(ctx)
80
+
81
+ // Build args
82
+ const args: string[] = []
83
+
84
+ // Determine mode
85
+ if (p.list) args.push("-t")
86
+ else if (p.src) args.push("-x")
87
+ else args.push("-c")
88
+
89
+ // Verbose
90
+ if (p.verbose) args.push("-v")
91
+
92
+ // Compression
93
+ if (p.gzip) args.push("--gzip")
94
+ if (p.bzip2) args.push("--bzip2")
95
+ if (p.xz) args.push("--xz")
96
+ if (p.zstd) args.push("--zstd")
97
+
98
+ // Archive file
99
+ const archiveArg = p.src ?? p.dest
100
+ if (archiveArg) args.push("-f", sh(archiveArg))
101
+
102
+ // Directory change before action
103
+ if (p.chdir) args.push("-C", sh(p.chdir))
104
+
105
+ // Creation paths
106
+ if (p.path && !p.src) {
107
+ const list = Array.isArray(p.path) ? p.path : [p.path]
108
+ for (const item of list) args.push(sh(item))
109
+ }
110
+
111
+ // Filter flags
112
+ if (p.strip_components != null)
113
+ args.push(`--strip-components=${p.strip_components}`)
114
+
115
+ if (p.exclude) for (const ex of p.exclude) args.push(`--exclude=${sh(ex)}`)
116
+
117
+ // Ownership/Permission
118
+ if (p.numeric_owner) args.push("--numeric-owner")
119
+ if (p.uid != null) args.push(`--uid=${p.uid}`)
120
+ if (p.gid != null) args.push(`--gid=${p.gid}`)
121
+
122
+ if (p.preserve_permissions) args.push("--preserve-permissions")
123
+ if (p.no_same_owner) args.push("--no-same-owner")
124
+ if (p.no_same_permissions) args.push("--no-same-permissions")
125
+
126
+ // Raw extra options
127
+ if (p.options) args.push(...p.options)
128
+
129
+ // Final command
130
+ const cmd = `${tarCmd} ${args.join(" ")}`
131
+
132
+ const r = await ctx.exec(cmd)
133
+
134
+ return {
135
+ changed: !p.list,
136
+ failed: r.code !== 0,
137
+ stdout: r.stdout,
138
+ stderr: r.stderr,
139
+ dest: p.dest
140
+ }
141
+ }
142
+
143
+ private async detectTar(ctx: Katmer.TaskContext): Promise<string> {
144
+ // Try bsdtar first, then tar
145
+ const bins = ["tar", "tar.exe"]
146
+ for (const b of bins) {
147
+ try {
148
+ await ctx.exec(`${b} --version`)
149
+ return b
150
+ } catch {
151
+ /* ignore */
152
+ }
153
+ }
154
+ throw new Error("archive: neither bsdtar nor tar was found on target")
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Options for the {@link ArchiveModule | `archive`} module.
160
+ *
161
+ * @public
162
+ */
163
+ export interface ArchiveModuleOptions {
164
+ /**
165
+ * Archive file to extract or list
166
+ */
167
+ src?: string
168
+
169
+ /**
170
+ * Destination directory (where to extract or where the created archive is saved)
171
+ */
172
+ dest?: string
173
+
174
+ /**
175
+ * Path(s) to include when creating an archive
176
+ */
177
+ path?: string | string[]
178
+
179
+ /**
180
+ * Change to this directory before running tar (`-C`)
181
+ */
182
+ chdir?: string
183
+
184
+ /**
185
+ * When true, list archive contents instead of extract/create
186
+ */
187
+ list?: boolean
188
+
189
+ /**
190
+ * Compression options (mutually exclusive)
191
+ */
192
+ gzip?: boolean
193
+ bzip2?: boolean
194
+ xz?: boolean
195
+ zstd?: boolean
196
+
197
+ /**
198
+ * Strip leading path components on extract
199
+ */
200
+ strip_components?: number
201
+
202
+ /**
203
+ * Exclude patterns (glob-like)
204
+ */
205
+ exclude?: string[]
206
+
207
+ /**
208
+ * Preserve permissions on extract
209
+ */
210
+ preserve_permissions?: boolean
211
+
212
+ /**
213
+ * Don’t restore owner on extract
214
+ */
215
+ no_same_owner?: boolean
216
+
217
+ /**
218
+ * Don’t restore permissions on extract
219
+ */
220
+ no_same_permissions?: boolean
221
+
222
+ /**
223
+ * When true, use numeric owner/gid
224
+ */
225
+ numeric_owner?: boolean
226
+
227
+ /**
228
+ * Force a specific uid on extract
229
+ */
230
+ uid?: number
231
+
232
+ /**
233
+ * Force a specific gid on extract
234
+ */
235
+ gid?: number
236
+
237
+ /**
238
+ * Verbose output (`-v`)
239
+ */
240
+ verbose?: boolean
241
+
242
+ /**
243
+ * Extra raw flags passed directly to tar (after bsdtar detection)
244
+ */
245
+ options?: string[]
246
+ }
247
+
248
+ /**
249
+ * Result of the archive operation.
250
+ *
251
+ * @public
252
+ */
253
+ export interface ArchiveModuleResult extends ModuleCommonReturn {
254
+ /**
255
+ * Destination directory or archive path that was acted upon
256
+ */
257
+ dest?: string
258
+ }
259
+
260
+ /* normalize and defaults */
261
+ function normalizeOptions(opts: ArchiveModuleOptions): ArchiveModuleOptions {
262
+ return {
263
+ ...opts,
264
+ list: opts.list ?? false,
265
+ gzip: opts.gzip ?? false,
266
+ bzip2: opts.bzip2 ?? false,
267
+ xz: opts.xz ?? false,
268
+ zstd: opts.zstd ?? false,
269
+ strip_components: opts.strip_components ?? 0,
270
+ exclude: opts.exclude ?? [],
271
+ preserve_permissions: opts.preserve_permissions ?? false,
272
+ no_same_owner: opts.no_same_owner ?? false,
273
+ no_same_permissions: opts.no_same_permissions ?? false,
274
+ numeric_owner: opts.numeric_owner ?? false,
275
+ uid: opts.uid ?? undefined,
276
+ gid: opts.gid ?? undefined,
277
+ verbose: opts.verbose ?? false,
278
+ options: opts.options ?? []
279
+ }
280
+ }
@@ -0,0 +1,119 @@
1
+ import { type ModuleConstraints } from "../interfaces/module.interface"
2
+ import type { SSHProvider } from "../providers/ssh/ssh.provider"
3
+ import type { Katmer } from "../interfaces/task.interface"
4
+ import { KatmerModule } from "../module"
5
+
6
+ declare module "../interfaces/task.interface" {
7
+ export namespace Katmer {
8
+ export interface TaskActions {
9
+ become?: BecomeModuleOptions
10
+ }
11
+ }
12
+ }
13
+ /**
14
+ * When `true`, enable sudo with defaults.
15
+ * When `false`, disable privilege escalation (no-op).
16
+ * Or provide an object to customize user/prompt/password.
17
+ */
18
+ export type BecomeModuleOptions =
19
+ | boolean
20
+ | {
21
+ /**
22
+ * Target user to run commands as (e.g. "root" or "deploy").
23
+ */
24
+ user?: string
25
+ /**
26
+ * Password for sudo (falls back to provider's password if omitted).
27
+ */
28
+ password?: string
29
+ /**
30
+ * Prompt marker for sudo; used to detect when to send the password.
31
+ */
32
+ prompt?: string
33
+ }
34
+
35
+ /**
36
+ * Control privilege escalation.
37
+ *
38
+ * @remarks
39
+ * - Set to `true` to enable sudo with sensible defaults (uses the provider's password if available).
40
+ * - Set to `false` to disable privilege escalation (no-op).
41
+ * - Provide an object to override sudo behavior (`user`, `password`, `prompt`).
42
+ * - The module rewrites subsequent commands to `sudo -S -p [prompt] [-u <user>] ...`
43
+ * and automatically responds to the prompt with the configured password.
44
+ *
45
+ * @examples
46
+ * ```yaml
47
+ * # Enable with defaults (use provider password, run as root)
48
+ * - name: Run with sudo
49
+ * become: true
50
+ *
51
+ * # Custom sudo user and prompt
52
+ * - name: Run as deploy user with custom prompt
53
+ * become:
54
+ * user: deploy
55
+ * prompt: "SUDO:"
56
+ *
57
+ * # Explicit password override (falls back to provider password if omitted)
58
+ * - name: Use an explicit sudo password
59
+ * become:
60
+ * user: root
61
+ * password: "{{ VAULT_SUDO_PASSWORD }}"
62
+ *
63
+ * # Disable privilege escalation
64
+ * - name: Run without sudo
65
+ * become: false
66
+ * ```
67
+ */
68
+ export class BecomeModule extends KatmerModule<
69
+ BecomeModuleOptions,
70
+ {},
71
+ SSHProvider
72
+ > {
73
+ static internal = true
74
+
75
+ static name = "become" as const
76
+
77
+ constraints = {
78
+ platform: {
79
+ any: true
80
+ }
81
+ } satisfies ModuleConstraints
82
+
83
+ async check(_ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {}
84
+
85
+ async initialize(ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {
86
+ if (this.params === false) return
87
+
88
+ const opts = Object.assign(
89
+ {
90
+ prompt: "KATMER_SUDO_PROMPT:",
91
+ user: "",
92
+ password: (ctx.provider as any).options?.password
93
+ },
94
+ this.params === true ? {} : this.params
95
+ )
96
+ const promptMarker = opts.prompt ?? "KATMER_SUDO_PROMPT:"
97
+ const interactivePassword =
98
+ opts.password ?? (ctx.provider as any).options?.password ?? ""
99
+ const userPart = opts.user ? ` -u ${opts.user}` : ""
100
+
101
+ // Supply rewrite + prompt handling hints for downstream exec()
102
+ const rewriteCommand = (prepared: string) => {
103
+ return `sudo -S -p '${promptMarker}'${userPart} ${prepared}`
104
+ }
105
+
106
+ ctx.exec = ctx.provider.executor({
107
+ rewriteCommand,
108
+ promptMarker: promptMarker,
109
+ interactivePassword: interactivePassword,
110
+ hidePromptLine: true
111
+ })
112
+ }
113
+
114
+ async execute(_ctx: Katmer.TaskContext<SSHProvider>) {
115
+ return { changed: false }
116
+ }
117
+
118
+ async cleanup(_ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {}
119
+ }