@mtakla/cronops 0.1.1-rc7 → 0.1.2
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
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# CronOps
|
|
2
2
|
|
|
3
|
-
[](https://github.com/mtakla/cronops/blob/
|
|
4
|
-
[](https://github.com/mtakla/cronops/blob/main/LICENSE)
|
|
4
|
+
[](https://img.shields.io/badge/coverage-95%25-green)
|
|
5
|
+
[](https://mtakla.github.io/cronops/)
|
|
5
6
|
[](https://www.buymeacoffee.com/nevereven)
|
|
6
7
|
|
|
7
8
|
**CronOps** is a lightweight, cron-based file management and system task scheduler for containerized environments. It automates copying, moving, archiving, and cleaning up files across mounted volumes — keeping your storage tidy, enabling seamless file exchange between containerized services, and triggering regular tasks in your development, integration or production environments.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
> This project is
|
|
10
|
+
⚠ **WARNING**
|
|
11
|
+
> This project is under active development. Production use is not yet recommended.
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## Why CronOps?
|
|
13
14
|
|
|
14
15
|
In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your **digital janitor**, running scheduled jobs that:
|
|
15
16
|
|
|
@@ -45,8 +46,11 @@ All configured via simple, version-controllable ***.yml** based **job definition
|
|
|
45
46
|
|
|
46
47
|
CronOps is built and optimized to run as a Docker container itself.
|
|
47
48
|
|
|
49
|
+
To download and start
|
|
50
|
+
|
|
48
51
|
```sh
|
|
49
|
-
docker run \
|
|
52
|
+
docker run -d \
|
|
53
|
+
--name cronops \
|
|
50
54
|
-p 8083:8083 \
|
|
51
55
|
-v ./config:/config \
|
|
52
56
|
-v ./data:/io/source \
|
|
@@ -56,7 +60,7 @@ docker run \
|
|
|
56
60
|
ghcr.io/mtakla/cronops:latest
|
|
57
61
|
```
|
|
58
62
|
|
|
59
|
-
To check if the server is running
|
|
63
|
+
To check if the server is running
|
|
60
64
|
|
|
61
65
|
```sh
|
|
62
66
|
docker logs -f cronops
|
|
@@ -83,8 +87,15 @@ target:
|
|
|
83
87
|
|
|
84
88
|
Now you can add more job configuration files to `./config/jobs`. For detailes, see [job configuration](#job-configuration) section below.
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
> You
|
|
90
|
+
🛈 **Note**
|
|
91
|
+
> You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.
|
|
92
|
+
|
|
93
|
+
To pull latest release of CronOps
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
docker pull ghcr.io/mtakla/cronops:latest
|
|
97
|
+
```
|
|
98
|
+
|
|
88
99
|
|
|
89
100
|
### Using Docker Compose
|
|
90
101
|
|
|
@@ -128,28 +139,37 @@ To enable **admin Web-API**, just set `CROPS_API_KEY` environment variable. Deta
|
|
|
128
139
|
|
|
129
140
|
This requires [Node.js](https://nodejs.org/) (>= v24) to be installed on your server.
|
|
130
141
|
|
|
131
|
-
|
|
142
|
+
To install & start CronOps, type:
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
npx @mtakla/cronops
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For configuration, create an empty folder with an `.env` file that contains your config settings (see [Configuration](#configuration) section below).
|
|
132
150
|
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
CROPS_TARGET_ROOT=./data
|
|
136
|
-
|
|
137
|
-
CROPS_LOG_DIR=./logs # default is ~/.cronops/logs
|
|
151
|
+
```dotenv
|
|
152
|
+
CROPS_CONFIG_DIR=./config
|
|
153
|
+
CROPS_TARGET_ROOT=./data
|
|
154
|
+
CROPS_SOURCE_ROOT=./data
|
|
138
155
|
```
|
|
139
156
|
|
|
140
|
-
To
|
|
157
|
+
To start CronOps with
|
|
141
158
|
|
|
142
|
-
```
|
|
159
|
+
```sh
|
|
143
160
|
npx @dotenvx/dotenvx run -- npx @mtakla/cronops
|
|
144
161
|
```
|
|
145
162
|
|
|
146
163
|
This will ...
|
|
147
164
|
|
|
148
|
-
- download the latest version of dotenvx
|
|
149
|
-
- load
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
165
|
+
- download the latest version of **dotenvx** and **cronops**
|
|
166
|
+
- load environment settings defined in the `.env` file
|
|
167
|
+
- create job config directory in `./config` with some example jobs
|
|
168
|
+
- starts the CronOps service
|
|
169
|
+
- the **example job** `[example job]` is active by default and scheduled to run every 5 seconds:
|
|
170
|
+
- the job will move files found in `./data/inbox` to `./data/outbox`
|
|
171
|
+
- in addition, all files moved to `./data/outbox` will be automatically deleted after 30 seconds
|
|
172
|
+
|
|
153
173
|
|
|
154
174
|
You can now add job configuration files to `./config/jobs` directory. Each YAML file in this directory defines a job. The server will hot reload when job files are added, modified, or removed.
|
|
155
175
|
|
|
@@ -202,28 +222,30 @@ runner.onError((err) => {
|
|
|
202
222
|
runner.schedule();
|
|
203
223
|
```
|
|
204
224
|
|
|
225
|
+
For more details, see the [TypeDoc documentation](https://mtakla.github.io/cronops/)
|
|
226
|
+
|
|
205
227
|
|
|
206
228
|
## Configuration
|
|
207
229
|
|
|
208
230
|
The CronOps service can be configured with the following environment variables:
|
|
209
231
|
|
|
210
|
-
| ENV | Description
|
|
211
|
-
| --------------------- |
|
|
212
|
-
| `CROPS_SOURCE_ROOT` | Path to primary source directory
|
|
213
|
-
| `CROPS_TARGET_ROOT` | Path to primary target directory
|
|
214
|
-
| `CROPS_SOURCE_2_ROOT` | Path to secondary source directory
|
|
215
|
-
| `CROPS_TARGET_2_ROOT` | Path to secondary target directory
|
|
216
|
-
| `CROPS_SOURCE_3_ROOT` | Path to tertiary source directory
|
|
217
|
-
| `CROPS_TARGET_3_ROOT` | Path to tertiary target directory
|
|
218
|
-
| `CROPS_CONFIG_DIR` | Path to the config directory where job files are located
|
|
219
|
-
| `CROPS_TEMP_DIR` | Path to temporary folder used for dry-run mode
|
|
220
|
-
| `CROPS_LOG_DIR` | Path to directory where job logs and file history are stored
|
|
221
|
-
| `CROPS_HOST` | Host address for the
|
|
222
|
-
| `CROPS_PORT` | Port for the
|
|
223
|
-
| `CROPS_EXEC_SHELL` | (*Optional*) Default shell for `exec` actions. Can be `false
|
|
224
|
-
| `CROPS_API_KEY` | (*Optional*) API key to secure admin API endpoints. Must be a hex‑encoded 256‑bit secret (e.g. 'openssl rand -hex 32')
|
|
225
|
-
| `CROPS_BASE_URL` | (*Optional*) Base URL for admin API and OpenAPI docs
|
|
226
|
-
| `TZ` | (*Optional*) Timezone for cron scheduling (standard timezone format)
|
|
232
|
+
| ENV | Description | Docker defaults |
|
|
233
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------- |
|
|
234
|
+
| `CROPS_SOURCE_ROOT` | Path to primary source directory | `/io/source` |
|
|
235
|
+
| `CROPS_TARGET_ROOT` | Path to primary target directory | `/io/target` |
|
|
236
|
+
| `CROPS_SOURCE_2_ROOT` | Path to secondary source directory | `/io/source2` |
|
|
237
|
+
| `CROPS_TARGET_2_ROOT` | Path to secondary target directory | `/io/target2` |
|
|
238
|
+
| `CROPS_SOURCE_3_ROOT` | Path to tertiary source directory | `/io/source3` |
|
|
239
|
+
| `CROPS_TARGET_3_ROOT` | Path to tertiary target directory | `/io/target3` |
|
|
240
|
+
| `CROPS_CONFIG_DIR` | Path to the config directory where job files are located | `/config` |
|
|
241
|
+
| `CROPS_TEMP_DIR` | Path to temporary folder used for dry-run mode | `/data/temp` |
|
|
242
|
+
| `CROPS_LOG_DIR` | Path to directory where job logs and file history are stored | `/data/logs` |
|
|
243
|
+
| `CROPS_HOST` | Host address for the admin API server | `0.0.0.0` |
|
|
244
|
+
| `CROPS_PORT` | Port for the admin API server | `8083` |
|
|
245
|
+
| `CROPS_EXEC_SHELL` | (*Optional*) Default shell for `exec` actions. Can be `false` (no shell), `true` (default shell), or path like `/bin/bash` | `false` |
|
|
246
|
+
| `CROPS_API_KEY` | (*Optional*) API key to secure admin API endpoints. Must be a hex‑encoded 256‑bit secret (e.g. 'openssl rand -hex 32') | - |
|
|
247
|
+
| `CROPS_BASE_URL` | (*Optional*) Base URL for admin API and OpenAPI docs | - |
|
|
248
|
+
| `TZ` | (*Optional*) Timezone for cron scheduling (standard timezone format) | `UTC` |
|
|
227
249
|
|
|
228
250
|
|
|
229
251
|
## Job Configuration
|
|
@@ -249,9 +271,8 @@ dry_run: true
|
|
|
249
271
|
enabled: false
|
|
250
272
|
```
|
|
251
273
|
|
|
252
|
-
|
|
253
|
-
You can change the job configuration at any time and the server will hot reload and schedule the new job
|
|
254
|
-
Be aware that once the job config has been changed, active running tasks will be terminated and the job will be rescheduled
|
|
274
|
+
🛈 **Note**
|
|
275
|
+
> You can change the job configuration at any time and the server will hot reload and schedule the new job configuration. Be aware that once the job config has been changed, active running tasks will be (gracefully) terminated and the job will be rescheduled
|
|
255
276
|
|
|
256
277
|
### Job Actions
|
|
257
278
|
|
|
@@ -268,10 +289,10 @@ CronOps supports 5 different job actions:
|
|
|
268
289
|
|
|
269
290
|
- **`exec`** - Execute a command or script. Use with `command`, `args`, `shell`, and `env` properties
|
|
270
291
|
|
|
271
|
-
|
|
272
|
-
>
|
|
273
|
-
>
|
|
274
|
-
>
|
|
292
|
+
💡 **Tip**
|
|
293
|
+
> Use `$1`, `$2`, or `$3` in job paths to refer to the configured roots.
|
|
294
|
+
> - **Source:** `$1` → `CROPS_SOURCE_ROOT`, `$2` → `CROPS_SOURCE_2_ROOT`, `$3` → `CROPS_SOURCE_3_ROOT`
|
|
295
|
+
> - **Target:** `$1` → `CROPS_TARGET_ROOT`, `$2` → `CROPS_TARGET_2_ROOT`, `$3` → `CROPS_TARGET_3_ROOT`
|
|
275
296
|
|
|
276
297
|
### Job Configuration examples
|
|
277
298
|
|
|
@@ -401,18 +422,16 @@ If the exec action is configured to run on selected `source` files:
|
|
|
401
422
|
| `verbose` | (*Optional*) Enable verbose logging for this job. Default: `false` |
|
|
402
423
|
| `enabled` | (*Optional*) If `false`, the job will not be scheduled. Default: `true` |
|
|
403
424
|
|
|
425
|
+
## Security considerations
|
|
404
426
|
|
|
405
|
-
|
|
406
|
-
## Security considerations & Trouble shooting
|
|
407
|
-
|
|
408
|
-
> [!NOTE]
|
|
427
|
+
🛈 **Note**
|
|
409
428
|
> It is strongly advised against accessing or modifying the data directly on the host system within Docker's internal volume storage path (typically `/var/lib/docker/volumes/`).
|
|
410
429
|
|
|
411
|
-
|
|
430
|
+
⚠ **WARNING**
|
|
412
431
|
> **Hazardous Misconfiguration**
|
|
413
432
|
>
|
|
414
|
-
> By default, CronOps runs as user/group **1000:1000** to follow a security‑first principle.
|
|
415
|
-
> You *can* run it as root by setting `PUID=0` and `PGID=0`, but **this is dangerous**.
|
|
433
|
+
> By default, the CronOps docker container runs as user/group **1000:1000** to follow a security‑first principle.
|
|
434
|
+
> You *can* run it as root by setting `PUID=0` and `PGID=0`, but **this is not recommended and can be dangerous**.
|
|
416
435
|
>
|
|
417
436
|
> When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. `/etc`, `/var`).
|
|
418
437
|
> This creates a **high‑risk security scenario**:
|
package/dist/api/openapi.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export class ExecHandler extends AbstractHandler {
|
|
|
17
17
|
await super.cleanup(ctx, fileHistory);
|
|
18
18
|
}
|
|
19
19
|
async exec(ctx, entry) {
|
|
20
|
-
const { targetDir } = ctx;
|
|
21
20
|
const verbose = ctx.job.verbose === true;
|
|
22
21
|
await new Promise((resolve, reject) => {
|
|
23
22
|
const { vars, env } = this.createVars(ctx, entry);
|
|
@@ -42,7 +41,6 @@ export class ExecHandler extends AbstractHandler {
|
|
|
42
41
|
stdio: ["ignore", verbose ? logFd : "ignore", verbose ? logFd : "ignore"],
|
|
43
42
|
shell: ctx.job.shell ?? this.setup.shell,
|
|
44
43
|
env: { ...process.env, ...env },
|
|
45
|
-
cwd: targetDir,
|
|
46
44
|
});
|
|
47
45
|
pid = child.pid;
|
|
48
46
|
ctx.writeLog(`◉ Subprocess started (pid:${pid}) ➜ ${cmd} [${args}]`);
|
|
@@ -27,14 +27,14 @@ export class JobRunnerSetup {
|
|
|
27
27
|
targetRootDirs;
|
|
28
28
|
handlerMap = new Map();
|
|
29
29
|
constructor(options = {}) {
|
|
30
|
-
this.sourceRoot = resolve(options.sourceRoot ?? process.env[ENV.SOURCE_ROOT] ?? "./");
|
|
31
|
-
this.targetRoot = resolve(options.targetRoot ?? process.env[ENV.TARGET_ROOT] ?? "./");
|
|
32
|
-
this.source2Root = resolve(options.source2Root ?? process.env[ENV.SOURCE_2_ROOT] ?? "./");
|
|
33
|
-
this.target2Root = resolve(options.target2Root ?? process.env[ENV.TARGET_2_ROOT] ?? "./");
|
|
34
|
-
this.source3Root = resolve(options.source3Root ?? process.env[ENV.SOURCE_3_ROOT] ?? "./");
|
|
35
|
-
this.target3Root = resolve(options.target3Root ?? process.env[ENV.TARGET_3_ROOT] ?? "./");
|
|
36
|
-
this.configDir = resolve(options.configDir ?? process.env[ENV.CONFIG_DIR] ??
|
|
37
|
-
this.logDir = resolve(options.logDir ?? process.env[ENV.LOG_DIR] ??
|
|
30
|
+
this.sourceRoot = resolve(options.sourceRoot ?? process.env[ENV.SOURCE_ROOT] ?? "./data");
|
|
31
|
+
this.targetRoot = resolve(options.targetRoot ?? process.env[ENV.TARGET_ROOT] ?? "./data");
|
|
32
|
+
this.source2Root = resolve(options.source2Root ?? process.env[ENV.SOURCE_2_ROOT] ?? "./data");
|
|
33
|
+
this.target2Root = resolve(options.target2Root ?? process.env[ENV.TARGET_2_ROOT] ?? "./data");
|
|
34
|
+
this.source3Root = resolve(options.source3Root ?? process.env[ENV.SOURCE_3_ROOT] ?? "./data");
|
|
35
|
+
this.target3Root = resolve(options.target3Root ?? process.env[ENV.TARGET_3_ROOT] ?? "./data");
|
|
36
|
+
this.configDir = resolve(options.configDir ?? process.env[ENV.CONFIG_DIR] ?? "./config");
|
|
37
|
+
this.logDir = resolve(options.logDir ?? process.env[ENV.LOG_DIR] ?? "./logs");
|
|
38
38
|
this.tempDir = resolve(options.tempDir ?? process.env[ENV.TEMP_DIR] ?? join(os.tmpdir(), "cronops"));
|
|
39
39
|
this.uid = options.uid ?? process.env[ENV.PUID] ?? `${process.getuid?.() ?? "0"}`;
|
|
40
40
|
this.gid = options.gid ?? process.env[ENV.PGID] ?? `${process.getgid?.() ?? "0"}`;
|
package/dist/tasks/JobLoader.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
1
|
import fsx from "fs-extra";
|
|
3
2
|
import YAML from "yaml";
|
|
4
3
|
import glob from "fast-glob";
|
|
@@ -17,7 +16,7 @@ export class JobLoader extends AbstractTask {
|
|
|
17
16
|
jobHistory;
|
|
18
17
|
constructor(options = {}) {
|
|
19
18
|
super("*/8 * * * * *");
|
|
20
|
-
this.configDir = resolve(options.configDir ?? process.env[ENV.CONFIG_DIR] ??
|
|
19
|
+
this.configDir = resolve(options.configDir ?? process.env[ENV.CONFIG_DIR] ?? "./config");
|
|
21
20
|
this.jobHistory = new FileHistoryModel();
|
|
22
21
|
}
|
|
23
22
|
async run() {
|
|
@@ -3,11 +3,11 @@ export declare const JobSchema: z.ZodObject<{
|
|
|
3
3
|
id: z.ZodOptional<z.ZodString>;
|
|
4
4
|
cron: z.ZodOptional<z.ZodString>;
|
|
5
5
|
action: z.ZodEnum<{
|
|
6
|
+
copy: "copy";
|
|
7
|
+
delete: "delete";
|
|
6
8
|
exec: "exec";
|
|
7
9
|
call: "call";
|
|
8
|
-
copy: "copy";
|
|
9
10
|
move: "move";
|
|
10
|
-
delete: "delete";
|
|
11
11
|
archive: "archive";
|
|
12
12
|
}>;
|
|
13
13
|
command: z.ZodOptional<z.ZodString>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mtakla/cronops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Automated cross-container file lifecycle management with cron scheduling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "mtakla@nevereven",
|
|
@@ -66,28 +66,27 @@
|
|
|
66
66
|
"zod": "4.3.6"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@biomejs/biome": "^2.
|
|
69
|
+
"@biomejs/biome": "^2.4.4",
|
|
70
70
|
"@types/fs-extra": "^11.0.4",
|
|
71
|
-
"@types/node": "^25.0
|
|
71
|
+
"@types/node": "^25.3.0",
|
|
72
72
|
"@types/shell-quote": "^1.7.5",
|
|
73
73
|
"@types/tar-fs": "2.0.4",
|
|
74
|
-
"@vitest/coverage-v8": "^4.0.
|
|
75
|
-
"typedoc": "^0.28.
|
|
74
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
75
|
+
"typedoc": "^0.28.17",
|
|
76
76
|
"typescript": "^5.9.3",
|
|
77
|
-
"vitest": "^4.0.
|
|
77
|
+
"vitest": "^4.0.18"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"check": "biome check",
|
|
81
81
|
"check:fix": "biome check --write",
|
|
82
82
|
"test": "vitest run",
|
|
83
|
-
"docs": "typedoc",
|
|
84
83
|
"coverage": "vitest run --coverage",
|
|
85
84
|
"loadtest": "tsc && node ./dist/tests/loadtest.js",
|
|
86
|
-
"
|
|
87
|
-
"dev": "tsc && node ./dist/server.js",
|
|
85
|
+
"docs": "typedoc",
|
|
88
86
|
"build": "tsc",
|
|
89
87
|
"build:types": "tsc --declaration",
|
|
90
|
-
"
|
|
88
|
+
"dev": "tsc --sourceMap --inlineSources && node --env-file=.env.local ./dist/server.js",
|
|
89
|
+
"docker": "docker build --load --target app -t cronops:local .",
|
|
91
90
|
"docker:docs": "docker build --load --target docs -t cronops-docs:local ."
|
|
92
91
|
}
|
|
93
92
|
}
|