unitup 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.
- package/LICENSE +21 -0
- package/README.md +387 -0
- package/bin/unitup.js +4 -0
- package/index.d.ts +378 -0
- package/package.json +28 -0
- package/src/cli.js +351 -0
- package/src/doctor.js +109 -0
- package/src/index.js +70 -0
- package/src/systemd.js +506 -0
- package/src/unit.js +207 -0
- package/src/utils.js +184 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 unitup contributors
|
|
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 A PARTICULAR PURPOSE AND CLAIM,
|
|
19
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
20
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
21
|
+
DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# unitup
|
|
2
|
+
|
|
3
|
+
> Minimal, zero-dependency Node.js CLI & library to run Node.js scripts as `systemd` user services.
|
|
4
|
+
|
|
5
|
+
`unitup` provides a thin, user-level layer over Linux `systemd`. It does **not** create a custom daemon, manage process trees, or act as a PM2 alternative. Instead, it generates and manages native systemd user units (`~/.config/systemd/user/unitup-<name>.service`), letting systemd handle process supervision, auto-restarts, logging, and OS boot startup without requiring `sudo` privileges.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## `unitup` vs PM2: Key Differences and Core Promises
|
|
10
|
+
|
|
11
|
+
`unitup` is **not** a PM2 replacement or an independent process manager. While PM2 runs its own master daemon, `unitup` is strictly a thin and transparent CLI layer on top of Linux `systemd`.
|
|
12
|
+
|
|
13
|
+
### Comparison Table
|
|
14
|
+
|
|
15
|
+
| Feature | `unitup` | PM2 |
|
|
16
|
+
| :--- | :--- | :--- |
|
|
17
|
+
| **Background Process (Daemon)** | **None** (0 MB RAM). Systemd directly supervises processes. | **Yes** (PM2 master daemon consumes ~50-100 MB RAM in background). |
|
|
18
|
+
| **System Integration** | Native Linux OS `systemd` user service (`~/.config/systemd/user/`). | Custom process monitoring via PM2's internal daemon. |
|
|
19
|
+
| **Privileges Required** | Does **not** require `sudo`. | Requires `sudo` / `pm2 startup` for boot startup configuration. |
|
|
20
|
+
| **Dependencies** | **0 Runtime Dependencies** (Uses Node.js standard modules only). | Dozens of 3rd party npm packages. |
|
|
21
|
+
| **Log Management** | Delegates to native Linux `journald` system (`journalctl`). | Manages `.pm2/logs` files (requires `pm2-logrotate` for rotation). |
|
|
22
|
+
| **Boot Startup** | Native systemd lingering (`loginctl enable-linger`). | Custom startup script launching PM2 daemon. |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### What `unitup` Promises (Core Promises)
|
|
27
|
+
|
|
28
|
+
1. ⥠**Zero Memory & CPU Waste**: After running `unitup` commands to add or manage a service, `unitup` exits immediately. No extra Node.js master process stays running in the background.
|
|
29
|
+
2. ð **100% Transparency & Safety**: No hidden background tasks. Generated `.service` files are saved as standard text at `~/.config/systemd/user/` for easy inspection. 100% safe against shell injection.
|
|
30
|
+
3. ð ïļ **Automatic Path & Environment Resolution**: Automatically detects `PATH` issues common with NVM, FNM, or custom Node.js installations and injects the resolved `PATH` environment into the unit file.
|
|
31
|
+
4. ðŠķ **Ultra Lightweight Footprint**: Zero bloat for your project or server; installs instantly and runs with minimal overhead.
|
|
32
|
+
|
|
33
|
+
### What `unitup` Does NOT Do (Non-Goals)
|
|
34
|
+
|
|
35
|
+
- â Does not write a custom daemon or process watcher system (does not attempt to behave like PM2).
|
|
36
|
+
- â No PM2 Cluster Mode (internal load balancing). Use Nginx, HAProxy, or Node.js native `cluster` module instead.
|
|
37
|
+
- â No web dashboard, metrics panel, or telemetry.
|
|
38
|
+
- â Does not manage or rotate log files directly; delegates 100% of logging to OS-level `journald`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- ⥠**Zero runtime dependencies** â ultra lightweight native ESM Node 20+ package.
|
|
45
|
+
- ð **No root / sudo required** â operates entirely within systemd user scope (`~/.config/systemd/user/`).
|
|
46
|
+
- ðĄïļ **Secure by design** â systemd escaping, strict service name sanitization, and shell-injection safe command execution (`execFile`/`spawn` with `shell: false`).
|
|
47
|
+
- ðĐš **System readiness check** â `unitup doctor` verifies OS, systemd PID 1, systemctl user bus, and user lingering state.
|
|
48
|
+
- ð **Compact status & logs** â clean summary output from `systemctl show` and live `journalctl` streaming.
|
|
49
|
+
- ðĶ **Programmatic API** â imported directly into JavaScript applications.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
- **OS:** Linux with `systemd`
|
|
56
|
+
- **Node.js:** `>= 20.0.0`
|
|
57
|
+
- **Permissions:** Standard non-root user session
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install -g unitup
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or install locally in your project:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install unitup
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 1. Verify system readiness
|
|
79
|
+
unitup doctor
|
|
80
|
+
|
|
81
|
+
# 2. Add your Node.js script as a user service and start it immediately
|
|
82
|
+
unitup add server.js --name api --env-file .env --start
|
|
83
|
+
|
|
84
|
+
# 3. Check service status
|
|
85
|
+
unitup status api
|
|
86
|
+
|
|
87
|
+
# 4. Stream real-time journalctl logs
|
|
88
|
+
unitup logs api --follow
|
|
89
|
+
|
|
90
|
+
# 5. Stop or remove service when no longer needed
|
|
91
|
+
unitup stop api
|
|
92
|
+
unitup remove api
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## CLI Commands
|
|
98
|
+
|
|
99
|
+
### `unitup doctor`
|
|
100
|
+
|
|
101
|
+
Performs comprehensive system and Node.js runtime diagnostic checks:
|
|
102
|
+
- OS check (Linux)
|
|
103
|
+
- `systemctl` executable availability
|
|
104
|
+
- PID 1 systemd verification
|
|
105
|
+
- Systemd user bus accessibility
|
|
106
|
+
- Node.js executable PATH resolution (`which node`, `command -v`, `process.execPath`)
|
|
107
|
+
- Node.js binary execution capability (`node -v`)
|
|
108
|
+
- Unit directory write permissions (`~/.config/systemd/user/`)
|
|
109
|
+
- User lingering state (`loginctl enable-linger`)
|
|
110
|
+
|
|
111
|
+
Example output:
|
|
112
|
+
```text
|
|
113
|
+
unitup doctor
|
|
114
|
+
|
|
115
|
+
â Linux detected
|
|
116
|
+
â systemctl available
|
|
117
|
+
â systemd is running
|
|
118
|
+
â systemd user services available
|
|
119
|
+
â Node.js: /usr/bin/node
|
|
120
|
+
â Node version: v20.11.0
|
|
121
|
+
â PATH includes node
|
|
122
|
+
! User lingering is disabled
|
|
123
|
+
|
|
124
|
+
User lingering is disabled.
|
|
125
|
+
The service may stop after logout.
|
|
126
|
+
|
|
127
|
+
Enable it manually:
|
|
128
|
+
loginctl enable-linger username
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### Diagnosing Node.js Runtime & Path Problems
|
|
134
|
+
|
|
135
|
+
`unitup` automatically detects and diagnoses Node.js installation issues before creating services.
|
|
136
|
+
|
|
137
|
+
#### 1. Node.js not found in PATH
|
|
138
|
+
```text
|
|
139
|
+
â Node.js not found in PATH
|
|
140
|
+
|
|
141
|
+
Install Node.js:
|
|
142
|
+
https://nodejs.org
|
|
143
|
+
or via package manager:
|
|
144
|
+
sudo apt install nodejs
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### 2. Node.js binary exists but is not executable
|
|
148
|
+
```text
|
|
149
|
+
â Node.js binary exists but is not executable
|
|
150
|
+
|
|
151
|
+
Check permissions:
|
|
152
|
+
chmod +x $(which node)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### 3. Resolving custom or nvm Node.js executable paths
|
|
156
|
+
Systemd services execute in a minimal environment. To ensure systemd finds `node` regardless of user PATH setup (nvm, fnm, etc.), `unitup` resolves absolute paths and automatically injects a `PATH` environment directive into every generated unit file:
|
|
157
|
+
|
|
158
|
+
```ini
|
|
159
|
+
[Service]
|
|
160
|
+
Type=simple
|
|
161
|
+
WorkingDirectory=/home/user/projects/app
|
|
162
|
+
Environment=PATH="/home/user/.nvm/versions/node/v20.11.0/bin:/usr/local/bin:/usr/bin:/bin"
|
|
163
|
+
ExecStart=/home/user/.nvm/versions/node/v20.11.0/bin/node /home/user/projects/app/server.js
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
You can also specify an explicit Node.js binary path when creating a service:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
unitup add app.js --node /usr/bin/node
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If Node.js cannot be resolved, `unitup add` will safely abort and request that you run `unitup doctor`.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### `unitup add <script>`
|
|
177
|
+
|
|
178
|
+
Creates a new systemd user service unit file at `~/.config/systemd/user/unitup-<name>.service`.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
unitup add app.js
|
|
182
|
+
unitup add server.js --name api --env NODE_ENV=production --env PORT=3000 --start
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Options:**
|
|
186
|
+
- `--name <name>`: Custom service name (defaults to script file name without extension).
|
|
187
|
+
- `--node <path>`: Explicit Node.js executable path.
|
|
188
|
+
- `--cwd <path>`: Working directory (defaults to script directory).
|
|
189
|
+
- `--env KEY=value`: Pass environment variable (can be specified multiple times).
|
|
190
|
+
- `--env-file <file>`: Absolute or relative path to environment file (adds `EnvironmentFile=...`).
|
|
191
|
+
- `--restart <policy>`: Systemd restart policy (`on-failure`, `always`, `no`, `on-abnormal`, etc. Default: `on-failure`).
|
|
192
|
+
- `--arg <value>`: Script positional arguments (can be specified multiple times).
|
|
193
|
+
- `--start`: Automatically enables and starts the service immediately (`systemctl --user enable --now`).
|
|
194
|
+
|
|
195
|
+
Generated unit file example (`~/.config/systemd/user/unitup-api.service`):
|
|
196
|
+
|
|
197
|
+
```ini
|
|
198
|
+
[Unit]
|
|
199
|
+
Description=unitup service: api
|
|
200
|
+
After=network.target
|
|
201
|
+
|
|
202
|
+
[Service]
|
|
203
|
+
Type=simple
|
|
204
|
+
WorkingDirectory=/home/user/projects/app
|
|
205
|
+
ExecStart=/usr/bin/node /home/user/projects/app/server.js
|
|
206
|
+
Restart=on-failure
|
|
207
|
+
RestartSec=3
|
|
208
|
+
EnvironmentFile=/home/user/projects/app/.env
|
|
209
|
+
Environment=PATH="/usr/bin:/usr/local/bin:/bin"
|
|
210
|
+
Environment=NODE_ENV="production"
|
|
211
|
+
Environment=PORT="3000"
|
|
212
|
+
|
|
213
|
+
[Install]
|
|
214
|
+
WantedBy=default.target
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### `unitup start <name>`
|
|
220
|
+
|
|
221
|
+
Starts an existing service unit.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
unitup start api
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Pass `--enable` to enable the service to start automatically on system boot:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
unitup start api --enable
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### `unitup stop <name>`
|
|
236
|
+
|
|
237
|
+
Stops a running service:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
unitup stop api
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### `unitup restart <name>`
|
|
246
|
+
|
|
247
|
+
Restarts a service:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
unitup restart api
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### `unitup status <name>`
|
|
256
|
+
|
|
257
|
+
Shows a compact status summary for the service:
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
api
|
|
261
|
+
|
|
262
|
+
Status: running
|
|
263
|
+
PID: 14220
|
|
264
|
+
Started: 12 minutes ago
|
|
265
|
+
Restarts: 0
|
|
266
|
+
Script: /home/user/projects/app/server.js
|
|
267
|
+
Working directory: /home/user/projects/app
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
To view systemctl's raw output, pass `--raw`:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
unitup status api --raw
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
### `unitup logs <name>`
|
|
279
|
+
|
|
280
|
+
Displays logs collected by systemd's `journalctl`.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# View last 100 log lines (default)
|
|
284
|
+
unitup logs api
|
|
285
|
+
|
|
286
|
+
# Specify number of lines
|
|
287
|
+
unitup logs api --lines 30
|
|
288
|
+
|
|
289
|
+
# Live follow/stream mode
|
|
290
|
+
unitup logs api --follow
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### `unitup list`
|
|
296
|
+
|
|
297
|
+
Lists all user services managed by `unitup` (`unitup-*.service`):
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
NAME STATUS ENABLED
|
|
301
|
+
api running yes
|
|
302
|
+
worker stopped yes
|
|
303
|
+
cleanup failed no
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### `unitup remove <name>`
|
|
309
|
+
|
|
310
|
+
Stops, disables, and deletes the unit file, then reloads systemd:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
unitup remove api
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Equivalent systemctl commands executed:
|
|
317
|
+
1. `systemctl --user disable --now unitup-api.service`
|
|
318
|
+
2. Remove `~/.config/systemd/user/unitup-api.service`
|
|
319
|
+
3. `systemctl --user daemon-reload`
|
|
320
|
+
4. `systemctl --user reset-failed`
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## User Lingering & Persistence Across Logout
|
|
325
|
+
|
|
326
|
+
By default, Linux systemd terminates user processes when a user logs out. To allow your Node.js services to run continuously in the background after logout or system startup, enable **user lingering**:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
loginctl enable-linger $USER
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`unitup` checks this state in `unitup doctor` and warns if lingering is disabled. `unitup` will never attempt to run `loginctl` or `sudo` automatically on your behalf.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Programmatic JavaScript & TypeScript API
|
|
337
|
+
|
|
338
|
+
`unitup` includes full TypeScript declaration files (`index.d.ts`). You can import `unitup` directly into JavaScript or TypeScript applications:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import {
|
|
342
|
+
createService,
|
|
343
|
+
startService,
|
|
344
|
+
stopService,
|
|
345
|
+
restartService,
|
|
346
|
+
removeService,
|
|
347
|
+
getServiceStatus,
|
|
348
|
+
listServices,
|
|
349
|
+
isSystemdAvailable,
|
|
350
|
+
CreateServiceOptions,
|
|
351
|
+
ServiceStatus
|
|
352
|
+
} from "unitup";
|
|
353
|
+
|
|
354
|
+
// Check if systemd user services are available
|
|
355
|
+
if (await isSystemdAvailable()) {
|
|
356
|
+
const options: CreateServiceOptions = {
|
|
357
|
+
name: "api",
|
|
358
|
+
script: "./server.js",
|
|
359
|
+
cwd: process.cwd(),
|
|
360
|
+
env: {
|
|
361
|
+
NODE_ENV: "production",
|
|
362
|
+
PORT: "3000"
|
|
363
|
+
},
|
|
364
|
+
restart: "on-failure"
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Add a service
|
|
368
|
+
await createService(options);
|
|
369
|
+
|
|
370
|
+
// Start the service
|
|
371
|
+
await startService("api");
|
|
372
|
+
|
|
373
|
+
// Get compact status
|
|
374
|
+
const status: ServiceStatus = await getServiceStatus("api");
|
|
375
|
+
console.log(`Service PID: ${status.pid}, Status: ${status.status}`);
|
|
376
|
+
|
|
377
|
+
// List all unitup services
|
|
378
|
+
const services = await listServices();
|
|
379
|
+
console.log(services);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## License
|
|
386
|
+
|
|
387
|
+
[MIT](LICENSE)
|
package/bin/unitup.js
ADDED