Skip to content

awbx/cronix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

86 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

cronix

cronix

Cron jobs as code. Your handler is the source of truth β€” cronix reconciles it onto crontab, systemd-timer, Kubernetes, or AWS EventBridge.

npm version Go Reference CI Release License

Docs Β· Quick start Β· RFC Β· Examples


cronix-showcase.mp4

⚠️ Under active development. The on-the-wire spec is stable; APIs may evolve before v1.0. Try it and file issues.

The problem

Today, "I need a scheduled job" has three answers β€” none of them tell you the whole picture:

  • 🟧 In-app queue (BullMQ / Agenda) β€” needs Redis ops, repeats stack on restart, schedule lives in code and in Redis.
  • 🟧 In-process (node-cron / cron) β€” stops with the process, every replica fires it (N pods β†’ N runs), no audit, no retries.
  • 🟧 Host scheduler (crontab / systemd / k8s) β€” per-machine install, ssh-edit drift, no who-changed-what audit, silent failures.

Whichever you pick, you can't answer: is this running anywhere right now? who changed the schedule? did the last run succeed?

The flip

cronix puts the schedule next to the handler. Your app's /.well-known/cron-manifest endpoint is the source of truth. cronix apply reconciles it against whichever scheduler the host provides. The host scheduler does the firing. A small Go binary, cronix trigger, handles HMAC signing, concurrency locks, timeouts, and retries on every fire.

The protocol is the product. The reconciler and SDKs are reference implementations.

Install

CLI (the reconciler)

# macOS β€” Homebrew
brew install awbx/cronix/cronix

# Linux / macOS β€” one-liner
curl -fsSL https://raw.githubusercontent.com/awbx/cronix/main/install.sh | sh

# Pin a version + custom install dir
curl -fsSL https://raw.githubusercontent.com/awbx/cronix/main/install.sh \
  | CRONIX_VERSION=v0.7.2 INSTALL_DIR=/usr/local/bin sh

# Linux packages β€” grab from the latest release
# https://github.com/awbx/cronix/releases/latest
#   cronix_<ver>_linux_amd64.deb  (Debian/Ubuntu)
#   cronix_<ver>_linux_amd64.rpm  (RHEL/Fedora/openSUSE)
#   cronix_<ver>_linux_amd64.apk  (Alpine)

# Go developers
go install github.com/awbx/cronix/go/cmd/cronix@latest

# Docker
docker pull awbx/cronix

Verify:

cronix version

App SDK

# TypeScript
pnpm add @awbx/cronix-sdk

# Framework adapters (only if you need them β€” see below)
pnpm add @awbx/cronix-adapter-express
pnpm add @awbx/cronix-adapter-fastify
pnpm add @awbx/cronix-adapter-koa
pnpm add @awbx/cronix-adapter-nest

# Go (signature verification only)
go get github.com/awbx/cronix/go/pkg/cronsdk

Quick start (TypeScript + Hono)

import { createCron } from "@awbx/cronix-sdk";
import { Hono } from "hono";

const cron = createCron({
  app: "billing-service",
  baseUrl: "https://billing.example.com",
  secret: process.env.CRON_SECRET!,
});

cron.register({
  name: "reconcile-payments",
  schedule: "*/15 * * * *", // ← lives next to the handler
  handler: async (ctx) => {
    console.log(`fired ${ctx.name} run=${ctx.runId}`);
    // your work here
    return { ok: true };
  },
});

const app = new Hono();
app.all("/.well-known/cron-manifest", (c) => cron.handle(c.req.raw));
app.all("/api/v1/scheduled/:name", (c) => cron.handle(c.req.raw));

export default app;

Reconcile from your laptop or CI:

cronix apply \
  --manifest https://billing.example.com/.well-known/cron-manifest \
  --backend crontab \
  --crontab-path /etc/crontab \
  --trigger-bin /usr/local/bin/cronix \
  --secret-ref env:CRON_SECRET

That's it. Your */15 * * * * line lives in your app code; cron(8) actually fires it; cronix trigger signs the request and POSTs back to your handler.

Examples

Runnable mini-apps, each one ~50 lines:

Example Stack
ts/examples/hono-app Hono β€” runs unchanged on Node, Bun, Cloudflare Workers
ts/examples/express-app Express + @awbx/cronix-adapter-express
ts/examples/fastify-app Fastify + @awbx/cronix-adapter-fastify
ts/examples/hand-rolled No framework β€” just node:http + verifyManifest/verifyTrigger
go/examples/go-app Go net/http server using pkg/cronsdk for HMAC verify

Each example has a README with the exact pnpm dev (or go run) command and a curl recipe to test end-to-end.

Backends

Backend What it writes Setup
crontab /etc/crontab lines with # cronix:owned markers docs/src/content/docs/backends/crontab.md
systemd-timer .timer + .service units in /etc/systemd/system docs/src/content/docs/backends/systemd.md
kubernetes CronJob + ConfigMap per job docs/src/content/docs/backends/kubernetes.md
aws-scheduler EventBridge Schedules β†’ cronix-trigger Lambda docs/src/content/docs/backends/aws.md

cronix tracks ownership inside each resource β€” it never touches lines, units, or objects it didn't create. Run alongside hand-edited entries safely.

Framework adapters (TypeScript)

For frameworks that don't speak Web Fetch natively, install the matching sibling adapter package. Each one exports a handle() that lifts any (req: Request) => Response | Promise<Response> into a framework-native handler:

// Express
import { handle } from "@awbx/cronix-adapter-express";
app.all("/.well-known/cron-manifest", handle((req) => cron.handle(req)));

// Fastify (rawBody installs a wildcard parser to keep bytes-as-sent)
import { handle, rawBody } from "@awbx/cronix-adapter-fastify";
rawBody(app);
app.all("/.well-known/cron-manifest", handle((req) => cron.handle(req)));

// Koa (mount before any body-parser middleware)
import { handle } from "@awbx/cronix-adapter-koa";
router.all("/.well-known/cron-manifest", handle((req) => cron.handle(req)));

// NestJS (Express by default β€” bootstrap with `bodyParser: false`)
import { handle } from "@awbx/cronix-adapter-nest";
app.use("/.well-known/cron-manifest", handle((req) => cron.handle(req)));

Hono, Bun, Workers, Vercel/Next.js, and Deno all serve a Web Request natively β€” no adapter needed; just call cron.handle(req) directly from your route.

Documentation

Project status

Area State
Spec RFC v1 frozen β€” see spec/RFC.md
Backends crontab, systemd-timer, kubernetes, aws-scheduler β€” all reconcile end-to-end
CLI init, validate, plan / diff, apply, drift, list, global-status, show, prune, history, trigger, version, completion
TypeScript SDK @awbx/cronix-sdk + 4 framework adapters, conformance-tested against shared spec vectors
Go SDK pkg/cronsdk β€” HMAC verify only, conformance-tested
Distribution Homebrew tap, deb / rpm / apk, Docker, npm

Contributing

cronix is open source under MIT β€” issues, discussions, and PRs are welcome. A few things worth knowing before you dive in:

  • The RFC is the product. Behavior changes are discussed and agreed before code lands. The protocol shape (manifest, signing, headers) is the contract; everything else is an implementation detail.
  • Both languages stay in lock-step. Manifest shape, header format, and signing scheme changes must land in TypeScript (@awbx/cronix-sdk) and Go (internal/manifest, internal/auth) in the same PR, with both passing the shared manifest-vectors.json and auth-vectors.json.
  • Conformance vectors are sacred. Adding or modifying one is a spec change.

Full dev setup, branch flow, and release process: CONTRIBUTING.md.

Quick paths to help if you're new:

  • File an issue about something that surprised you β€” bad error messages, missing docs, unclear flags. No issue is too small.
  • Add an example for a stack we don't yet cover (Bun-only, Cloudflare Workers, AWS Lambda app, etc.).
  • Port the SDK β€” Python and Ruby SDKs are wide open. The conformance vectors give you a green-light test suite.

License

MIT Β© Abdelhadi Sabani β€” see LICENSE.

About

No description, website, or topics provided.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors