@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
- [![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
 
@@ -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
- > [!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.
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
- First step is to create an empty CronOps app directory with a `.env` file that contains your configuration settings, e.g.:
133
+ To install & start CronOps, type:
134
+
132
135
 
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
136
+ ```sh
137
+ npx @mtakla/cronops
138
138
  ```
139
139
 
140
- To install & start CronOps, type:
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
- ```bash
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 & 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
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 | 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` |
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
- > [!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
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
- > [!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`.
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 & Trouble shooting
418
+ ## Security considerations
407
419
 
408
- > [!NOTE]
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
- > [!WARNING]
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**:
@@ -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";
@@ -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.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.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",
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
  }