Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

CronOps

License: ISCCoverage StatusDocsBuy Me A Coffee

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.

WARNING

This project is under active development. Production use is not yet recommended.

Why CronOps?

In containerized workflows, files often accumulate in volumes: downloads, logs, temporary exports, backups. CronOps acts as your digital janitor, running scheduled jobs that:

  • Execute OS commands
  • Select files using powerful glob patterns and
    • delete them on a regular basis
    • copy or move them to specific target path
    • archive them automatically using date/time bases archive name patterns
    • process them with OS commands (e.g. awk/sed, curl, untar/unzip, ...)
    • execute scripts on them (sh/bash/cmd/powershell/node/lua, ...)

All configured via simple, version-controllable *.yml based job definition files — no coding required.

Top Features

  • Cron-like scheduling – Flexible job timing using familiar cron syntax
  • Glob-based filtering – Precisely select source files to be processed
  • File operations – Copy, move, delete, or archive files
  • Command execution – Process files with OS commands or custom scripts
  • Permission management – Change uid, gid, and file permissions on processed target files
  • Automatic cleanup – Remove target files after a configurable retention period
  • Incremental processing – Only process changed or new files since last run
  • Dry-run mode – Test jobs and execute scripts safely before applying changes
  • Detailed logging – Detailed job execution logs with stdout/stderr aggregation
  • Hot reload – Change job configs without restarting the service
  • Admin API – Trigger jobs, check status, pause/resume scheduling via secured REST-API
  • OpenAPI Web UI – Interactive API documentation and execution
  • Easy setup – Runs with zero configuration. All config via environment variables

Installation

Install & run with Docker

CronOps is built and optimized to run as a Docker container itself.

To download and start

docker run -d \
--name cronops \
-p 8083:8083 \
-v ./config:/config \
-v ./data:/io/source \
-v ./data:/io/target \
-e PUID=1000 \
-e PGID=1000 \
ghcr.io/mtakla/cronops:latest

To check if the server is running

docker logs -f cronops

If your container is running there is an example job active that is scheduled every 5 seconds and moves files from ./data/inbox to ./data/outbox. In addition, files in the outbox that are older than 30sec will be automatically cleaned up.

The corresponding job config can be found in ./config/jobs/example-job.yaml:

action: movecron: "*/5 * * * * *"source:
dir: /inboxincludes:
- "**/**"excludes:
- "**/.*"
- "**/*.log"target:
dir: /outboxretention: "20s"

Now you can add more job configuration files to ./config/jobs. For detailes, see job configuration section below.

🛈 Note

You don't need to restart the server after changing job files. The server identifies any changes and will automatcally hot reload the configuration.

To pull latest release of CronOps

docker pull ghcr.io/mtakla/cronops:latest

Using Docker Compose

To install and run CronOps via docker compose, just create a compose.yaml file in an empty directory:

services:
cronops:
image: ghcr.io/mtakla/cronops:latestcontainer_name: cronopsrestart: unless-stoppedvolumes:
- ./config:/config
- ./logs:/data/logs
- ./data:/io/source
- ./data:/io/targetenvironment:
PUID: 1000PGID: 1000TZ: Europe/Berlin

In same directory, type docker compose up -d to install and start the cronops service.

Updating CronOps with Docker Compose

When using docker compose, to update to the latest version of CronOps, just type

docker compose pull && docker compose up -d

in the same directory where compose.yaml has been created.

Admin API

By default

To use the Admin API, define an api key via CROPS_API_KEY environment variable. The api key must be a hex‑encoded 256‑bit secret that can e.g. be created via

 openssl rand -hex 32

By default, the OpenAPI UI (/docs) does not remember the API key you enter — it has to be re-entered on every page reload. Setting CROPS_UI_PERSIST_AUTH=true makes the UI persist the entered API key in the browser so it survives reloads.

WARNING

CROPS_UI_PERSIST_AUTH=true stores your CROPS_API_KEY in the browser (local storage) of whoever opens /docs. Anyone with access to that browser/profile, or any script able to read its storage (e.g. via an XSS vulnerability), can retrieve the key and make authenticated calls to the Admin API. Only enable this on trusted, single-user machines, never on a shared or public browser, and always serve /docs over HTTPS when enabled.

Manual installation

This requires Node.js (>= v24) to be installed on your server.

To install & start CronOps

npx @mtakla/cronops

For configuration, create an .env file in you folder that contains your config settings (see Configuration section below).

CROPS_CONFIG_DIR=./configCROPS_TARGET_ROOT=./dataCROPS_SOURCE_ROOT=./data

Then, start CronOps with

npx @dotenvx/dotenvx run -- npx @mtakla/cronops

This will ...

  • download the latest version of dotenvx and cronops
  • load environment settings defined in the .env file
  • create job config directory in ./config with some example jobs
  • start the CronOps service
  • the example job[example job] is active by default and scheduled to run every 5 seconds. It job will
    • move files found in ./data/inbox to ./data/outbox
    • cleanup all files moved to ./data/outbox after 30 seconds

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.

