@mtakla/cronops 0.1.1-rc7 → 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
|
@@ -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
|
|
|
@@ -47,6 +48,7 @@ CronOps is built and optimized to run as a Docker container itself.
|
|
|
47
48
|
|
|
48
49
|
```sh
|
|
49
50
|
docker run \
|
|
51
|
+
--name cronops \
|
|
50
52
|
-p 8083:8083 \
|
|
51
53
|
-v ./config:/config \
|
|
52
54
|
-v ./data:/io/source \
|
|
@@ -83,8 +85,8 @@ target:
|
|
|
83
85
|
|
|
84
86
|
Now you can add more job configuration files to `./config/jobs`. For detailes, see [job configuration](#job-configuration) section below.
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
> You
|
|
88
|
+
🛈 **Note**
|
|
89
|
+
> You don't need to restart the server after changing job files. The server identifies any changes and will hot reload the configuration. If a job configuration is invalid, an appropriate message will appear in the docker logs and the specific job will not be scheduled.
|
|
88
90
|
|
|
89
91
|
### Using Docker Compose
|
|
90
92
|
|
|
@@ -128,28 +130,37 @@ To enable **admin Web-API**, just set `CROPS_API_KEY` environment variable. Deta
|
|
|
128
130
|
|
|
129
131
|
This requires [Node.js](https://nodejs.org/) (>= v24) to be installed on your server.
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
To install & start CronOps, type:
|
|
134
|
+
|
|
132
135
|
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
CROPS_TARGET_ROOT=./data # change as you like
|
|
136
|
-
CROPS_CONFIG_DIR=./config # default is ~/.cronops/config
|
|
137
|
-
CROPS_LOG_DIR=./logs # default is ~/.cronops/logs
|
|
136
|
+
```sh
|
|
137
|
+
npx @mtakla/cronops
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
For configuration, create an empty folder with an `.env` file that contains your config settings (see [Configuration](#configuration) section below).
|
|
141
|
+
|
|
142
|
+
```dotenv
|
|
143
|
+
CROPS_CONFIG_DIR=./config
|
|
144
|
+
CROPS_TARGET_ROOT=./data
|
|
145
|
+
CROPS_SOURCE_ROOT=./data
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
To start CronOps with
|
|
141
149
|
|
|
142
|
-
```
|
|
150
|
+
```sh
|
|
143
151
|
npx @dotenvx/dotenvx run -- npx @mtakla/cronops
|
|
144
152
|
```
|
|
145
153
|
|
|
146
154
|
This will ...
|
|
147
155
|
|
|
148
|
-
- download the latest version of dotenvx
|
|
149
|
-
- load
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
156
|
+
- download the latest version of **dotenvx** and **cronops**
|
|
157
|
+
- load environment settings defined in the `.env` file
|
|
158
|
+
- create job config directory in `./config` with some example jobs
|
|
159
|
+
- starts the CronOps service
|
|
160
|
+
- the **example job** `[example job]` is active by default and scheduled to run every 5 seconds:
|
|
161
|
+
- the job will move files found in `./data/inbox` to `./data/outbox`
|
|
162
|
+
- in addition, all files moved to `./data/outbox` will be automatically deleted after 30 seconds
|
|
163
|
+
|
|
153
164
|
|
|
154
165
|
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
166
|
|
|
@@ -202,28 +213,30 @@ runner.onError((err) => {
|
|
|
202
213
|
runner.schedule();
|
|
203
214
|
```
|
|
204
215
|
|
|
216
|
+
For more details, see the [TypeDoc documentation](https://mtakla.github.io/cronops/)
|
|
217
|
+
|
|
205
218
|
|
|
206
219
|
## Configuration
|
|
207
220
|
|
|
208
221
|
The CronOps service can be configured with the following environment variables:
|
|
209
222
|
|
|
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)
|
|
223
|
+
| ENV | Description | Docker defaults |
|
|
224
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------- |
|
|
225
|
+
| `CROPS_SOURCE_ROOT` | Path to primary source directory | `/io/source` |
|
|
226
|
+
| `CROPS_TARGET_ROOT` | Path to primary target directory | `/io/target` |
|
|
227
|
+
| `CROPS_SOURCE_2_ROOT` | Path to secondary source directory | `/io/source2` |
|
|
228
|
+
| `CROPS_TARGET_2_ROOT` | Path to secondary target directory | `/io/target2` |
|
|
229
|
+
| `CROPS_SOURCE_3_ROOT` | Path to tertiary source directory | `/io/source3` |
|
|
230
|
+
| `CROPS_TARGET_3_ROOT` | Path to tertiary target directory | `/io/target3` |
|
|
231
|
+
| `CROPS_CONFIG_DIR` | Path to the config directory where job files are located | `/config` |
|
|
232
|
+
| `CROPS_TEMP_DIR` | Path to temporary folder used for dry-run mode | `/data/temp` |
|
|
233
|
+
| `CROPS_LOG_DIR` | Path to directory where job logs and file history are stored | `/data/logs` |
|
|
234
|
+
| `CROPS_HOST` | Host address for the admin API server | `0.0.0.0` |
|
|
235
|
+
| `CROPS_PORT` | Port for the admin API server | `8083` |
|
|
236
|
+
| `CROPS_EXEC_SHELL` | (*Optional*) Default shell for `exec` actions. Can be `false` (no shell), `true` (default shell), or path like `/bin/bash` | `false` |
|
|
237
|
+
| `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') | - |
|
|
238
|
+
| `CROPS_BASE_URL` | (*Optional*) Base URL for admin API and OpenAPI docs | - |
|
|
239
|
+
| `TZ` | (*Optional*) Timezone for cron scheduling (standard timezone format) | `UTC` |
|
|
227
240
|
|
|
228
241
|
|
|
229
242
|
## Job Configuration
|
|
@@ -249,9 +262,8 @@ dry_run: true
|
|
|
249
262
|
enabled: false
|
|
250
263
|
```
|
|
251
264
|
|
|
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
|
|
265
|
+
🛈 **Note**
|
|
266
|
+
> 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
267
|
|
|
256
268
|
### Job Actions
|
|
257
269
|
|
|
@@ -268,10 +280,10 @@ CronOps supports 5 different job actions:
|
|
|
268
280
|
|
|
269
281
|
- **`exec`** - Execute a command or script. Use with `command`, `args`, `shell`, and `env` properties
|
|
270
282
|
|
|
271
|
-
|
|
272
|
-
>
|
|
273
|
-
>
|
|
274
|
-
>
|
|
283
|
+
💡 **Tip**
|
|
284
|
+
> Use `$1`, `$2`, or `$3` in job paths to refer to the configured roots.
|
|
285
|
+
> - **Source:** `$1` → `CROPS_SOURCE_ROOT`, `$2` → `CROPS_SOURCE_2_ROOT`, `$3` → `CROPS_SOURCE_3_ROOT`
|
|
286
|
+
> - **Target:** `$1` → `CROPS_TARGET_ROOT`, `$2` → `CROPS_TARGET_2_ROOT`, `$3` → `CROPS_TARGET_3_ROOT`
|
|
275
287
|
|
|
276
288
|
### Job Configuration examples
|
|
277
289
|
|
|
@@ -403,16 +415,16 @@ If the exec action is configured to run on selected `source` files:
|
|
|
403
415
|
|
|
404
416
|
|
|
405
417
|
|
|
406
|
-
## Security considerations
|
|
418
|
+
## Security considerations
|
|
407
419
|
|
|
408
|
-
|
|
420
|
+
🛈 **Note**
|
|
409
421
|
> 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
422
|
|
|
411
|
-
|
|
423
|
+
⚠ **WARNING**
|
|
412
424
|
> **Hazardous Misconfiguration**
|
|
413
425
|
>
|
|
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**.
|
|
426
|
+
> By default, the CronOps docker container runs as user/group **1000:1000** to follow a security‑first principle.
|
|
427
|
+
> You *can* run it as root by setting `PUID=0` and `PGID=0`, but **this is not recommended and can be dangerous**.
|
|
416
428
|
>
|
|
417
429
|
> 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
430
|
> 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"}`;
|
|
@@ -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.1
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Automated cross-container file lifecycle management with cron scheduling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "mtakla@nevereven",
|
|
@@ -66,27 +66,26 @@
|
|
|
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",
|
|
88
|
+
"dev": "tsc --sourceMap --inlineSources && node --env-file=.env.local ./dist/server.js",
|
|
90
89
|
"docker": "docker build --load --target app -t cronops-dev:local .",
|
|
91
90
|
"docker:docs": "docker build --load --target docs -t cronops-docs:local ."
|
|
92
91
|
}
|