unitup 0.0.12 → 0.1.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/README.md CHANGED
@@ -85,7 +85,7 @@ At its core, `unitup` generates systemd service unit files directly from a gener
85
85
  - 🧠 **Systemd-Native Memory Limits** — Configure `MemoryHigh`, `MemoryMax`, and `MemorySwapMax` per service without extra monitoring daemons.
86
86
  - 📜 **Journald Log Maintenance** — Advanced log streaming with filters (`--since`, `--until`, `--priority`, `--grep`, `--boot`, `--json`) and journal maintenance (`disk-usage`, `rotate`, `vacuum`).
87
87
  - 🩺 **System readiness & runtime check** — `unitup doctor` verifies OS, systemd PID 1, systemctl user bus, cgroup v2 memory controller support, user lingering, and detected runtimes.
88
- - 📋 **Compact status & logs** — clean summary output from `systemctl show` and live `journalctl` streaming.
88
+ - ⏰ **Systemd Timer-Based Scheduling** — Create native `.timer` systemd units (`unitup schedule`) with interval (`--every 30m`), calendar (`--calendar daily`), boot delay (`--on-boot`), random delay jitter (`--random-delay`), and missed execution persistence (`--persistent`) without background cron daemons or continuously running worker processes.
89
89
  - 📦 **Programmatic API** — imported directly into JavaScript and TypeScript applications.
90
90
 
91
91
  ---
@@ -112,6 +112,32 @@ unitup limits api --reset-memory
112
112
 
113
113
  *Supported size units:* `128K`, `256M`, `1G`, `infinity`, `max`, or raw byte integers.
114
114
 
115
+ ### Memory Usage Overview (`unitup memory` / `unitup top`)
116
+
117
+ ```bash
118
+ # View memory usage overview for all running services & schedules
119
+ unitup top
120
+
121
+ # Alias: unitup memory or unitup mem
122
+ unitup memory
123
+
124
+ # Filter memory overview by group
125
+ unitup top --group backend
126
+
127
+ # View detailed memory breakdown for a specific app
128
+ unitup memory api
129
+ ```
130
+
131
+ Example Output:
132
+ ```text
133
+ NAME GROUP TYPE STATUS PID MEMORY PEAK LIMIT (MAX)
134
+ api backend service running 14220 284 MB 361 MB 512 MB
135
+ worker backend service running 14302 120 MB 150 MB 1 GB
136
+ cleanup backend timer waiting - 0 MB unavailable 1 GB
137
+
138
+ Total Memory Usage across 2 active app(s): 404 MB
139
+ ```
140
+
115
141
  ### Journald Log Maintenance
116
142
 
117
143
  ```bash
@@ -131,6 +157,52 @@ unitup journal vacuum --files 10 --yes
131
157
  > [!NOTE]
132
158
  > `unitup journal vacuum` prompts for user confirmation before executing system-wide log cleanup unless `--yes` / `-y` or `--dry-run` is provided. If permissions are insufficient, `unitup` handles it gracefully without executing `sudo`.
133
159
 
160
+ ### Systemd Timer Schedules (`unitup schedule`)
161
+
162
+ `unitup` allows you to schedule tasks completely via native systemd `.timer` units without running background daemons, cron services, or continuous worker loops.
163
+
164
+ Each schedule creates a paired service (`~/.config/systemd/user/unitup-<name>.service`) with `Type=oneshot` and timer (`~/.config/systemd/user/unitup-<name>.timer`).
165
+
166
+ ```bash
167
+ # Interval schedule (--every)
168
+ unitup schedule cleanup.js --every 30m --persistent --start
169
+
170
+ # Calendar schedule (--calendar)
171
+ unitup schedule backup.py --calendar daily --random-delay 10m --persistent --start
172
+
173
+ # Complex calendar expression
174
+ unitup schedule report.js --calendar "Mon..Fri 09:00" --start
175
+
176
+ # List all active schedule timers
177
+ unitup schedules # or unitup timers
178
+
179
+ # Detailed schedule status
180
+ unitup schedule-status cleanup # or unitup timer-status cleanup
181
+
182
+ # Manually run the schedule service once (leaves timer schedule unchanged)
183
+ unitup schedule-run cleanup
184
+
185
+ # Enable or disable schedule timers
186
+ unitup schedule-enable cleanup
187
+ unitup schedule-disable cleanup
188
+
189
+ # Remove schedule timer, service unit, and metadata
190
+ unitup schedule-remove cleanup [--force]
191
+
192
+ # Read scheduled task logs
193
+ unitup schedule-logs cleanup
194
+ ```
195
+
196
+ #### Schedule Options
197
+
198
+ - `--every <duration>`: Execution interval (e.g., `30s`, `10m`, `2h`, `1d`). Generates `OnActiveSec` and `OnUnitActiveSec`.
199
+ - `--calendar <expression>`: Systemd calendar expression (e.g., `daily`, `weekly`, `"Mon..Fri 09:00"`). Validated via `systemd-analyze calendar`.
200
+ - `--on-boot <duration>`: Delay execution relative to system boot (e.g., `5m`).
201
+ - `--on-active <duration>`: Delay execution relative to timer activation.
202
+ - `--random-delay <duration>`: Adds a randomized delay jitter to prevent simultaneous job stampedes (e.g., `10m`).
203
+ - `--persistent`: Sets `Persistent=true` so systemd executes missed schedules immediately after system boot or wake.
204
+ - `--start`: Automatically enables and starts the timer unit after creation (`systemctl --user enable --now`).
205
+
134
206
  ---
135
207
 
136
208
  ## Requirements
@@ -525,15 +597,62 @@ await createService({
525
597
  ```js
526
598
  const {
527
599
  createService,
528
- startService,
529
- getServiceStatus
600
+ createSchedule,
601
+ listSchedules
530
602
  } = require("unitup");
531
603
 
532
- await createService({
533
- name: "api",
534
- script: "./server.js",
604
+ await createSchedule({
605
+ name: "cleanup",
606
+ script: "./cleanup.js",
607
+ every: "30m",
608
+ start: true
609
+ });
610
+ ```
611
+
612
+ ### Programmatic Schedule API Example
613
+
614
+ ```ts
615
+ import {
616
+ createSchedule,
617
+ listSchedules,
618
+ getScheduleStatus,
619
+ runSchedule,
620
+ enableSchedule,
621
+ disableSchedule,
622
+ removeSchedule
623
+ } from "unitup";
624
+
625
+ // Create an interval schedule
626
+ await createSchedule({
627
+ name: "cleanup",
628
+ script: "./cleanup.js",
629
+ runtime: "node",
630
+ every: "30m",
631
+ persistent: true,
535
632
  start: true
536
633
  });
634
+
635
+ // Create a calendar schedule with random delay jitter
636
+ await createSchedule({
637
+ name: "backup",
638
+ script: "./backup.py",
639
+ runtime: "python",
640
+ calendar: "daily",
641
+ randomDelay: "10m",
642
+ persistent: true,
643
+ start: true
644
+ });
645
+
646
+ // List all active schedules
647
+ const schedules = await listSchedules();
648
+ console.log(schedules);
649
+
650
+ // Manually trigger a schedule run
651
+ await runSchedule("cleanup");
652
+
653
+ // Disable and remove schedule
654
+ await disableSchedule("cleanup");
655
+ await removeSchedule("cleanup");
537
656
  ```
538
657
 
539
658
  ### Script & Runtime Example
package/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Use this section to tell people about which versions of your project are
6
+ currently being supported with security updates.
7
+
8
+ | Version | Supported |
9
+ | ------- | ------------------ |
10
+ | 5.1.x | :white_check_mark: |
11
+ | 5.0.x | :x: |
12
+ | 4.0.x | :white_check_mark: |
13
+ | < 4.0 | :x: |
14
+
15
+ ## Reporting a Vulnerability
16
+
17
+ Use this section to tell people how to report a vulnerability.
18
+
19
+ Tell them where to go, how often they can expect to get an update on a
20
+ reported vulnerability, what to expect if the vulnerability is accepted or
21
+ declined, etc.
package/docs/index.html CHANGED
@@ -25,6 +25,8 @@
25
25
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/yaml.min.js"></script>
26
26
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ini.min.js"></script>
27
27
  <style>
28
+ body { overflow-x: hidden; }
29
+ .grid > * { min-width: 0; }
28
30
  html { scroll-behavior: smooth; }
29
31
  pre { tab-size: 2; margin: 0; overflow-x: auto; max-width: 100%; }
30
32
  pre code { padding: 1rem; background: #0f172a; border-radius: 0.75rem; font-size: 0.85rem; display: block; overflow-x: auto; white-space: pre; }
@@ -67,6 +69,7 @@
67
69
  <a href="#architecture" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">Architecture</a>
68
70
  <a href="#runtimes" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">Runtimes</a>
69
71
  <a href="#cli" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">CLI Reference</a>
72
+ <a href="#schedules" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">Schedules</a>
70
73
  <a href="#api" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">API</a>
71
74
  <a href="#declarative" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">Roadmap</a>
72
75
  <a href="#troubleshooting" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">Lingering</a>
@@ -212,8 +215,8 @@ unitup add ./server --runtime native --name api --start</code></pre>
212
215
  At its core, unitup generates systemd service unit files directly from a generic <code class="text-cyan-600 dark:text-cyan-400">command + args</code> model rather than being hardcoded to Node.js:
213
216
  </p>
214
217
 
215
- <div class="grid md:grid-cols-2 gap-6">
216
- <div>
218
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
219
+ <div class="min-w-0">
217
220
  <h3 class="text-sm font-semibold uppercase tracking-wider text-cyan-600 dark:text-cyan-400 mb-2">JSON Execution Model</h3>
218
221
  <pre><code class="language-json">{
219
222
  "command": "/usr/bin/python3",
@@ -224,8 +227,8 @@ unitup add ./server --runtime native --name api --start</code></pre>
224
227
  }
225
228
  }</code></pre>
226
229
  </div>
227
- <div>
228
- <h3 class="text-sm font-semibold uppercase tracking-wider text-cyan-600 dark:text-cyan-400 mb-2">Generated Unit File (<code class="text-xs">unitup-worker.service</code>)</h3>
230
+ <div class="min-w-0">
231
+ <h3 class="text-sm font-semibold uppercase tracking-wider text-cyan-600 dark:text-cyan-400 mb-2 flex flex-wrap items-center gap-1">Generated Unit File <span class="lowercase font-mono text-xs normal-case opacity-90 break-all">(unitup-worker.service)</span></h3>
229
232
  <pre><code class="language-ini">[Unit]
230
233
  Description=unitup service: worker
231
234
  After=network.target
@@ -381,6 +384,15 @@ unitup stop @backend</code></pre>
381
384
  unitup limits api --reset-memory</code></pre>
382
385
  </div>
383
386
 
387
+ <!-- memory / top -->
388
+ <div class="p-5 rounded-xl border border-slate-200 dark:border-slate-800 bg-white/70 dark:bg-slate-900/50">
389
+ <h3 class="text-lg font-bold text-cyan-600 dark:text-cyan-400 mb-2"><code class="text-base">unitup memory</code> / <code class="text-base">unitup top</code></h3>
390
+ <p class="text-sm text-slate-600 dark:text-slate-400 mb-3">Displays memory usage overview across all running apps and schedules, with aggregated total memory consumption summary.</p>
391
+ <pre><code class="language-bash">unitup top
392
+ unitup memory api
393
+ unitup mem --group backend</code></pre>
394
+ </div>
395
+
384
396
  <!-- status & logs -->
385
397
  <div class="p-5 rounded-xl border border-slate-200 dark:border-slate-800 bg-white/70 dark:bg-slate-900/50">
386
398
  <h3 class="text-lg font-bold text-cyan-600 dark:text-cyan-400 mb-2"><code class="text-base">unitup status / logs &lt;name&gt;</code></h3>
@@ -409,6 +421,58 @@ unitup remove @backend --force</code></pre>
409
421
  </div>
410
422
  </section>
411
423
 
424
+ <!-- Section: Schedules -->
425
+ <section id="schedules" class="doc-section mb-14">
426
+ <h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-4">Systemd Timer Schedules</h2>
427
+ <p class="text-slate-600 dark:text-slate-400 mb-6 leading-relaxed">
428
+ <strong class="text-slate-800 dark:text-slate-200">unitup</strong> supports native systemd timer scheduling without running continuous worker processes or cron daemons. Each schedule generates a paired oneshot service (<code class="text-cyan-700 dark:text-cyan-400">~/.config/systemd/user/unitup-&lt;name&gt;.service</code>) and timer unit (<code class="text-cyan-700 dark:text-cyan-400">~/.config/systemd/user/unitup-&lt;name&gt;.timer</code>).
429
+ </p>
430
+
431
+ <div class="space-y-6">
432
+ <div class="p-5 rounded-xl border border-slate-200 dark:border-slate-800 bg-white/70 dark:bg-slate-900/50">
433
+ <h3 class="text-lg font-bold text-cyan-600 dark:text-cyan-400 mb-2">CLI Schedule Commands</h3>
434
+ <pre><code class="language-bash"># Create interval schedule (--every 30m)
435
+ unitup schedule cleanup.js --every 30m --persistent --start
436
+
437
+ # Create calendar schedule (--calendar daily with random delay jitter)
438
+ unitup schedule backup.py --calendar daily --random-delay 10m --persistent --start
439
+
440
+ # Specific calendar expression
441
+ unitup schedule report.js --calendar "Mon..Fri 09:00" --start
442
+
443
+ # List all schedule timers
444
+ unitup schedules # or unitup timers
445
+
446
+ # Detailed schedule status
447
+ unitup schedule-status cleanup
448
+
449
+ # Manually trigger schedule service unit once
450
+ unitup schedule-run cleanup
451
+
452
+ # Enable / Disable / Remove schedule
453
+ unitup schedule-enable cleanup
454
+ unitup schedule-disable cleanup
455
+ unitup schedule-remove cleanup [--force]
456
+
457
+ # Read schedule logs
458
+ unitup schedule-logs cleanup</code></pre>
459
+ </div>
460
+
461
+ <div class="p-5 rounded-xl border border-slate-200 dark:border-slate-800 bg-white/70 dark:bg-slate-900/50">
462
+ <h3 class="text-lg font-bold text-cyan-600 dark:text-cyan-400 mb-2">Schedule Options</h3>
463
+ <ul class="list-disc list-inside space-y-1.5 text-sm text-slate-600 dark:text-slate-400">
464
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--every &lt;duration&gt;</code>: Execution interval (e.g., <code class="text-xs">30s</code>, <code class="text-xs">10m</code>, <code class="text-xs">2h</code>, <code class="text-xs">1d</code>). Sets <code class="text-xs">OnActiveSec</code> and <code class="text-xs">OnUnitActiveSec</code>.</li>
465
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--calendar &lt;expression&gt;</code>: Systemd calendar expression (e.g., <code class="text-xs">daily</code>, <code class="text-xs">"Mon..Fri 09:00"</code>). Validated via <code class="text-xs">systemd-analyze calendar</code>.</li>
466
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--on-boot &lt;duration&gt;</code>: Delay execution relative to system boot (e.g., <code class="text-xs">5m</code>).</li>
467
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--on-active &lt;duration&gt;</code>: Delay execution relative to timer activation.</li>
468
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--random-delay &lt;duration&gt;</code>: Adds randomized delay jitter (<code class="text-xs">RandomizedDelaySec</code>).</li>
469
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--persistent</code>: Sets <code class="text-xs">Persistent=true</code> to execute missed schedules immediately after system wake/boot.</li>
470
+ <li><code class="text-xs font-bold text-slate-800 dark:text-slate-200">--start</code>: Enables and starts the timer unit immediately after creation.</li>
471
+ </ul>
472
+ </div>
473
+ </div>
474
+ </section>
475
+
412
476
  <!-- Section: API -->
413
477
  <section id="api" class="doc-section mb-14">
414
478
  <h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-4">Programmatic API</h2>
package/index.d.ts CHANGED
@@ -97,6 +97,11 @@ export interface CreateServiceOptions {
97
97
  * Swap memory limit directive (e.g. '256M').
98
98
  */
99
99
  memorySwapMax?: string;
100
+
101
+ /**
102
+ * Default memory limit fallback or request marker (defaults to '1G').
103
+ */
104
+ defaultMemory?: string | boolean;
100
105
  }
101
106
 
102
107
  /**
@@ -420,6 +425,36 @@ export function inspectService(name: string): Promise<InspectInfo>;
420
425
  export function getServiceFailures(): Promise<FailureItem[]>;
421
426
  export function getServicesByGroup(groupName: string): Promise<string[]>;
422
427
  export function setServiceLimits(name: string, options?: ServiceLimitsOptions): Promise<InspectInfo>;
428
+ export function getServiceMemoryUsage(name: string): Promise<{
429
+ name: string;
430
+ group: string;
431
+ type: string;
432
+ status: string;
433
+ pid: string;
434
+ memoryBytes: number;
435
+ memory: string;
436
+ memoryPeak: string;
437
+ memoryHigh: string;
438
+ memoryMax: string;
439
+ memorySwapMax: string;
440
+ }>;
441
+ export function getAllServicesMemoryUsage(options?: { group?: string }): Promise<{
442
+ items: Array<{
443
+ name: string;
444
+ group: string;
445
+ type: string;
446
+ status: string;
447
+ pid: string;
448
+ memory: string;
449
+ memoryPeak: string;
450
+ memoryMax: string;
451
+ memoryHigh: string;
452
+ memoryBytes: number;
453
+ }>;
454
+ totalBytes: number;
455
+ totalMemory: string;
456
+ runningCount: number;
457
+ }>;
423
458
  export function executeJournalctlMaintenance(action: string, options?: Record<string, unknown>): Promise<string>;
424
459
  export function getServiceLogs(name: string, options?: LogOptions): Promise<string | ChildProcess>;
425
460
 
@@ -463,3 +498,105 @@ export function getUnitupDir(): string;
463
498
  // Testing Mock Helpers
464
499
  export function setCommandRunner(runner: CommandRunnerFn): void;
465
500
  export function resetCommandRunner(): void;
501
+
502
+ // ---------------------------------------------------------------------------
503
+ // Schedule Management API
504
+ // ---------------------------------------------------------------------------
505
+
506
+ export interface CreateScheduleOptions {
507
+ name?: string;
508
+ script?: string;
509
+ command?: string;
510
+ runtime?: string;
511
+ args?: string[];
512
+ runtimeArgs?: string[];
513
+ cwd?: string;
514
+ env?: Record<string, string> | string[];
515
+ envFile?: string;
516
+ group?: string;
517
+ memoryHigh?: string;
518
+ memoryMax?: string;
519
+ memorySwapMax?: string;
520
+ defaultMemory?: string | boolean;
521
+ every?: string;
522
+ calendar?: string;
523
+ onBoot?: string;
524
+ onActive?: string;
525
+ randomDelay?: string;
526
+ persistent?: boolean;
527
+ start?: boolean;
528
+ enable?: boolean;
529
+ }
530
+
531
+ export interface ScheduleMetadata {
532
+ name: string;
533
+ group: string;
534
+ type: string;
535
+ runtime: string;
536
+ command: string;
537
+ args: string[];
538
+ cwd: string;
539
+ schedule: {
540
+ every: string | null;
541
+ calendar: string | null;
542
+ onBoot: string | null;
543
+ onActive: string | null;
544
+ persistent: boolean;
545
+ randomDelay: string | null;
546
+ };
547
+ serviceUnit: string;
548
+ timerUnit: string;
549
+ }
550
+
551
+ export interface ScheduleStatus {
552
+ name: string;
553
+ group: string;
554
+ schedule: string;
555
+ nextRun: string;
556
+ lastRun: string;
557
+ status: string;
558
+ activeState: string;
559
+ subState: string;
560
+ unitFileState: string;
561
+ serviceActiveState: string;
562
+ metadata?: ScheduleMetadata | null;
563
+ serviceUnit: string;
564
+ timerUnit: string;
565
+ }
566
+
567
+ export interface CreateScheduleResult {
568
+ name: string;
569
+ group: string;
570
+ type: string;
571
+ runtime: string;
572
+ command: string;
573
+ args: string[];
574
+ cwd: string;
575
+ schedule: Record<string, unknown>;
576
+ serviceUnit: string;
577
+ timerUnit: string;
578
+ servicePath: string;
579
+ timerPath: string;
580
+ }
581
+
582
+ export function createSchedule(options: CreateScheduleOptions): Promise<CreateScheduleResult>;
583
+ export function listSchedules(group?: string): Promise<ScheduleStatus[]>;
584
+ export function getScheduleStatus(name: string): Promise<ScheduleStatus>;
585
+ export function runSchedule(name: string): Promise<boolean>;
586
+ export function enableSchedule(name: string): Promise<boolean>;
587
+ export function disableSchedule(name: string): Promise<boolean>;
588
+ export function removeSchedule(name: string, options?: { force?: boolean }): Promise<boolean>;
589
+ export function validateCalendar(expression: string): Promise<{ valid: boolean; error?: string; warning?: string }>;
590
+ export function validateDuration(val: string | number, paramName?: string): string;
591
+ export function getTimerFilename(name: string): string;
592
+ export function getTimerPath(name: string): string;
593
+ export function timerFileExists(name: string): boolean;
594
+ export function readScheduleMetadata(name: string): ScheduleMetadata | null;
595
+ export function getScheduleMetadataPath(name: string): string;
596
+ export function getSchedulesDir(): string;
597
+ export function readGlobalConfig(): Record<string, unknown>;
598
+ export function saveGlobalConfig(config: Record<string, unknown>): Record<string, unknown>;
599
+ export function getConfigFilepath(): string;
600
+ export function resolveEffectiveMemoryLimits(opts?: Record<string, unknown>): { memoryHigh?: string; memoryMax?: string; memorySwapMax?: string };
601
+
602
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unitup",
3
- "version": "0.0.12",
3
+ "version": "0.1.1",
4
4
  "description": "Minimal, zero-dependency systemd-native service manager for any runtime, script, or executable",
5
5
  "main": "src/index.js",
6
6
  "types": "./index.d.ts",