unitup 0.0.11 → 0.1.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 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,12 +25,16 @@
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
- pre { tab-size: 2; margin: 0; }
30
- pre code { padding: 1rem; background: #0f172a; border-radius: 0.75rem; font-size: 0.875rem; }
31
- code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
31
+ pre { tab-size: 2; margin: 0; overflow-x: auto; max-width: 100%; }
32
+ pre code { padding: 1rem; background: #0f172a; border-radius: 0.75rem; font-size: 0.85rem; display: block; overflow-x: auto; white-space: pre; }
33
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; word-break: break-word; }
32
34
  .doc-section { scroll-margin-top: 2rem; }
33
- table { border-collapse: collapse; width: 100%; }
35
+ .table-container { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: 0.75rem; border: 1px solid #e2e8f0; margin-bottom: 1rem; }
36
+ .dark .table-container { border-color: #334155; }
37
+ table { border-collapse: collapse; width: 100%; min-width: 580px; }
34
38
  th, td { border: 1px solid #e2e8f0; padding: 0.75rem 1rem; text-align: left; }
35
39
  th { background: #f8fafc; font-weight: 600; }
36
40
  .dark th, .dark td { border-color: #334155; }
@@ -38,31 +42,39 @@
38
42
  </style>
39
43
  </head>
40
44
  <body class="bg-slate-50 text-slate-900 antialiased dark:bg-slate-950 dark:text-slate-100 transition-colors">
41
- <div class="max-w-6xl mx-auto px-6 py-10">
45
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 py-6 sm:py-10">
42
46
  <!-- Top Navigation Bar -->
43
- <div class="flex items-center justify-between mb-10 gap-4 flex-wrap pb-6 border-b border-slate-200 dark:border-slate-800">
44
- <div class="flex items-center gap-3">
47
+ <div class="flex items-center justify-between mb-8 sm:mb-10 gap-4 flex-wrap pb-6 border-b border-slate-200 dark:border-slate-800">
48
+ <div class="flex items-center gap-2.5 sm:gap-3">
45
49
  <span class="text-2xl font-black tracking-tight text-cyan-600 dark:text-cyan-400">unitup</span>
46
- <span class="text-xs px-2.5 py-0.5 rounded-full bg-cyan-100 text-cyan-800 dark:bg-cyan-950 dark:text-cyan-300 font-semibold border border-cyan-300 dark:border-cyan-800">v0.0.10</span>
47
- <a href="https://github.com/litepacks/unitup" target="_blank" rel="noopener noreferrer" class="text-slate-500 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 transition-colors" aria-label="GitHub Repository">
50
+ <span class="text-xs px-2.5 py-0.5 rounded-full bg-cyan-100 text-cyan-800 dark:bg-cyan-950 dark:text-cyan-300 font-semibold border border-cyan-300 dark:border-cyan-800">v0.0.11</span>
51
+ <a href="https://github.com/litepacks/unitup" target="_blank" rel="noopener noreferrer" class="text-slate-500 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 transition-colors p-1" aria-label="GitHub Repository">
48
52
  <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
49
53
  </a>
50
54
  </div>
51
- <nav class="flex flex-wrap gap-x-4 gap-y-2 text-sm font-medium" aria-label="Page sections">
52
- <a href="#overview" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">Overview</a>
53
- <a href="#comparison" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">vs PM2</a>
54
- <a href="#architecture" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">Architecture</a>
55
- <a href="#runtimes" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">Runtimes</a>
56
- <a href="#cli" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">CLI Reference</a>
57
- <a href="#api" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">API</a>
58
- <a href="#declarative" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">Roadmap</a>
59
- <a href="#troubleshooting" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400">Lingering</a>
60
- <a href="https://github.com/litepacks/unitup" target="_blank" rel="noopener noreferrer" class="text-cyan-600 dark:text-cyan-400 hover:underline flex items-center gap-1 font-semibold">GitHub &rarr;</a>
55
+
56
+ <div class="flex items-center gap-2">
57
+ <button id="mobile-menu-btn" type="button" class="md:hidden p-2 rounded-lg border border-slate-200 dark:border-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-800 dark:text-slate-400" aria-label="Toggle navigation menu">
58
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
59
+ </button>
60
+ <button id="theme-toggle" type="button" class="p-2 rounded-lg border border-slate-200 dark:border-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-800 dark:text-slate-400 shrink-0" aria-label="Toggle dark mode">
61
+ <svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
62
+ <svg class="w-5 h-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
63
+ </button>
64
+ </div>
65
+
66
+ <nav id="nav-menu" class="hidden md:flex w-full md:w-auto flex-col md:flex-row flex-wrap gap-x-4 gap-y-2 text-sm font-medium pt-3 md:pt-0 border-t md:border-t-0 border-slate-200 dark:border-slate-800" aria-label="Page sections">
67
+ <a href="#overview" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">Overview</a>
68
+ <a href="#comparison" class="text-slate-600 hover:text-cyan-600 dark:text-slate-400 dark:hover:text-cyan-400 py-1">vs PM2</a>
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>
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>
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>
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>
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>
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>
76
+ <a href="https://github.com/litepacks/unitup" target="_blank" rel="noopener noreferrer" class="text-cyan-600 dark:text-cyan-400 hover:underline flex items-center gap-1 font-semibold py-1">GitHub &rarr;</a>
61
77
  </nav>
62
- <button id="theme-toggle" type="button" class="p-2 rounded-lg border border-slate-200 dark:border-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-800 dark:text-slate-400 shrink-0" aria-label="Toggle dark mode">
63
- <svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
64
- <svg class="w-5 h-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
65
- </button>
66
78
  </div>
67
79
 
68
80
  <!-- Header / Hero Section -->
@@ -146,7 +158,7 @@ unitup add ./server --runtime native --name api --start</code></pre>
146
158
  <strong class="text-slate-800 dark:text-slate-200">unitup</strong> is not a PM2 replacement or an independent process manager. While PM2 runs its own master daemon, unitup is strictly a thin, transparent CLI layer on top of Linux OS <code class="text-cyan-600 dark:text-cyan-400">systemd</code>.
147
159
  </p>
148
160
 
149
- <div class="overflow-x-auto">
161
+ <div class="table-container">
150
162
  <table>
151
163
  <thead>
152
164
  <tr>
@@ -203,8 +215,8 @@ unitup add ./server --runtime native --name api --start</code></pre>
203
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:
204
216
  </p>
205
217
 
206
- <div class="grid md:grid-cols-2 gap-6">
207
- <div>
218
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
219
+ <div class="min-w-0">
208
220
  <h3 class="text-sm font-semibold uppercase tracking-wider text-cyan-600 dark:text-cyan-400 mb-2">JSON Execution Model</h3>
209
221
  <pre><code class="language-json">{
210
222
  "command": "/usr/bin/python3",
@@ -215,8 +227,8 @@ unitup add ./server --runtime native --name api --start</code></pre>
215
227
  }
216
228
  }</code></pre>
217
229
  </div>
218
- <div>
219
- <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>
220
232
  <pre><code class="language-ini">[Unit]
221
233
  Description=unitup service: worker
222
234
  After=network.target
@@ -241,7 +253,7 @@ WantedBy=default.target</code></pre>
241
253
  <section id="runtimes" class="doc-section mb-14">
242
254
  <h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-4">Supported Runtimes & Auto-Detection</h2>
243
255
 
244
- <div class="overflow-x-auto mb-6">
256
+ <div class="table-container">
245
257
  <table>
246
258
  <thead>
247
259
  <tr>
@@ -372,6 +384,15 @@ unitup stop @backend</code></pre>
372
384
  unitup limits api --reset-memory</code></pre>
373
385
  </div>
374
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
+
375
396
  <!-- status & logs -->
376
397
  <div class="p-5 rounded-xl border border-slate-200 dark:border-slate-800 bg-white/70 dark:bg-slate-900/50">
377
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>
@@ -400,6 +421,58 @@ unitup remove @backend --force</code></pre>
400
421
  </div>
401
422
  </section>
402
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
+
403
476
  <!-- Section: API -->
404
477
  <section id="api" class="doc-section mb-14">
405
478
  <h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-4">Programmatic API</h2>
@@ -535,6 +608,22 @@ unitup restart @default</code></pre>
535
608
  document.documentElement.classList.toggle('dark');
536
609
  });
537
610
 
611
+ // Mobile navigation toggle
612
+ const mobileMenuBtn = document.getElementById('mobile-menu-btn');
613
+ const navMenu = document.getElementById('nav-menu');
614
+ if (mobileMenuBtn && navMenu) {
615
+ mobileMenuBtn.addEventListener('click', () => {
616
+ navMenu.classList.toggle('hidden');
617
+ });
618
+ navMenu.querySelectorAll('a').forEach(link => {
619
+ link.addEventListener('click', () => {
620
+ if (window.innerWidth < 768) {
621
+ navMenu.classList.add('hidden');
622
+ }
623
+ });
624
+ });
625
+ }
626
+
538
627
  // Highlight code blocks
539
628
  document.addEventListener('DOMContentLoaded', () => {
540
629
  hljs.highlightAll();
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.11",
3
+ "version": "0.1.0",
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",
@@ -36,6 +36,14 @@
36
36
  ],
37
37
  "author": "",
38
38
  "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/litepacks/unitup.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/litepacks/unitup/issues"
45
+ },
46
+ "homepage": "https://github.com/litepacks/unitup#readme",
39
47
  "engines": {
40
48
  "node": ">=20.0.0"
41
49
  }