@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
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://github.com/mtakla/cronops/blob/master/LICENSE)
4
- [![Coverage Status](https://img.shields.io/badge/coverage-95%25-green)](https://img.shields.io/badge/coverage-95%25-green)
3
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://github.com/mtakla/cronops/blob/main/LICENSE)
4
+ [![Coverage Status](https://img.shields.io/badge/Coverage-95%25-green)](https://img.shields.io/badge/coverage-95%25-green)
5
+ [![Docs](https://img.shields.io/badge/TypeDoc-docs-blue)](https://mtakla.github.io/cronops/)
5
6
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Donate-orange.svg)](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
- > [!WARNING]
10
- > This project is in early BETA. Production use is not yet recommened!
10
+ **WARNING**
11
+ > This project is under active development. Production use is not yet recommended.
11
12
 
12
- ## 💡 Why CronOps?
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
- > [!NOTE]
87
- > You do not 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.
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
- First step is to create an empty CronOps app directory with a `.env` file that contains your configuration settings, e.g.:
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
- ```ini
134
- CROPS_SOURCE_ROOT=./data # change as you like
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
151
+ ```dotenv
152
+ CROPS_CONFIG_DIR=./config
153
+ CROPS_TARGET_ROOT=./data
154
+ CROPS_SOURCE_ROOT=./data
138
155
  ```
139
156
 
140
- To install & start CronOps, type:
157
+ To start CronOps with
141
158
 
142
- ```bash
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 & cronops
149
- - load the environment settings defined in the `.env` file
150
- - start the CronOps service with the loaded environment settings
151
- - create config directory in `./config` if it doesn't exist
152
- - create logs directory in `./logs` if it doesn't exist
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 | Docker defaults |
211
- | --------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------- |
212
- | `CROPS_SOURCE_ROOT` | Path to primary source directory | `/io/source` |
213
- | `CROPS_TARGET_ROOT` | Path to primary target directory | `/io/target` |
214
- | `CROPS_SOURCE_2_ROOT` | Path to secondary source directory | `/io/source2` |
215
- | `CROPS_TARGET_2_ROOT` | Path to secondary target directory | `/io/target2` |
216
- | `CROPS_SOURCE_3_ROOT` | Path to tertiary source directory | `/io/source3` |
217
- | `CROPS_TARGET_3_ROOT` | Path to tertiary target directory | `/io/target3` |
218
- | `CROPS_CONFIG_DIR` | Path to the config directory where job files are located | `/config` |
219
- | `CROPS_TEMP_DIR` | Path to temporary folder used for dry-run mode | `/data/temp` |
220
- | `CROPS_LOG_DIR` | Path to directory where job logs and file history are stored | `/data/logs` |
221
- | `CROPS_HOST` | Host address for the Admin API server | `0.0.0.0` |
222
- | `CROPS_PORT` | Port for the Admin API server | `8083` |
223
- | `CROPS_EXEC_SHELL` | (*Optional*) Default shell for `exec` actions. Can be `false`, `true`, or path | `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) | `UTC` |
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
- > [!NOTE]
253
- You can change the job configuration at any time and the server will hot reload and schedule the new job configuration.
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
- > [!TIP]
272
- > **Path References**: Use `$1`, `$2`, or `$3` in job paths to reference configured root directories.
273
- > For source paths, these map to `CROPS_SOURCE_ROOT`, `CROPS_SOURCE_2_ROOT`, and `CROPS_SOURCE_3_ROOT`.
274
- > For target paths, they map to `CROPS_TARGET_ROOT`, `CROPS_TARGET_2_ROOT`, and `CROPS_TARGET_3_ROOT`.
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
- > [!WARNING]
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**:
@@ -47,7 +47,7 @@ export declare const openapi: {
47
47
  shell: {
48
48
  anyOf: ({
49
49
  type: string;
50
- minLength?: never;
50
+ minLength?: undefined;
51
51
  } | {
52
52
  type: string;
53
53
  minLength: number;
@@ -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] ?? join(os.homedir(), ".cronops", "config"));
37
- this.logDir = resolve(options.logDir ?? process.env[ENV.LOG_DIR] ?? join(os.homedir(), ".cronops", "logs"));
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"}`;
@@ -1,4 +1,4 @@
1
- import cron, {} from "node-cron";
1
+ import cron from "node-cron";
2
2
  import { setTimeout } from "node:timers/promises";
3
3
  import { EventEmitter } from "node:events";
4
4
  import { ENV } from "../types/Options.types.js";
@@ -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] ?? join(os.homedir(), ".cronops", "config"));
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.1-rc7",
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.3.6",
69
+ "@biomejs/biome": "^2.4.4",
70
70
  "@types/fs-extra": "^11.0.4",
71
- "@types/node": "^25.0.3",
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.14",
75
- "typedoc": "^0.28.16",
74
+ "@vitest/coverage-v8": "^4.0.18",
75
+ "typedoc": "^0.28.17",
76
76
  "typescript": "^5.9.3",
77
- "vitest": "^4.0.14"
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
- "clean": "rm -rf dist",
87
- "dev": "tsc && node ./dist/server.js",
85
+ "docs": "typedoc",
88
86
  "build": "tsc",
89
87
  "build:types": "tsc --declaration",
90
- "docker": "docker build --load --target app -t cronops-dev:local .",
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
  }