paratix 0.0.1

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 (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -0
  3. package/package.json +29 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastian Software GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # Paratix
2
+
3
+ Idempotent VPS configuration in TypeScript.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/paratix)](https://www.npmjs.com/package/paratix)
6
+ [![license](https://img.shields.io/npm/l/paratix)](https://opensource.org/licenses/MIT)
7
+ [![node](https://img.shields.io/node/v/paratix)](https://nodejs.org/)
8
+
9
+ ## Overview
10
+
11
+ Paratix configures Linux servers over SSH using TypeScript playbooks. Each playbook declares the desired state of a server -- packages, files, services, firewall rules -- and Paratix enforces that state idempotently. Every module follows a check/apply pattern: `check` determines whether work is needed, `apply` makes it so.
12
+
13
+ If you have used Ansible, the idea is the same. The difference is that you write TypeScript instead of YAML, with full type safety and your existing toolchain.
14
+
15
+ ## Getting started
16
+
17
+ Scaffold a new project:
18
+
19
+ ```bash
20
+ npm create paratix my-server
21
+ # or: pnpm create paratix my-server
22
+ # or: yarn create paratix my-server
23
+ # or: bun create paratix my-server
24
+ ```
25
+
26
+ This creates a project with the following structure:
27
+
28
+ ```
29
+ my-server/
30
+ server.ts # Your playbook
31
+ files/ # Templates and config files
32
+ package.json
33
+ tsconfig.json
34
+ ```
35
+
36
+ Open `server.ts` and define your server:
37
+
38
+ ```typescript
39
+ import { server } from "paratix"
40
+ import { hostname, package as pkg } from "paratix/modules"
41
+
42
+ export default server({
43
+ name: "web-01",
44
+ host: "10.0.0.1",
45
+ ssh: {
46
+ user: "root",
47
+ ports: [22],
48
+ privateKey: "~/.ssh/id_ed25519",
49
+ },
50
+ run: [hostname.set("web-01"), pkg.update("2025-03-01"), pkg.installed("nginx", "curl", "git")],
51
+ })
52
+ ```
53
+
54
+ Apply the playbook:
55
+
56
+ ```bash
57
+ npx paratix apply server.ts
58
+ ```
59
+
60
+ Preview changes without applying them:
61
+
62
+ ```bash
63
+ npx paratix apply server.ts --dry-run
64
+ ```
65
+
66
+ ## Core concepts
67
+
68
+ ### Playbook
69
+
70
+ A playbook is a TypeScript file that default-exports a `server()` call. It declares the target host, SSH credentials, environment variables, and an ordered list of modules to run.
71
+
72
+ ```typescript
73
+ import { server } from "paratix"
74
+
75
+ export default server({
76
+ name: "web-01",
77
+ host: "10.0.0.1",
78
+ ssh: { user: "root", ports: [22], privateKey: "~/.ssh/id_ed25519" },
79
+ env: {
80
+ DOMAIN: "example.com",
81
+ APP_PORT: 3000,
82
+ },
83
+ run: [
84
+ // modules go here
85
+ ],
86
+ signals: [
87
+ // fire after run if anything changed
88
+ ],
89
+ })
90
+ ```
91
+
92
+ ### Module
93
+
94
+ 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.
95
+
96
+ ```typescript
97
+ import { package as pkg, service } from "paratix/modules"
98
+
99
+ pkg.installed("nginx") // installs nginx if missing, does nothing if present
100
+ service.running("nginx") // starts nginx if stopped, does nothing if running
101
+ ```
102
+
103
+ ### Recipe
104
+
105
+ 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.
106
+
107
+ ```typescript
108
+ import { recipe } from "paratix"
109
+ import { package as pkg, file, service } from "paratix/modules"
110
+
111
+ const nginx = recipe(
112
+ "nginx",
113
+ [
114
+ pkg.installed("nginx"),
115
+ file.template("/etc/nginx/nginx.conf", "./files/nginx.conf.tmpl"),
116
+ service.enabled("nginx"),
117
+ service.running("nginx"),
118
+ ],
119
+ {
120
+ signals: [service.reload("nginx")],
121
+ }
122
+ )
123
+ ```
124
+
125
+ If the config file changes, `service.reload("nginx")` fires. If nothing changed, the reload is skipped.
126
+
127
+ ### Signals
128
+
129
+ Signals are modules that run only when something changed. Use them for actions like reloading a service after a config file update.
130
+
131
+ 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.
132
+
133
+ ```typescript
134
+ export default server({
135
+ // ...
136
+ run: [file.template("/etc/myapp/config.yml", "./files/config.yml.tmpl")],
137
+ signals: [service.restart("myapp")],
138
+ })
139
+ ```
140
+
141
+ **Note:** `service.restart()` and `service.reload()` always apply when called. Place them in `signals`, not directly in `run`.
142
+
143
+ ### Environment
144
+
145
+ The `env` object makes values available to templates and conditional logic. Values can be strings, numbers, or async functions (resolved lazily on first access).
146
+
147
+ ```typescript
148
+ export default server({
149
+ // ...
150
+ env: {
151
+ DOMAIN: "example.com",
152
+ APP_PORT: 3000,
153
+ DB_PASSWORD: async () => fetchFromVault("db-password"),
154
+ },
155
+ run: [
156
+ /* ... */
157
+ ],
158
+ })
159
+ ```
160
+
161
+ Modules can return `meta` in their result, which merges into the environment for subsequent modules.
162
+
163
+ ### Templates
164
+
165
+ `file.template()` deploys a file with `{{KEY}}` placeholders resolved from the environment.
166
+
167
+ Template file (`files/nginx.conf.tmpl`):
168
+
169
+ ```
170
+ server {
171
+ listen 80;
172
+ server_name {{DOMAIN}};
173
+ proxy_pass http://127.0.0.1:{{APP_PORT}};
174
+ }
175
+ ```
176
+
177
+ ```typescript
178
+ file.template("/etc/nginx/sites-available/default", "./files/nginx.conf.tmpl")
179
+ ```
180
+
181
+ Use `\{{` to produce a literal `{{` in the output. Unknown keys throw an error at runtime.
182
+
183
+ ## CLI reference
184
+
185
+ ```
186
+ paratix apply <file>
187
+
188
+ Options:
189
+ --dry-run Check only, don't apply
190
+ --env <key=value> Set environment variable (repeatable)
191
+ --env-file <path> Load .env file
192
+ --reconnect-timeout <s> SSH reconnect timeout in seconds (default: 300)
193
+ --verbose Show full stack traces on error
194
+ --version Show version number
195
+ --help Show help
196
+ ```
197
+
198
+ ## Module reference
199
+
200
+ All modules are imported from `"paratix/modules"`.
201
+
202
+ **Note:** `package` is a reserved word in JavaScript. Import it as: `import { package as pkg } from "paratix/modules"`.
203
+
204
+ ### System
205
+
206
+ | Namespace | Methods |
207
+ | ---------- | --------------------------- |
208
+ | `hostname` | `set` |
209
+ | `system` | `facts`, `reboot`, `uptime` |
210
+ | `sysctl` | `set` |
211
+ | `mount` | `present`, `absent` |
212
+
213
+ ### Packages
214
+
215
+ | Namespace | Methods |
216
+ | ---------------- | --------------------------------------------- |
217
+ | `package` | `installed`, `absent`, `update`, `upgrade` |
218
+ | `apt` | `debconf`, `distUpgrade`, `key`, `repository` |
219
+ | `releaseUpgrade` | `upgrade` |
220
+
221
+ ### Files
222
+
223
+ | Namespace | Methods |
224
+ | ---------- | ------------------------------------------------------------------------------------------------------- |
225
+ | `file` | `absent`, `assemble`, `block`, `copy`, `directory`, `line`, `properties`, `replace`, `stat`, `template` |
226
+ | `archive` | `extract` |
227
+ | `download` | `url`, `github`, `large` |
228
+ | `git` | `clone` |
229
+ | `rsync` | `sync` |
230
+
231
+ ### Services
232
+
233
+ | Namespace | Methods |
234
+ | --------- | ------------------------------------------------------------------------- |
235
+ | `service` | `running`, `stopped`, `enabled`, `disabled`, `restart`, `reload`, `facts` |
236
+ | `systemd` | `unit`, `daemonReload`, `masked`, `unmasked` |
237
+
238
+ ### Users and groups
239
+
240
+ | Namespace | Methods |
241
+ | --------- | ------------------- |
242
+ | `user` | `present`, `absent` |
243
+ | `group` | `present`, `absent` |
244
+
245
+ ### Network and security
246
+
247
+ | Namespace | Methods |
248
+ | --------- | ------------------------------ |
249
+ | `ufw` | `enabled`, `rule` |
250
+ | `ssh` | `authorizedKeys`, `knownHosts` |
251
+ | `sshd` | `config`, `port` |
252
+
253
+ ### Scheduling
254
+
255
+ | Namespace | Methods |
256
+ | --------- | ------- |
257
+ | `cron` | `job` |
258
+
259
+ ### Commands
260
+
261
+ | Namespace | Methods |
262
+ | --------- | ------- |
263
+ | `command` | `shell` |
264
+
265
+ ### Secrets
266
+
267
+ | Namespace | Methods |
268
+ | --------- | --------- |
269
+ | `op` | `resolve` |
270
+
271
+ ## Custom modules
272
+
273
+ A custom module is an object with `name`, `check`, and `apply`:
274
+
275
+ ```typescript
276
+ import type { Module, ModuleResult, SshConnection, Environment } from "paratix"
277
+ import { NEEDS_APPLY } from "paratix"
278
+
279
+ function ensureFile(path: string, content: string): Module {
280
+ return {
281
+ name: `ensure-file: ${path}`,
282
+
283
+ async check(ssh: SshConnection | null, env: Environment): Promise<"needs-apply" | "ok"> {
284
+ if (!ssh) return NEEDS_APPLY
285
+ const current = await ssh.readFile(path).catch(() => null)
286
+ return current === content ? "ok" : NEEDS_APPLY
287
+ },
288
+
289
+ async apply(ssh: SshConnection | null, env: Environment): Promise<ModuleResult> {
290
+ if (!ssh) return { status: "failed" }
291
+ await ssh.writeFile(path, content)
292
+ return { status: "changed" }
293
+ },
294
+ }
295
+ }
296
+ ```
297
+
298
+ Rules for custom modules:
299
+
300
+ - `check` returns `"ok"` or `NEEDS_APPLY` (use the exported constant, not the string `"needs-apply"`).
301
+ - `apply` returns `{ status }` where status is `"changed"`, `"failed"`, `"ok"`, or `"skipped"`.
302
+ - Always handle the case where `ssh` is `null` (happens for local-only modules).
303
+ - Return `meta` from `apply` to pass data to subsequent modules via the environment.
304
+ - Set `local: true` on the module object if it runs on the local machine instead of over SSH.
305
+
306
+ ## SshConnection API
307
+
308
+ Methods available on the `ssh` parameter in custom modules:
309
+
310
+ | Method | Returns | Description |
311
+ | ----------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------- |
312
+ | `exec(cmd, options?)` | `Promise<ExecResult>` | Run a command. Returns `{ code, stdout, stderr }`. Throws on non-zero exit unless `ignoreExitCode: true`. |
313
+ | `test(cmd)` | `Promise<boolean>` | Run a command, return `true` if exit code is 0. |
314
+ | `output(cmd)` | `Promise<string>` | Run a command, return trimmed stdout. |
315
+ | `lines(cmd)` | `Promise<string[]>` | Run a command, return stdout split into lines. |
316
+ | `exists(path)` | `Promise<boolean>` | Check if a remote path exists. |
317
+ | `readFile(path)` | `Promise<string>` | Read a remote file. |
318
+ | `writeFile(path, content)` | `Promise<void>` | Write content to a remote file. |
319
+ | `uploadFile(local, remote)` | `Promise<void>` | Upload a local file via SFTP. |
320
+ | `downloadFile(remote, local)` | `Promise<void>` | Download a remote file. |
321
+ | `sha256(path)` | `Promise<string \| null>` | Get SHA-256 hex digest, or `null` if the file does not exist. |
322
+
323
+ Additional methods (`addPort`, `disconnect`, `getConnectionInfo`, `probeSudo`, `updateHost`) are available for advanced use cases. See the type definitions for details.
324
+
325
+ ## Built-in helpers
326
+
327
+ Import these from `"paratix"`:
328
+
329
+ | Helper | Description |
330
+ | ------------------------------ | ------------------------------------------------------------------------------------------------- |
331
+ | `recipe(name, modules, opts?)` | Group modules into a reusable unit with optional signals. See [Recipes](#recipe) above. |
332
+ | `assert(condition, message)` | Abort the run if `condition` returns false. The condition receives the current environment. |
333
+ | `when(condition, ...modules)` | Run modules only if `condition` returns true. Skipped modules report `"skipped"`, not `"failed"`. |
334
+ | `debug(message)` | Print a debug message during the run. |
335
+ | `fail(message)` | Abort the run unconditionally with a failure. |
336
+ | `pause(message?)` | Wait for the operator to press Enter before continuing. |
337
+ | `shellQuote(value)` | Safely quote a string for shell interpolation. |
338
+ | `NEEDS_APPLY` | Constant to return from `check` when work is needed. |
339
+
340
+ ## LLM Guide
341
+
342
+ 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.
343
+
344
+ ## License
345
+
346
+ MIT
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "paratix",
3
+ "version": "0.0.1",
4
+ "description": "Idempotent VPS setup tool in TypeScript",
5
+ "type": "module",
6
+ "keywords": [
7
+ "paratix",
8
+ "vps",
9
+ "server",
10
+ "ssh",
11
+ "deployment",
12
+ "idempotent",
13
+ "typescript",
14
+ "devops",
15
+ "infrastructure",
16
+ "automation"
17
+ ],
18
+ "homepage": "https://paratix.oss.sebastian-software.com",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/sebastian-software/paratix.git",
22
+ "directory": "packages/paratix"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/sebastian-software/paratix/issues"
26
+ },
27
+ "author": "Sebastian Fastner <s.fastner@sebastian-software.de>",
28
+ "license": "MIT"
29
+ }