paratix 0.2.0 → 0.4.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/README.md +60 -358
- package/dist/{chunk-ULJMW23T.js → chunk-C45YPXCX.js} +35 -2
- package/dist/chunk-C45YPXCX.js.map +1 -0
- package/dist/{chunk-DUIGEB2J.js → chunk-EGP3QRLV.js} +5 -2
- package/dist/chunk-EGP3QRLV.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +16 -1
- package/dist/index.js +182 -98
- package/dist/index.js.map +1 -1
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules/index.js +1 -1
- package/llm-guide.md +39 -4
- package/package.json +1 -1
- package/dist/chunk-DUIGEB2J.js.map +0 -1
- package/dist/chunk-ULJMW23T.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,43 +1,50 @@
|
|
|
1
1
|
# Paratix
|
|
2
2
|
|
|
3
|
-
Idempotent VPS
|
|
3
|
+
Idempotent VPS automation in TypeScript.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/paratix)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://nodejs.org/)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Paratix lets you manage Linux servers over SSH with TypeScript playbooks instead of YAML or ad-hoc shell scripts. You describe the desired end state of a machine, run the playbook, and Paratix changes only what is necessary.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
It is built for developers and operators who want infrastructure automation that feels like application code: typed, reviewable, composable, and easy to keep in version control. You can start small on a single VPS and still keep a disciplined, repeatable workflow.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The result is a practical server automation tool with a compact mental model: modules check state, modules apply state, recipes group related work, and signals run only when changes actually happened.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Features
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- **Idempotent runs**: rerunning the same playbook on an already configured server is safe.
|
|
18
|
+
- **TypeScript authoring**: use regular `.ts` files with imports, conditions, and editor tooling.
|
|
19
|
+
- **Resilient SSH flow**: reconnects after reboots and SSH port changes when modules require it.
|
|
20
|
+
- **Structured orchestration**: recipes and signals keep service reloads and grouped changes explicit.
|
|
21
|
+
- **Declarative host guards**: gate modules on package, command, file, directory, symlink, or socket state without embedding shell checks in strings.
|
|
22
|
+
- **Strong bootstrap story**: supports explicit first-run flows and strict host-key handling.
|
|
23
|
+
- **Practical built-in modules**: packages, files, services, users, SSH, firewall, systemd, sysctl, mount, rsync, and more.
|
|
24
|
+
|
|
25
|
+
## Getting Started
|
|
26
|
+
|
|
27
|
+
If you want the fastest path, scaffold a project first:
|
|
18
28
|
|
|
19
29
|
```bash
|
|
20
30
|
npm create paratix my-server
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
cd my-server
|
|
32
|
+
npm run apply:dry
|
|
33
|
+
npm run apply -- --first-run
|
|
34
|
+
npm run apply
|
|
24
35
|
```
|
|
25
36
|
|
|
26
|
-
|
|
37
|
+
If you want to install `paratix` directly:
|
|
27
38
|
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
server.ts # Your playbook
|
|
31
|
-
files/ # Templates and config files
|
|
32
|
-
package.json
|
|
33
|
-
tsconfig.json
|
|
39
|
+
```bash
|
|
40
|
+
npm install paratix
|
|
34
41
|
```
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
Create a playbook:
|
|
37
44
|
|
|
38
45
|
```typescript
|
|
39
46
|
import { server } from "paratix"
|
|
40
|
-
import { hostname, package as pkg } from "paratix/modules"
|
|
47
|
+
import { hostname, package as pkg, service } from "paratix/modules"
|
|
41
48
|
|
|
42
49
|
export default server({
|
|
43
50
|
name: "web-01",
|
|
@@ -45,13 +52,19 @@ export default server({
|
|
|
45
52
|
ssh: {
|
|
46
53
|
user: "root",
|
|
47
54
|
ports: [22],
|
|
48
|
-
privateKey: "~/.ssh/id_ed25519",
|
|
55
|
+
privateKey: "~/.ssh/id_ed25519",
|
|
49
56
|
},
|
|
50
|
-
run: [
|
|
57
|
+
run: [
|
|
58
|
+
hostname.set("web-01"),
|
|
59
|
+
pkg.update("2026-03-01"),
|
|
60
|
+
pkg.installed("nginx", "curl"),
|
|
61
|
+
service.enabled("nginx"),
|
|
62
|
+
service.running("nginx"),
|
|
63
|
+
],
|
|
51
64
|
})
|
|
52
65
|
```
|
|
53
66
|
|
|
54
|
-
Apply
|
|
67
|
+
Apply it:
|
|
55
68
|
|
|
56
69
|
```bash
|
|
57
70
|
npx paratix apply server.ts
|
|
@@ -63,362 +76,51 @@ Preview changes without applying them:
|
|
|
63
76
|
npx paratix apply server.ts --dry-run
|
|
64
77
|
```
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
For SSH hardening modules, Paratix now goes one step further:
|
|
68
|
-
|
|
69
|
-
- `sshd.config` validates the prospective config with `sshd -t`
|
|
70
|
-
- `sshd.port` validates the prospective config with `sshd -t`
|
|
71
|
-
|
|
72
|
-
Runtime effects are still intentionally not executed during `--dry-run`. In particular, reloads,
|
|
73
|
-
restarts, port switches, firewall reachability, and reconnect behavior are reported as limited
|
|
74
|
-
verification in the run output instead of being performed.
|
|
75
|
-
|
|
76
|
-
## SSH host key migration
|
|
77
|
-
|
|
78
|
-
Paratix now defaults to strict host-key checking (`ssh.strictHostKeyChecking: "yes"`).
|
|
79
|
-
Existing playbooks that relied on implicit TOFU must now opt in explicitly:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
ssh: {
|
|
83
|
-
user: "root",
|
|
84
|
-
ports: [22],
|
|
85
|
-
privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
|
|
86
|
-
strictHostKeyChecking: "accept-new", // explicit TOFU opt-in
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
For a safer bootstrap of brand-new hosts, pin the expected host key instead of using TOFU:
|
|
79
|
+
## How Paratix Works
|
|
91
80
|
|
|
92
|
-
|
|
93
|
-
ssh: {
|
|
94
|
-
user: "root",
|
|
95
|
-
ports: [22],
|
|
96
|
-
privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
|
|
97
|
-
expectedHostFingerprint: "SHA256:your-known-fingerprint",
|
|
98
|
-
}
|
|
99
|
-
```
|
|
81
|
+
### Playbooks
|
|
100
82
|
|
|
101
|
-
|
|
83
|
+
A playbook is a TypeScript file that default-exports `server(...)`. It defines the target host, SSH configuration, optional environment values, and an ordered list of modules to run.
|
|
102
84
|
|
|
103
|
-
|
|
85
|
+
### Modules
|
|
104
86
|
|
|
105
|
-
|
|
87
|
+
Modules are the smallest units of work. Each module checks whether its target state already exists and only applies changes when needed.
|
|
106
88
|
|
|
107
|
-
|
|
89
|
+
### Recipes
|
|
108
90
|
|
|
109
|
-
|
|
110
|
-
import { server } from "paratix"
|
|
111
|
-
|
|
112
|
-
export default server({
|
|
113
|
-
name: "web-01",
|
|
114
|
-
host: "10.0.0.1",
|
|
115
|
-
ssh: {
|
|
116
|
-
user: "root",
|
|
117
|
-
ports: [22],
|
|
118
|
-
privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
|
|
119
|
-
expectedHostFingerprint: "SHA256:your-known-fingerprint",
|
|
120
|
-
},
|
|
121
|
-
env: {
|
|
122
|
-
DOMAIN: "example.com",
|
|
123
|
-
APP_PORT: 3000,
|
|
124
|
-
},
|
|
125
|
-
run: [
|
|
126
|
-
// modules go here
|
|
127
|
-
],
|
|
128
|
-
signals: [
|
|
129
|
-
// fire after run if anything changed
|
|
130
|
-
],
|
|
131
|
-
})
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Module
|
|
135
|
-
|
|
136
|
-
A module is the smallest unit of configuration. Each module has a `check` function (is the desired state already in place?) and an `apply` function (enforce the desired state). Modules are idempotent: running them twice produces the same result as running them once.
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import { package as pkg, service } from "paratix/modules"
|
|
140
|
-
|
|
141
|
-
pkg.installed("nginx") // installs nginx if missing, does nothing if present
|
|
142
|
-
service.running("nginx") // starts nginx if stopped, does nothing if running
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Recipe
|
|
146
|
-
|
|
147
|
-
A recipe groups related modules into a reusable unit with its own signals. Signals on a recipe fire only when a module inside that recipe reported a change.
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import { recipe } from "paratix"
|
|
151
|
-
import { package as pkg, file, service } from "paratix/modules"
|
|
152
|
-
|
|
153
|
-
const nginx = recipe(
|
|
154
|
-
"nginx",
|
|
155
|
-
[
|
|
156
|
-
pkg.installed("nginx"),
|
|
157
|
-
file.template("/etc/nginx/nginx.conf", "./files/nginx.conf.tmpl"),
|
|
158
|
-
service.enabled("nginx"),
|
|
159
|
-
service.running("nginx"),
|
|
160
|
-
],
|
|
161
|
-
{
|
|
162
|
-
signals: [service.reload("nginx")],
|
|
163
|
-
}
|
|
164
|
-
)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
If the config file changes, `service.reload("nginx")` fires. If nothing changed, the reload is skipped.
|
|
91
|
+
Recipes group related modules into a named unit. They help structure larger playbooks and keep the CLI output readable.
|
|
168
92
|
|
|
169
93
|
### Signals
|
|
170
94
|
|
|
171
|
-
Signals are
|
|
172
|
-
|
|
173
|
-
At the server level, `signals` fire when any module in `run` reported a change. Inside a recipe, signals fire only when that recipe's modules changed.
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
export default server({
|
|
177
|
-
// ...
|
|
178
|
-
run: [file.template("/etc/myapp/config.yml", "./files/config.yml.tmpl")],
|
|
179
|
-
signals: [service.restart("myapp")],
|
|
180
|
-
})
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**Note:** `service.restart()` and `service.reload()` always apply when called. Place them in `signals`, not directly in `run`.
|
|
184
|
-
|
|
185
|
-
### Environment
|
|
186
|
-
|
|
187
|
-
The `env` object makes values available to templates and conditional logic. Values can be strings, numbers, or async functions (resolved lazily on first access).
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
export default server({
|
|
191
|
-
// ...
|
|
192
|
-
env: {
|
|
193
|
-
DOMAIN: "example.com",
|
|
194
|
-
APP_PORT: 3000,
|
|
195
|
-
DB_PASSWORD: async () => fetchFromVault("db-password"),
|
|
196
|
-
},
|
|
197
|
-
run: [
|
|
198
|
-
/* ... */
|
|
199
|
-
],
|
|
200
|
-
})
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Modules can return `meta` in their result, which merges into the environment for subsequent modules.
|
|
204
|
-
|
|
205
|
-
When environment values come from multiple sources, Paratix merges them in this order, with later values winning:
|
|
206
|
-
|
|
207
|
-
1. `--env-file <path>`
|
|
208
|
-
2. `server({ env })`
|
|
209
|
-
3. `--env <key=value>`
|
|
210
|
-
|
|
211
|
-
### Templates
|
|
212
|
-
|
|
213
|
-
`file.template()` deploys a file with `{{KEY}}` placeholders resolved from the environment.
|
|
95
|
+
Signals are deferred side effects such as `service.reload(...)` or `service.restart(...)`. They run when the surrounding scope actually changed, and can also be flushed explicitly with `signals.flush()` when you need a checkpoint inside a larger flow.
|
|
214
96
|
|
|
215
|
-
|
|
97
|
+
### Guards
|
|
216
98
|
|
|
217
|
-
|
|
218
|
-
server {
|
|
219
|
-
listen 80;
|
|
220
|
-
server_name {{DOMAIN}};
|
|
221
|
-
proxy_pass http://127.0.0.1:{{APP_PORT}};
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
file.template("/etc/nginx/sites-available/default", "./files/nginx.conf.tmpl")
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Use `\{{` to produce a literal `{{` in the output. Unknown keys throw an error at runtime.
|
|
99
|
+
Paratix also supports declarative host-state guards. Use `when.packageInstalled(...)`, `when.commandExists(...)`, `when.fileExists(...)`, `when.pathExists(...)`, `when.symlinkExists(...)`, or `when.socketExists(...)` and their inverted forms to gate modules or recipes on remote host state without shell-heavy playbooks.
|
|
230
100
|
|
|
231
|
-
## CLI
|
|
101
|
+
## CLI
|
|
232
102
|
|
|
233
|
-
```
|
|
234
|
-
paratix apply <file>
|
|
103
|
+
```text
|
|
104
|
+
paratix apply <file> [options]
|
|
235
105
|
|
|
236
106
|
Options:
|
|
237
|
-
--dry-run
|
|
238
|
-
--env <key=value>
|
|
239
|
-
--env-file <path>
|
|
240
|
-
--
|
|
241
|
-
--
|
|
242
|
-
--
|
|
243
|
-
--
|
|
107
|
+
--dry-run
|
|
108
|
+
--env <key=value>
|
|
109
|
+
--env-file <path>
|
|
110
|
+
--first-run
|
|
111
|
+
--reconnect-timeout <seconds>
|
|
112
|
+
--verbose
|
|
113
|
+
--version
|
|
114
|
+
--help
|
|
244
115
|
```
|
|
245
116
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
All modules are imported from `"paratix/modules"`.
|
|
249
|
-
|
|
250
|
-
**Note:** `package` is a reserved word in JavaScript. Import it as: `import { package as pkg } from "paratix/modules"`.
|
|
251
|
-
|
|
252
|
-
### System
|
|
253
|
-
|
|
254
|
-
| Namespace | Methods |
|
|
255
|
-
| ---------- | --------------------------- |
|
|
256
|
-
| `hostname` | `set` |
|
|
257
|
-
| `system` | `facts`, `reboot`, `uptime` |
|
|
258
|
-
| `sysctl` | `set` |
|
|
259
|
-
| `mount` | `present`, `absent` |
|
|
260
|
-
|
|
261
|
-
### Packages
|
|
262
|
-
|
|
263
|
-
| Namespace | Methods |
|
|
264
|
-
| ---------------- | --------------------------------------------- |
|
|
265
|
-
| `package` | `installed`, `absent`, `update`, `upgrade` |
|
|
266
|
-
| `apt` | `debconf`, `distUpgrade`, `key`, `repository` |
|
|
267
|
-
| `releaseUpgrade` | `upgrade` |
|
|
268
|
-
|
|
269
|
-
### Files
|
|
270
|
-
|
|
271
|
-
| Namespace | Methods |
|
|
272
|
-
| ---------- | ------------------------------------------------------------------------------------------------------- |
|
|
273
|
-
| `file` | `absent`, `assemble`, `block`, `copy`, `directory`, `line`, `properties`, `replace`, `stat`, `template` |
|
|
274
|
-
| `archive` | `extract` |
|
|
275
|
-
| `download` | `url`, `github`, `large` |
|
|
276
|
-
| `git` | `clone` |
|
|
277
|
-
| `rsync` | `sync` |
|
|
278
|
-
|
|
279
|
-
### Services
|
|
280
|
-
|
|
281
|
-
| Namespace | Methods |
|
|
282
|
-
| --------- | ------------------------------------------------------------------------- |
|
|
283
|
-
| `service` | `running`, `stopped`, `enabled`, `disabled`, `restart`, `reload`, `facts` |
|
|
284
|
-
| `systemd` | `unit`, `daemonReload`, `masked`, `unmasked` |
|
|
285
|
-
|
|
286
|
-
### Users and groups
|
|
287
|
-
|
|
288
|
-
| Namespace | Methods |
|
|
289
|
-
| --------- | ------------------- |
|
|
290
|
-
| `user` | `present`, `absent` |
|
|
291
|
-
| `group` | `present`, `absent` |
|
|
292
|
-
|
|
293
|
-
### Network and security
|
|
294
|
-
|
|
295
|
-
| Namespace | Methods |
|
|
296
|
-
| --------- | ------------------------------ |
|
|
297
|
-
| `ufw` | `enabled`, `rule` |
|
|
298
|
-
| `ssh` | `authorizedKeys`, `knownHosts` |
|
|
299
|
-
| `sshd` | `config`, `port` |
|
|
300
|
-
|
|
301
|
-
### Scheduling
|
|
302
|
-
|
|
303
|
-
| Namespace | Methods |
|
|
304
|
-
| --------- | ------- |
|
|
305
|
-
| `cron` | `job` |
|
|
306
|
-
|
|
307
|
-
### Commands
|
|
308
|
-
|
|
309
|
-
| Namespace | Methods |
|
|
310
|
-
| --------- | ------- |
|
|
311
|
-
| `command` | `shell` |
|
|
312
|
-
|
|
313
|
-
### Secrets
|
|
314
|
-
|
|
315
|
-
| Namespace | Methods |
|
|
316
|
-
| --------- | --------- |
|
|
317
|
-
| `op` | `resolve` |
|
|
318
|
-
|
|
319
|
-
## Custom modules
|
|
320
|
-
|
|
321
|
-
A custom module is an object with `name`, `check`, and `apply`:
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
import type { Module, ModuleResult, SshConnection, Environment } from "paratix"
|
|
325
|
-
import { NEEDS_APPLY } from "paratix"
|
|
326
|
-
|
|
327
|
-
function ensureFile(path: string, content: string): Module {
|
|
328
|
-
return {
|
|
329
|
-
name: `ensure-file: ${path}`,
|
|
330
|
-
|
|
331
|
-
async check(ssh: SshConnection | null, env: Environment): Promise<"needs-apply" | "ok"> {
|
|
332
|
-
if (!ssh) return NEEDS_APPLY
|
|
333
|
-
const current = await ssh.readFile(path).catch(() => null)
|
|
334
|
-
return current === content ? "ok" : NEEDS_APPLY
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
async apply(ssh: SshConnection | null, env: Environment): Promise<ModuleResult> {
|
|
338
|
-
if (!ssh) return { status: "failed" }
|
|
339
|
-
await ssh.writeFile(path, content)
|
|
340
|
-
return { status: "changed" }
|
|
341
|
-
},
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
Rules for custom modules:
|
|
347
|
-
|
|
348
|
-
- `check` returns `"ok"` or `NEEDS_APPLY` (use the exported constant, not the string `"needs-apply"`).
|
|
349
|
-
- `apply` returns `{ status }` where status is `"changed"`, `"failed"`, `"ok"`, or `"skipped"`.
|
|
350
|
-
- Always handle the case where `ssh` is `null` (happens for local-only modules).
|
|
351
|
-
- Return `meta` from `apply` to pass data to subsequent modules via the environment.
|
|
352
|
-
- Set `local: true` on the module object if it runs on the local machine instead of over SSH.
|
|
353
|
-
|
|
354
|
-
## SshConnection API
|
|
355
|
-
|
|
356
|
-
Methods available on the `ssh` parameter in custom modules:
|
|
357
|
-
|
|
358
|
-
| Method | Returns | Description |
|
|
359
|
-
| ----------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
360
|
-
| `exec(cmd, options?)` | `Promise<ExecResult>` | Run a command. Returns `{ code, stdout, stderr }`. Throws on non-zero exit unless `ignoreExitCode: true`. |
|
|
361
|
-
| `test(cmd)` | `Promise<boolean>` | Run a command, return `true` if exit code is 0. |
|
|
362
|
-
| `output(cmd)` | `Promise<string>` | Run a command, return trimmed stdout. |
|
|
363
|
-
| `lines(cmd)` | `Promise<string[]>` | Run a command, return stdout split into lines. |
|
|
364
|
-
| `exists(path)` | `Promise<boolean>` | Check if a remote path exists. |
|
|
365
|
-
| `readFile(path)` | `Promise<string>` | Read a remote file. |
|
|
366
|
-
| `writeFile(path, content)` | `Promise<void>` | Write content to a remote file. |
|
|
367
|
-
| `uploadFile(local, remote)` | `Promise<void>` | Upload a local file via SFTP. |
|
|
368
|
-
| `downloadFile(remote, local)` | `Promise<void>` | Download a remote file. |
|
|
369
|
-
| `sha256(path)` | `Promise<string \| null>` | Get SHA-256 hex digest, or `null` if the file does not exist. |
|
|
370
|
-
|
|
371
|
-
Additional methods (`addPort`, `disconnect`, `getConnectionInfo`, `probeSudo`, `updateHost`) are available for advanced use cases. See the type definitions for details.
|
|
372
|
-
|
|
373
|
-
## Built-in helpers
|
|
374
|
-
|
|
375
|
-
Import these from `"paratix"`:
|
|
376
|
-
|
|
377
|
-
| Helper | Description |
|
|
378
|
-
| ------------------------------ | ------------------------------------------------------------------------------------------------- |
|
|
379
|
-
| `recipe(name, modules, opts?)` | Group modules into a reusable unit with optional signals. See [Recipes](#recipe) above. |
|
|
380
|
-
| `assert(condition, message)` | Abort the run if `condition` returns false. The condition receives the current environment. |
|
|
381
|
-
| `when(condition, ...modules)` | Run modules only if `condition` returns true. Skipped modules report `"skipped"`, not `"failed"`. |
|
|
382
|
-
| `debug(message)` | Print a debug message during the run. |
|
|
383
|
-
| `fail(message)` | Abort the run unconditionally with a failure. |
|
|
384
|
-
| `pause(message?)` | Wait for the operator to press Enter before continuing. |
|
|
385
|
-
| `shellQuote(value)` | Safely quote a string for shell interpolation. |
|
|
386
|
-
| `NEEDS_APPLY` | Constant to return from `check` when work is needed. |
|
|
387
|
-
|
|
388
|
-
## LLM Guide
|
|
389
|
-
|
|
390
|
-
This package includes an `llm-guide.md` file that provides detailed information for writing Paratix modules and playbooks. It covers the complete API reference, code patterns, and common mistakes to avoid. When using an LLM to generate Paratix code, point it at this file for best results.
|
|
391
|
-
|
|
392
|
-
## Integration tests
|
|
393
|
-
|
|
394
|
-
Paratix ships a separate integration test entry point for real SSH/SFTP checks:
|
|
395
|
-
|
|
396
|
-
```bash
|
|
397
|
-
pnpm --filter paratix test:integration
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
For the full workspace review path including integration coverage, use:
|
|
401
|
-
|
|
402
|
-
```bash
|
|
403
|
-
pnpm agent:check:integration
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
These tests are intentionally not part of the default unit test run. They require:
|
|
407
|
-
|
|
408
|
-
- on macOS: `colima` plus a working Docker CLI connected to the active Colima runtime
|
|
409
|
-
- on Linux/CI: a reachable Docker runtime
|
|
410
|
-
|
|
411
|
-
The integration suite starts a temporary SSH test container, runs the tests against it and removes the container again afterwards. If `colima` is missing, the suite aborts with a clear error message instead of hanging or silently skipping coverage.
|
|
117
|
+
`--first-run` is meant for explicit bootstrap flows where a fresh server must be hardened first and the rest of the system should only be applied later.
|
|
412
118
|
|
|
413
|
-
|
|
414
|
-
`file.directory`, `file.copy`, `file.template`, `command.shell`, `download.url`,
|
|
415
|
-
and `download.large`, including ownership, mode, content, large-download flags,
|
|
416
|
-
and idempotent `check()` behavior against a live server.
|
|
119
|
+
## Documentation
|
|
417
120
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
you want the integration path to run on GitHub.
|
|
121
|
+
- For project scaffolding, see [`create-paratix`](https://www.npmjs.com/package/create-paratix).
|
|
122
|
+
- For detailed authoring guidance and module reference inside this repo, see [llm-guide.md](./llm-guide.md).
|
|
421
123
|
|
|
422
124
|
## License
|
|
423
125
|
|
|
424
|
-
MIT
|
|
126
|
+
MIT — Copyright 2026 [Sebastian Software GmbH](https://sebastian-software.com)
|
|
@@ -4729,6 +4729,37 @@ var systemd = {
|
|
|
4729
4729
|
// src/modules/ufw.ts
|
|
4730
4730
|
var UFW = "ufw";
|
|
4731
4731
|
var ufw = {
|
|
4732
|
+
/**
|
|
4733
|
+
* Ensure UFW is inactive. If UFW is not installed, this is treated as already satisfied.
|
|
4734
|
+
*
|
|
4735
|
+
* @returns A Module that ensures UFW is disabled.
|
|
4736
|
+
*/
|
|
4737
|
+
disabled() {
|
|
4738
|
+
return {
|
|
4739
|
+
async apply(ssh2) {
|
|
4740
|
+
if (!ssh2) return failed("[ufw.disabled] SSH connection is required");
|
|
4741
|
+
const pm = await detectPackageManager(ssh2);
|
|
4742
|
+
if (pm == null || !await isPackageInstalled(ssh2, pm, UFW)) {
|
|
4743
|
+
return { status: "ok" };
|
|
4744
|
+
}
|
|
4745
|
+
const result = await ssh2.exec(`${UFW} --force disable`, {
|
|
4746
|
+
ignoreExitCode: true,
|
|
4747
|
+
silent: true
|
|
4748
|
+
});
|
|
4749
|
+
return result.code === 0 ? { status: "changed" } : failedCommand("[ufw.disabled] ufw disable failed", result);
|
|
4750
|
+
},
|
|
4751
|
+
async check(ssh2) {
|
|
4752
|
+
if (!ssh2) return NEEDS_APPLY;
|
|
4753
|
+
const pm = await detectPackageManager(ssh2);
|
|
4754
|
+
if (pm == null || !await isPackageInstalled(ssh2, pm, UFW)) {
|
|
4755
|
+
return "ok";
|
|
4756
|
+
}
|
|
4757
|
+
const status = await ssh2.output(`${UFW} status`);
|
|
4758
|
+
return status.includes("Status: inactive") ? "ok" : NEEDS_APPLY;
|
|
4759
|
+
},
|
|
4760
|
+
name: "ufw.disabled"
|
|
4761
|
+
};
|
|
4762
|
+
},
|
|
4732
4763
|
/**
|
|
4733
4764
|
* Ensure UFW is active. Enables the firewall non-interactively if not already running.
|
|
4734
4765
|
*
|
|
@@ -4932,6 +4963,9 @@ export {
|
|
|
4932
4963
|
failed,
|
|
4933
4964
|
failedCommand,
|
|
4934
4965
|
NEEDS_APPLY,
|
|
4966
|
+
detectPackageManager,
|
|
4967
|
+
isPackageInstalled,
|
|
4968
|
+
pkg,
|
|
4935
4969
|
apt,
|
|
4936
4970
|
archive,
|
|
4937
4971
|
command,
|
|
@@ -4945,7 +4979,6 @@ export {
|
|
|
4945
4979
|
mount,
|
|
4946
4980
|
net,
|
|
4947
4981
|
op,
|
|
4948
|
-
pkg,
|
|
4949
4982
|
releaseUpgrade,
|
|
4950
4983
|
rsync,
|
|
4951
4984
|
script,
|
|
@@ -4958,4 +4991,4 @@ export {
|
|
|
4958
4991
|
ufw,
|
|
4959
4992
|
user
|
|
4960
4993
|
};
|
|
4961
|
-
//# sourceMappingURL=chunk-
|
|
4994
|
+
//# sourceMappingURL=chunk-C45YPXCX.js.map
|