Use in your code

Install CronOps in your project using npm

npm install @mtakla/cronops --save

To create a job runner:

import{createJobRunner}from"@mtakla/cronops";// create runner optionsconstrunnerOptions={configDir: "./config"};// create a job runner instance construnner=createJobRunner({action: "copy",cron: "*/5 * * * * *",source: {dir: "download/",},target: {dir: "backup/downloads",retention: "30d"}},runnerOptions);runner.onScheduled(()=>{console.log("job scheduled!");});runner.onStarted(()=>{console.log("job started!");});runner.onFinished(()=>{console.log("job finished!");});runner.onError((err)=>{console.log(`job failed with ${err.message}`);});// finally schedule jobrunner.schedule();

For more details, see the TypeDoc documentation

Configuration

The CronOps service can be configured with the following environment variables:

ENVDescriptionDocker defaults
CROPS_SOURCE_ROOTPath to primary source directory/io/source
CROPS_TARGET_ROOTPath to primary target directory/io/target
CROPS_SOURCE_2_ROOTPath to secondary source directory/io/source2
CROPS_TARGET_2_ROOTPath to secondary target directory/io/target2
CROPS_SOURCE_3_ROOTPath to tertiary source directory/io/source3
CROPS_TARGET_3_ROOTPath to tertiary target directory/io/target3
CROPS_CONFIG_DIRPath to the config directory where job files and scripts are located/config
CROPS_TEMP_DIRPath to temporary folder used for dry-run mode/data/temp
CROPS_LOG_DIRPath to directory where job logs and file history are stored/data/logs
CROPS_HOSTHost address for the admin API server0.0.0.0
CROPS_PORTPort for the admin API server8083
CROPS_EXEC_SHELL(Optional) Default shell for exec actions. Can be false (no shell), true (default shell), or path like /bin/bashfalse
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')-
CROPS_BASE_URL(Optional) Base URL for admin API and OpenAPI UI if cronops runs behind a reverse proxy-
CROPS_UI_PERSIST_AUTH(Optional) Persists the API key entered in the OpenAPI UI (/docs) across page reloads. See security warning below.false
TZ(Optional) Timezone for cron scheduling (standard timezone format)UTC
PUID(Optional, Docker only) UID of the user the CronOps server runs as in the docker container1000
PGID(Optional, Docker only) GID of the group the CronOps server runs as in the docker container1000

Job Configuration

Jobs are configured as YAML files in the CROPS_CONFIG_DIR/jobs directory. Each YAML file defines one job.

Example job config ./config/jobs/example.yaml

action: move # exec|copy|move|delete|archivecron: "*/5 * * * * *"source:
dir: $1/nzbget/config/data/downloadincludes:
- "**/*.mp4"target:
dir: $1/filegator/micha/downloadspermissions:
file_mode: "444"dir_mode: "711"retention: 12hdry_run: trueenabled: false

🛈 Note

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

Job Actions

CronOps supports 5 different job actions:

File based actions

  • copy - Copy files from source to target directory while preserving originals
  • move - Move files from source to target directory (removes originals after successful copy)
  • delete - Delete files matching the source patterns
  • archive - Create a compressed tar.gz archive of matched files in the target directory

Command execution action

  • exec - Execute a command or script. Use with command, args, shell, and env properties

💡 Tip

Use $1, $2, or $3 in job paths to refer to the configured roots.

  • Source:$1CROPS_SOURCE_ROOT, $2CROPS_SOURCE_2_ROOT, $3CROPS_SOURCE_3_ROOT
  • Target:$1CROPS_TARGET_ROOT, $2CROPS_TARGET_2_ROOT, $3CROPS_TARGET_3_ROOT

Job Configuration examples

Copy Files with Pattern Matching

action: copycron: "0 2 * * *"# Daily at 2 AMsource:
dir: $1/downloadsincludes:
- "**/*.pdf"
- "**/*.doc"excludes:
- "**/*.tmp"target:
dir: $1/archive/documentspermissions:
file_mode: "644"dir_mode: "755"retention: "30d"

Create an archive

action: archivecron: "0 0 * * 0"# Weekly on Sunday at midnightsource:
dir: $1/logsincludes:
- "**/*.log"excludes:
- ".git/**"
- "node_modules/**"target:
dir: $1/backupsarchive_name: "logs-{{yyyy-MM-dd}}.tgz"

Execute Custom Command

action: execcron: "*/15 * * * *"# Every 15 minutescommand: "node"args:
- "--experimental-vm-modules"
- "{scriptDir}/cleanup.js"env:
LOG_LEVEL: "info"API_TOKEN: "secret123"

Command execution parameters

For jobs of action type exec, you can use dynamic parameters in your command, args or custom env entries that will be resolved before the system command is executed:

ParameterDescription
{jobId}job identifier
{sourceDir}absolute path to the job source directory
{targetDir}absolute path to the job target directory (or CROPS_TARGET_ROOT)
{tempDir}absolute path to the configured temp directory
{logDir}absolute path to the configured log directory
{scriptDir}absolute path to the config/scripts directory
{secretDir}absolute path to the config/secrets directory

If the exec action is configured to run on selected source files:

ParameterDescription
{file}absolute path to the processed file, e.g. /io/source/foo/bar.txt
{fileDir}absolute path to the parent dir of the processed file, e.g. /io/source/foo
{fileName}name of the processed file, e.g. bar.txt
{fileBase}base name of the processed file without extension, e.g. bar
{fileExt}extension of the processed file, e.g. .txt

Command execution ENV defaults

For jobs of action type exec the following environment variables are available by default when the os command is executed.

ParameterDescription
CROPS_JOB_IDjob identifier
CROPS_SOURCE_DIRabsolute path to the job source directory
CROPS_TARGET_DIRabsolute path to the job target directory (or CROPS_TARGET_ROOT)
CROPS_SCRIPT_DIRabsolute path to the configured script directory
CROPS_TEMP_DIRabsolute path to the configured temp directory
CROPS_LOG_DIRabsolute path to the configured log directory
CROPS_DRY_RUN"true", if dry_run mode is enabled
CROPS_VERBOSE"true", if verbose mode is enabled

If the exec action is configured to run on selected source files:

ParameterDescription
CROPS_FILEabsolute path to the processed source file, e.g. /io/source/foo/bar.txt
CROPS_FILE_DIRabsolute path to the parent dir of the processed source file, e.g. /io/source/foo
CROPS_FILE_NAMEfile name of the processed source file, e.g. bar.txt
CROPS_FILE_BASEbase name of the processed source file without extension, e.g. bar
CROPS_FILE_EXTextension of the processed source file, e.g. .txt

Job properties

PropertyDescription
actionRequired. The action to perform. One of: exec, copy, move, delete, archive
cron(Optional) Cron-like scheduling string, e.g., */2 * * * *. See node-cron documentation for details. If omitted, job runs once.
command(For exec/call actions) Command to execute, e.g., "node", "/bin/bash"
shell(Optional) Shell to use for command execution. Can be true (use default shell) or a path to a shell binary
args(Optional) Array of command arguments for exec/call actions
env(Optional) Environment variables to pass to the command. Object with uppercase keys and string values
source.dirSource directory path. Can be absolute or use $1 (CROPS_SOURCE_ROOT), $2 (CROPS_SOURCE_2_ROOT), or $3 (CROPS_SOURCE_3_ROOT), e.g., "$1/downloads"
source.includes(Optional) Array of glob patterns to include files, relative to source.dir. Default: ["**/*"]
source.excludes(Optional) Array of glob patterns to exclude files from processing
target.dirTarget directory path. Can be absolute or use $1 (CROPS_TARGET_ROOT), $2 (CROPS_TARGET_2_ROOT), or $3 (CROPS_TARGET_3_ROOT)
target.archive_name(For archive action) Archive file name pattern with date placeholders, e.g., "backup-{{yyyy-MM-dd}}.tgz"
target.permissions.owner(Optional) Change user/group ownership to "uid:gid" for all target files. Default: process owner unless PUID or PGID environment is set
target.permissions.file_mode(Optional) Change file permissions using octal (e.g., "644") or symbolic mode (e.g., "ugo+r"). Default: "660"
target.permissions.dir_mode(Optional) Change directory permissions using octal (e.g., "755") or symbolic mode (e.g., "ugo+rx"). Default: "770"
target.retention(Optional) Time period after which target files will be deleted, e.g., "10d", "12h". Uses ms format. Default: files are kept
dry_run(Optional) If true, simulate the operation without making actual changes. Source files are never modified in dry-run mode. Default: false
verbose(Optional) Enable verbose logging for this job. Default: false
enabled(Optional) If false, the job will not be scheduled. Default: true

Security considerations

🛈 Note

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/).

WARNING

Hazardous Misconfiguration

By default, the CronOps docker container runs as user/group 1000:1000 to follow a security‑first principle.
You can run it as root by setting PUID=0 and PGID=0, but this is not recommended and can be dangerous.

When running as root, bind‑mounted host volumes (source/target directories) may map to critical system paths on the host (e.g. /etc, /var).
This creates a high‑risk security scenario:

  • System file overwrite: the container can read, modify, or delete critical host files via mounted paths.
  • Host damage through misconfigured mounts: a wrong bind mount can expose system directories, allowing root inside the container to corrupt or erase host data.

Other than that ...

License

 _____ _ _ | ____| _ __ (_) ___ _ _ | |
| _| | '_ \ | | / _ \ | | | | | |
| |___ | | | | | | | (_) | | |_| | |_|
|_____| |_| |_| _/ | \___/ \__, | (_)
|__/ |___/ 

CronOps is under ISC License. Made with ❤ in EU

About

Cron based cross-container lifecycle management

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages