create-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.
Files changed (2) hide show
  1. package/README.md +56 -319
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,368 +1,105 @@
1
1
  # create-paratix
2
2
 
3
- Scaffolds a new [Paratix](https://github.com/sebastian-software/paratix) server project. The CLI now explains which initial SSH user Paratix needs for the very first connection and lets you choose the matching bootstrap path via arrow-key selection: explicit `root` bootstrap or direct admin-user hardening.
3
+ Scaffold a new Paratix server project in a few minutes.
4
4
 
5
- ## Quick Start
5
+ `create-paratix` is the fastest way to start using Paratix on a real server. Instead of assembling a playbook, scripts, formatting, and bootstrap logic by hand, it gives you a ready-to-run project with a sensible structure and a hardened first-run workflow.
6
6
 
7
- **Step 1 -- Create the project**
7
+ It is designed for the moment when you already know the server you want to manage, but do not want to rebuild the same setup every time. The scaffold gives you a practical default project, then leaves the actual infrastructure logic in your hands as normal TypeScript.
8
8
 
9
- ```sh
10
- # npm
11
- npm create paratix my-server
9
+ For fresh machines, it also handles the awkward part that usually gets glossed over: initial SSH access, first-run hardening, switching to a dedicated admin user, and continuing safely from there.
12
10
 
13
- # pnpm
14
- pnpm create paratix my-server
11
+ ## Features
15
12
 
16
- # yarn
17
- yarn create paratix my-server
13
+ - **Project scaffold for Paratix**: generates a ready-to-edit server project instead of just a single file.
14
+ - **First-run bootstrap flow**: supports explicit `--first-run` hardening before later service rollout.
15
+ - **Initial user selection**: works with either a root bootstrap or an existing admin user.
16
+ - **SSH host-key bootstrap**: can pin the current host fingerprint during scaffolding.
17
+ - **DX-friendly TypeScript setup**: direct `tsx` execution, bundler-style module resolution, ESLint, and Prettier included.
18
+ - **Practical project defaults**: scripts, formatting, files directory, env example, and ignore files are created for you.
18
19
 
19
- # bun
20
- bunx create-paratix my-server
21
- ```
20
+ ## Getting Started
22
21
 
23
- Optional non-interactive bootstrap values:
22
+ Create a new project:
24
23
 
25
- ```sh
24
+ ```bash
26
25
  # npm
27
- npm create paratix my-server -- --host example.com --initial-user root --admin-public-key-file ~/.ssh/id_ed25519.pub
26
+ npm create paratix my-server
28
27
 
29
28
  # pnpm
30
- pnpm create paratix my-server --host example.com --initial-user root --admin-public-key-file ~/.ssh/id_ed25519.pub
29
+ pnpm create paratix my-server
31
30
 
32
31
  # yarn
33
- yarn create paratix my-server --host deploy.example.com --initial-user deploy --admin-public-key "ssh-ed25519 AAAA... you@example.com"
32
+ yarn create paratix my-server
34
33
 
35
34
  # bun
36
- bunx create-paratix my-server --host deploy.example.com --initial-user deploy --admin-public-key-file ~/.ssh/id_ed25519.pub
35
+ bunx create-paratix my-server
37
36
  ```
38
37
 
39
- **Step 2 -- Enter the directory**
38
+ Then enter the directory and review the generated playbook:
40
39
 
41
- Dependencies are installed automatically. If installation fails, run your package manager's install command manually.
42
-
43
- ```sh
40
+ ```bash
44
41
  cd my-server
45
42
  ```
46
43
 
47
- **Step 3 -- Review `server.ts`** with your final hostname, admin username, selected public key or placeholder, and the modules you want to apply.
48
-
49
- The scaffold also includes an explicit bootstrap switch driven by `PARATIX_FIRST_RUN`:
50
-
51
- - first run: call `paratix apply ... --first-run`, which sets `PARATIX_FIRST_RUN=true`, keeps SSH on port `22`, opens firewall port `22`, and uses `strictHostKeyChecking: "accept-new"`
52
- - root bootstrap path: the generated playbook also writes a dedicated `/etc/sudoers.d` drop-in so the new admin user can continue with `NOPASSWD sudo` after the first run
53
- - later runs: call `paratix apply ...` without `--first-run`, so the generated playbook switches to port `2222`, closes SSH port `22` in the firewall, and returns to strict host-key checking
54
- - the generated playbook still opens port `2222` before `sshd.port(2222)` runs, so the first real apply can reconnect safely
55
- - the generated playbook now stops the explicit first run after SSH hardening, kernel hardening, and automatic security upgrades, so later application services run only on the hardened baseline
56
-
57
- **Step 4 -- Apply**
58
-
59
- ```sh
60
- # pnpm / yarn / bun
61
- pnpm apply:dry # dry run first -- see what would change
62
- pnpm apply # apply to the server
63
-
64
- # npm
65
- npm run apply:dry
66
- npm run apply
67
- npm run lint
68
- npm run format:check
69
- npm run format:fix
70
- ```
71
-
72
- ## Project Structure
73
-
74
- | File / Directory | Purpose |
75
- | ------------------ | -------------------------------------------------------------------------------- |
76
- | `server.ts` | Your playbook. Edit this file. |
77
- | `package.json` | Includes `apply`, `apply:dry`, `lint`, `format:check`, and `format:fix` scripts. |
78
- | `tsconfig.json` | TypeScript config (ES2024, ESNext/Bundler, strict) for direct `tsx` use. |
79
- | `eslint.config.ts` | ESLint config via `eslint-config-setup` for Node-based scaffold projects. |
80
- | `.prettierrc` | Prettier defaults for the scaffolded project. |
81
- | `.prettierignore` | Excludes lockfiles from Prettier runs. |
82
- | `.gitignore` | Excludes `node_modules/`, `dist/`, `.env`, and log files. |
83
- | `.env.example` | Template for secrets. Copy to `.env` and fill in values. |
84
- | `files/` | Place template files here. They get uploaded to the server at apply time. |
85
-
86
- ## Writing Your Playbook
87
-
88
- `server.ts` exports a server definition. The generated file depends on the initial SSH user you choose:
89
-
90
- - `root`: Bootstrap once via `root`, create a dedicated admin user, then switch `ssh.user` to that admin user and disable root login.
91
- - `admin`: Connect directly as the named admin user and scaffold the hardened end state immediately.
92
-
93
- The direct admin-user path looks like this:
94
-
95
- ```typescript
96
- import { recipe, server } from "paratix"
97
- import { hostname, net, package as packages, ssh, sshd, ufw, user } from "paratix/modules"
98
-
99
- const adminUser = "paratix"
100
- const adminPublicKey = "ssh-ed25519 REPLACE_ME_WITH_YOUR_PUBLIC_KEY"
101
- const serverName = "my-server"
102
- const FIRST_RUN = process.env["PARATIX_FIRST_RUN"] === "true"
103
- const sshPorts = FIRST_RUN ? [22] : [2222]
104
- const firewallTcpPorts = FIRST_RUN ? [22, 2222, 80, 443] : [2222, 80, 443]
105
- const strictHostKeyChecking = FIRST_RUN ? "accept-new" : "yes"
106
-
107
- export default server({
108
- host: "1.2.3.4",
109
- name: serverName,
110
- env: {
111
- FIRST_RUN,
112
- SERVER_NAME: serverName,
113
- SSH_PORT: 2222,
114
- },
115
- ssh: {
116
- ports: sshPorts,
117
- privateKey: "~/.ssh/id_ed25519", // "~" is expanded by Paratix
118
- user: adminUser,
119
- // FIRST_RUN keeps the bootstrap path explicit:
120
- // - pass `paratix apply ... --first-run` for the bootstrap run
121
- // - later runs omit that flag and go through port 2222 with strict host-key checking again
122
- strictHostKeyChecking,
123
- // expectedHostFingerprint: "SHA256:REPLACE_ME_WITH_YOUR_HOST_FINGERPRINT",
124
- // expectedHostPublicKey: "ssh-ed25519 REPLACE_ME_WITH_YOUR_HOST_PUBLIC_KEY",
125
- },
126
- run: [
127
- net.hosts("127.0.1.1", [serverName]),
128
- hostname.set(serverName),
129
- packages.upgrade("2026-03-01"),
130
- packages.installed("curl", "htop", "ufw"),
131
-
132
- recipe("admin-access", [
133
- user.present(adminUser, {
134
- groups: ["sudo"],
135
- shell: "/bin/bash",
136
- }),
137
- ssh.authorizedKeys(adminUser, adminPublicKey),
138
- ]),
139
-
140
- recipe("firewall", [ufw.rule("allow", firewallTcpPorts), ufw.enabled()]),
141
-
142
- recipe("ssh-hardening", [
143
- sshd.port(2222),
144
- sshd.config({
145
- PasswordAuthentication: "no",
146
- PermitRootLogin: "no",
147
- }),
148
- ]),
149
-
150
- recipe("kernel-hardening", [
151
- sysctl.set("fs.protected_hardlinks", "1"),
152
- sysctl.set("fs.protected_symlinks", "1"),
153
- sysctl.set("kernel.dmesg_restrict", "1"),
154
- sysctl.set("kernel.kptr_restrict", "2"),
155
- sysctl.set("net.ipv4.conf.all.rp_filter", "1"),
156
- sysctl.set("net.ipv4.conf.default.rp_filter", "1"),
157
- sysctl.set("net.ipv4.tcp_syncookies", "1"),
158
- ]),
159
-
160
- recipe("automatic-security-upgrades", [
161
- packages.installed("unattended-upgrades"),
162
- file.copy("/etc/apt/apt.conf.d/20auto-upgrades", "./files/20auto-upgrades", {
163
- mode: "0644",
164
- owner: "root:root",
165
- }),
166
- file.copy("/etc/apt/apt.conf.d/50unattended-upgrades", "./files/50unattended-upgrades", {
167
- mode: "0644",
168
- owner: "root:root",
169
- }),
170
- ]),
171
-
172
- firstRun.stop("Bootstrap foundation complete; rerun without --first-run to continue."),
173
-
174
- // Add application and user-facing services below this line.
175
- ],
176
- })
177
- ```
178
-
179
- The generated `tsconfig.json` is intentionally DX-oriented for `tsx`-executed TypeScript projects:
180
-
181
- - `module: "ESNext"`
182
- - `moduleResolution: "Bundler"`
183
- - `include: ["**/*.ts"]`
184
-
185
- That means extensionless relative imports in your `.ts` sources work without NodeNext-style `.js` suffixes. This is a deliberate trade-off in favor of authoring ergonomics over strict Node-ESM path checking.
186
-
187
- The scaffold also includes Prettier out of the box:
188
-
189
- - `format:check` runs `prettier --check .`
190
- - `format:fix` runs `prettier --write .`
191
- - `.prettierignore` excludes lockfiles such as `pnpm-lock.yaml`
192
-
193
- The scaffold also includes ESLint:
194
-
195
- - `lint` runs `eslint .`
196
- - `eslint.config.ts` uses `await getEslintConfig({ node: true })`
197
-
198
- Wenn dein Server initial nur `root` per SSH anbietet, wähle im Prompt `root` oder rufe das Scaffold nicht-interaktiv mit `--initial-user root` auf. Dieses Template bleibt bewusst als temporärer Bootstrap markiert, erstellt den dedizierten Admin-User, legt einen `NOPASSWD sudo`-Eintrag für ihn unter `/etc/sudoers.d/` an und lässt Root-Login nur vorübergehend auf `prohibit-password`, bis du `ssh.user` auf den Admin-User umgestellt hast.
199
-
200
- Wenn bereits ein Admin-User wie `deploy`, `ubuntu` oder `admin` existiert, wähle diesen Namen direkt. Dann erzeugt `create-paratix` keinen Root-Bootstrap-Pfad, sondern scaffoldet sofort den gehärteten Zielzustand für genau diesen User.
201
-
202
- ### Initial user selection
203
-
204
- Standardmäßig fragt `create-paratix` interaktiv:
205
-
206
- 1. Welche Domain oder IP soll als Zielhost in `server.ts` stehen?
207
- 2. Ist der initiale SSH-User `root` oder ein Admin-User?
208
- 3. Falls Admin-User: Wie heißt dieser User konkret?
209
- 4. Soll ein vorhandener Public Key aus `~/.ssh` direkt übernommen werden?
210
-
211
- Im interaktiven Modus zeigt `create-paratix` dafür eine kurze Erklärung und eine Auswahl per Pfeiltasten:
212
-
213
- - `Root user`: frischer Server mit SSH nur als `root`; Paratix bootstrapt zuerst einen dedizierten Admin-User
214
- - `Admin user`: ein konkreter Admin-User existiert bereits; Paratix verbindet sich direkt als dieser User
215
-
216
- Nicht-interaktiv funktioniert derselbe Vertrag über `--host`, `--initial-user` und optional einen Admin-Public-Key:
217
-
218
- ```sh
219
- # Root bootstrap
220
- pnpm create paratix my-server --host example.com --initial-user root --admin-public-key-file ~/.ssh/id_ed25519.pub
221
-
222
- # Existing admin user
223
- pnpm create paratix my-server --host deploy.example.com --initial-user deploy --admin-public-key "ssh-ed25519 AAAA... you@example.com"
224
- ```
225
-
226
- Wichtig für den ersten echten Lauf: Das Scaffold liest `FIRST_RUN` aus `process.env.PARATIX_FIRST_RUN`. Für den Bootstrap rufst du Paratix explizit mit `--first-run` auf. Danach lässt du den Flag bei normalen Läufen weg; dann verwendet dasselbe Playbook Port `2222`, entfernt Port `22` aus der Firewall und kehrt zu strengem Host-Key-Checking zurück. Die Firewall-Freigabe für `2222` bleibt bewusst vor dem eigentlichen SSH-Portwechsel, damit Paratix nach `sshd.port(...)` sofort sicher reconnecten kann.
227
-
228
- ### Public key bootstrap
229
-
230
- Im interaktiven Modus bietet `create-paratix` zusätzlich an, einen vorhandenen Public Key aus
231
- `~/.ssh` direkt in `server.ts` zu übernehmen.
232
-
233
- - Wenn du zustimmst und mehrere `.pub`-Dateien existieren, kannst du den gewünschten Key per
234
- Pfeiltasten auswählen.
235
- - Wenn keine lesbaren `.pub`-Dateien gefunden werden, bleibt das bestehende Placeholder-Template
236
- erhalten.
237
- - Nicht-interaktiv kannst du denselben Wert über `--admin-public-key` oder
238
- `--admin-public-key-file` setzen.
239
- - Wenn kein CLI-Key gesetzt ist, bleibt in nicht-interaktiven Aufrufen weiter der Placeholder
240
- erhalten.
241
-
242
- ### Host-key bootstrap
243
-
244
- Paratix verwendet standardmäßig striktes Host-Key-Checking. `create-paratix` bietet deshalb interaktiv an, den aktuell auf SSH-Port `22` präsentierten Host-Key direkt per `ssh2` auszulesen und als `expectedHostFingerprint` in `server.ts` zu pinnen.
245
-
246
- Wenn du diesen Schritt bestätigst, erzeugt das Scaffold direkt einen expliziten Trust Anchor:
247
-
248
- ```ts
249
- const strictHostKeyChecking = "yes"
250
- // ...
251
- expectedHostFingerprint: "SHA256:..."
252
- ```
253
-
254
- Das ist ein bewusster TOFU-Schritt beim Scaffold-Zeitpunkt: Der Fingerprint stammt von dem Host-Key, den der Server im Moment des Scaffoldings auf Port `22` präsentiert. Du kannst ihn später jederzeit manuell prüfen oder ersetzen.
255
-
256
- Wenn du den Abruf ablehnst oder er fehlschlägt, bleibt der bisherige Fallback erhalten:
257
-
258
- ```ts
259
- const FIRST_RUN = process.env["PARATIX_FIRST_RUN"] === "true"
260
- const strictHostKeyChecking = FIRST_RUN ? "accept-new" : "yes"
261
- ```
262
-
263
- Das ist ein bewusst markierter Übergangsmodus für den ersten verifizierten Kontakt mit einem frischen Host. Direkt daneben enthält das generierte `server.ts` weiterhin kommentierte Platzhalter für:
264
-
265
- - `expectedHostFingerprint`
266
- - `expectedHostPublicKey`
267
-
268
- Empfohlener Ablauf:
269
-
270
- 1. Verifiziere den Host-Key deines Servers out of band.
271
- 2. Führe den ersten `apply:dry` und `apply` mit `--first-run` aus.
272
- 3. Führe spätere Runs ohne `--first-run` aus.
273
- 4. Optional: pinne zusätzlich `expectedHostFingerprint` oder `expectedHostPublicKey`.
274
-
275
- Key concepts:
44
+ Run the bootstrap flow:
276
45
 
277
- - **Modules** -- each item in `run` is a module. A module checks the current server state and applies changes only when needed (idempotent).
278
- - **Recipes** -- `recipe()` groups related modules under a name. If any module in the group changes something, signals fire after the group completes.
279
- - **Signals** -- actions that run after a recipe when at least one module in it made a change. Useful for reloading services.
280
- - **Env** -- values in the `env` field are available in template files as `{{KEY}}`. See [Environment Variables](#environment-variables) below.
281
-
282
- For the full list of built-in modules and their options, see the [Paratix module reference](https://github.com/sebastian-software/paratix).
283
-
284
- ## Applying to a Server
285
-
286
- Always do a dry run first to see what Paratix would change without touching the server:
287
-
288
- ```sh
46
+ ```bash
289
47
  pnpm apply:dry
290
- ```
291
-
292
- Then apply for real:
293
-
294
- ```sh
48
+ pnpm apply --first-run
295
49
  pnpm apply
296
50
  ```
297
51
 
298
- These scripts map to:
299
-
300
- ```sh
301
- paratix apply server.ts --dry-run
302
- paratix apply server.ts
303
- ```
304
-
305
- ### CLI flags
52
+ The first run is intentionally staged. It applies the hardened baseline first, then later runs continue with the remaining services and application-specific steps.
306
53
 
307
- | Flag | Default | Description |
308
- | ------------------------------- | -------- | -------------------------------------------------------------------- |
309
- | `<file>` | required | Path to the playbook file (`.ts` or `.js`). |
310
- | `--dry-run` | `false` | Check state only. Shows what would change without applying anything. |
311
- | `--env <key=value>` | `{}` | Set or override an env value. Repeatable: `--env A=1 --env B=2`. |
312
- | `--env-file <path>` | -- | Load a `.env` file. |
313
- | `--reconnect-timeout <seconds>` | `300` | Seconds to wait for SSH reconnect after a port or reboot change. |
314
- | `--verbose` | `false` | Show full stack traces on errors. |
54
+ ## What You Get
315
55
 
316
- After all modules run, Paratix prints a summary:
56
+ The scaffolded project includes:
317
57
 
318
- ```
319
- 3 changed · 5 ok · 0 skipped · 0 failed
320
- ```
58
+ | File / Directory | Purpose |
59
+ | --------------------------------- | ----------------------------------------------------------- |
60
+ | `server.ts` | Your Paratix playbook |
61
+ | `files/` | Templates and configuration files to upload |
62
+ | `package.json` | Apply, lint, and formatting scripts |
63
+ | `tsconfig.json` | TypeScript config for direct `tsx` execution |
64
+ | `eslint.config.ts` | ESLint config using `await getEslintConfig({ node: true })` |
65
+ | `.prettierrc` / `.prettierignore` | Formatting defaults, including lockfile ignores |
66
+ | `.env.example` | Starting point for environment values |
321
67
 
322
- ## Environment Variables
68
+ ## Bootstrap Model
323
69
 
324
- Copy `.env.example` to `.env` and fill in your values:
70
+ `create-paratix` supports two entry paths:
325
71
 
326
- ```sh
327
- cp .env.example .env
328
- ```
72
+ - **Root bootstrap**: for a fresh server that still only accepts SSH as `root`
73
+ - **Admin-user bootstrap**: for a server where a dedicated admin user already exists
329
74
 
330
- The example contains:
331
-
332
- ```sh
333
- # Server configuration
334
- # SUDO_PASSWORD=your-sudo-password
335
- # SSH_KEY_PATH=~/.ssh/id_ed25519
336
- ```
75
+ The generated project keeps this explicit:
337
76
 
338
- Env values come from three sources, merged in this order (last wins):
77
+ - `paratix apply ... --first-run` stays on port `22`, completes the hardened bootstrap stage, and stops intentionally at the first-run checkpoint
78
+ - later runs use the hardened path, usually on port `2222`, with strict host-key checking again
339
79
 
340
- 1. `--env-file <path>`
341
- 2. The `env` field in `server()`
342
- 3. `--env <key=value>` flags -- these have the highest priority
80
+ When you scaffold interactively, the CLI can also:
343
81
 
344
- > **Note:** CLI `--env` flags override both `.env` files and `server({ env })`. Put stable project defaults in `server({ env })`, environment-specific values in `.env` files, and one-off overrides on the CLI.
82
+ - select an admin public key from `~/.ssh`
83
+ - pin the current host key from SSH port `22` as `expectedHostFingerprint`
345
84
 
346
- ### Template files
85
+ ## Non-Interactive Usage
347
86
 
348
- Files in `files/` with a `.tmpl` extension support `{{KEY}}` placeholders. Paratix replaces them with env values at apply time.
87
+ You can provide the important bootstrap values on the command line:
349
88
 
350
- ```
351
- files/nginx.conf.tmpl
89
+ ```bash
90
+ pnpm create paratix my-server --host example.com --initial-user root --admin-public-key-file ~/.ssh/id_ed25519.pub
352
91
  ```
353
92
 
354
- ```nginx
355
- server {
356
- server_name {{SERVER_NAME}};
357
- }
358
- ```
93
+ Or for an existing admin user:
359
94
 
360
- To write a literal `{{`, use `\{{`.
95
+ ```bash
96
+ pnpm create paratix my-server --host deploy.example.com --initial-user deploy --admin-public-key "ssh-ed25519 AAAA... you@example.com"
97
+ ```
361
98
 
362
- ## Requirements
99
+ ## After Scaffolding
363
100
 
364
- - Node.js >= 24.0.0
101
+ Edit `server.ts` and extend the generated baseline with your own services, files, deployments, and runtime configuration. The scaffold is meant to get you to a safe and productive starting point quickly, not to lock you into a fixed project shape.
365
102
 
366
103
  ## License
367
104
 
368
- MIT
105
+ MIT — Copyright 2026 [Sebastian Software GmbH](https://sebastian-software.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-paratix",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Scaffold a new Paratix server project",
5
5
  "type": "module",
6
6
  "files": [