runwisp

module
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: GPL-3.0, Apache-2.0

README

RunWisp logo

RunWisp

See what ran, when, why it failed, and what it printed.

RunWisp is an open-source, self-hosted cron job manager and process supervisor. It ships as a single Go binary with a built-in web dashboard, terminal UI, and REST API.

runwisp.com · Documentation · Install · Quick Start · Compare

Latest Release CI Go Reference Coverage Security Rating GitHub Stars


RunWisp replaces crond and supervisord when you want scheduled jobs and long-running services in one place. You describe both in runwisp.toml; the daemon runs them without needing Python, Node.js or an external database.

Want a look first? Run bunx runwisp demo to try RunWisp with demo data and nothing to configure.

Every run leaves evidence behind: its exit code, duration, timestamps, and stdout/stderr. You can inspect history, follow logs, and trigger or stop tasks from the web dashboard, terminal UI, or REST API. Task definitions stay on disk in TOML.

RunWisp web dashboard showing a task run, live logs, and failure details

Trigger a run, follow its output, and inspect failures from the built-in dashboard.

Install

Install the latest binary on your PATH:

curl -fsSL https://get.runwisp.com | sh

On macOS or Linux with Homebrew:

brew install runwisp/tap/runwisp

You can also use the npm package, which runs the prebuilt Go binary:

bunx runwisp
bun add -g runwisp     # or: npm install -g runwisp

Tarballs are available from runwisp.com/releases. For Docker:

docker run -d --name runwisp -p 9477:9477 \
  -e RUNWISP_PASSWORD=change-me \
  -v ./runwisp:/etc/runwisp \
  -v runwisp-data:/var/lib/runwisp \
  runwisp/runwisp:latest

On first start, RunWisp writes a starter config to ./runwisp/runwisp.toml. The image supports amd64 and arm64, with Alpine and Debian variants. See the Docker guide for tags, environment variables, and volumes.

To build from source, see Getting started in the contributing guide.

Quick Start

Run:

runwisp

If the current directory has no runwisp.toml, RunWisp offers to create an example config file. Press Enter and it will write an example task, start the daemon, and open the terminal UI. The Home page shows the web dashboard URL and a generated password; press Enter on the password row to copy it.

Set RUNWISP_PASSWORD env variable if you want to choose the password yourself. For a trusted local setup, RUNWISP_AUTH=off disables authentication explicitly.

Running headless? Use runwisp daemon in cron, systemd, Docker, CI, or a piped SSH session. It doesn't prompt, and a missing config exits non-zero. To start RunWisp after a reboot, use runwisp service install for systemd or launchd.

Replace the starter task with your own jobs and services:

[tasks.backup-db]
cron       = "0 2 * * *"
jitter     = "30m"  # delay by up to 30 mins if other jobs are running
on_overlap = "kill" # if yesterday's job is still running, kill it
keep_runs  = 30     # remove older runs
run = "pg_dump mydb | gzip > /backups/mydb-$(date +%F).sql.gz"

[services.worker]
instances    = 3                           # keep three copies running
env          = { NODE_ENV = "production" } # shown in API/UI
secrets_file = "/etc/runwisp/worker.env"   # never leaked to the API/UI
run          = "node /app/worker.js"

Moving from cron, supervisord, or Docker Compose

RunWisp can start from the configuration you already have:

RunWisp terminal UI showing tasks, live output, and run controls over SSH

The terminal UI keeps task status, logs, history, and controls in the SSH session.

Features

Scheduling and supervision

  • Standard cron schedules, missed-run handling, retries, timeouts, and queue, skip, or kill overlap policies
  • Long-running services with multiple instances, restart backoff, crash recovery, and graceful shutdown. 500k lines/day, for months without interruption, RunWisp can handle it and stream it to you.
  • Typed parameters for manual runs, passed as arguments or environment variables through UI/API/CLI

Run history and logs

  • Exit code, duration, timestamps, and status persisted in embedded SQLite
  • Live stdout/stderr streaming in the web UI and terminal UI
  • Per-task log rotation and failure notifications through Slack, Discord, Telegram, ntfy, Gotify, Pushover, email, webhooks, or the in-app inbox

Day-to-day operation

  • One binary with the Svelte web dashboard and SQLite embedded
  • TOML configuration that can be versioned and reviewed like code
  • Validate-first reloads, recoverable state after a crash or power loss, and interrupted-run reporting on restart
  • Local-first operation with no signup, account, or required network connection. The only outbound call by default is an anonymous update check, turned off with check_updates = false

Comparison

crond systemd timers supervisord RunWisp
Cron scheduling Yes Yes No Yes
Process supervision No Yes Yes Yes
Web dashboard No No Basic HTML Built in
Terminal UI No No No Built in
Remote interface No D-Bus XML-RPC REST API
Live log streaming No journalctl -f Tail only Web UI, TUI, and SSE
Concurrency policies No Overlap prevention No Queue, skip, or kill
Failure alerts No OnFailure= unit Event listener Slack, Discord, Telegram, ntfy, Gotify, Pushover, email, webhook
Execution history No journalctl No SQLite, browsable in the UI
Runtime dependencies libc systemd Python None
Configuration crontab Unit files INI files One TOML file

The API and user interfaces can inspect tasks and trigger or stop them. Task definitions cannot be modified remotely. runwisp.toml remains the source of truth.

How RunWisp is made

RunWisp is built by PoppyCake, s.r.o. Most of the code isn't written by hand, so we lean hard on review and tests. Review goes down to the small stuff, like whether a helper deserves to exist or should just be three lines inline, because that's how the code stays small and quick to read. The daemon has more lines of tests than code, including awkward cases like a daily job scheduled inside the hour that daylight saving skips.

Careful review and good tests still aren't a guarantee. If we missed something, open an issue and we'll fix it.

Richard Popelis, lead developer

License

The RunWisp daemon and web UI are licensed under GPL-3.0-or-later. Basically, do what you want but if you distribute a modified version, you must keep it open under the same license. See LICENSE.

Shared libraries under packages/ use Apache-2.0 instead. See LICENSE-APACHE and the license file in each package.


Directories

Path Synopsis
apps
runwisp/cmd/e2e-fake-daemon command
Command e2e-fake-daemon impersonates the daemon for a single execution against a live station stack.
Command e2e-fake-daemon impersonates the daemon for a single execution against a live station stack.
runwisp/internal/afterfunc
Package afterfunc is the injectable timer seam shared by components that arm cancellable one-shot timers (the jitter gate, notification coalescing).
Package afterfunc is the injectable timer seam shared by components that arm cancellable one-shot timers (the jitter gate, notification coalescing).
runwisp/internal/autostart
Package autostart wires the daemon into the host's init system (systemd user unit on Linux/WSL, launchd LaunchAgent on macOS).
Package autostart wires the daemon into the host's init system (systemd user unit on Linux/WSL, launchd LaunchAgent on macOS).
runwisp/internal/autostart/autostartfake
Package autostartfake holds an in-memory autostart.Installer for tests of code that drives one (cutover, the service and takeover commands).
Package autostartfake holds an in-memory autostart.Installer for tests of code that drives one (cutover, the service and takeover commands).
runwisp/internal/autostart/autostarttest
Package autostarttest holds test doubles for the autostart seams.
Package autostarttest holds test doubles for the autostart seams.
runwisp/internal/backoff
Package backoff computes jittered exponential retry delays.
Package backoff computes jittered exponential retry delays.
runwisp/internal/bootid
Package bootid names the current machine or container boot, so a run_on_start = "boot" task can tell a reboot from a daemon restart.
Package bootid names the current machine or container boot, so a run_on_start = "boot" task can tell a reboot from a daemon restart.
runwisp/internal/chap
Package chap holds the daemon's challenge-response computation — the one formula every responder must agree on byte-for-byte: the Go server that verifies a login, the Go CLI that logs into a remote daemon, and the browser (which re-implements Response in WebCrypto, guarded by a shared test vector).
Package chap holds the daemon's challenge-response computation — the one formula every responder must agree on byte-for-byte: the Go server that verifies a login, the Go CLI that logs into a remote daemon, and the browser (which re-implements Response in WebCrypto, guarded by a shared test vector).
runwisp/internal/clilog
Package clilog owns how the RunWisp CLI and daemon write logs: slog handler selection (level, text/json, timestamps) and TTY-aware color gating for the styled banner.
Package clilog owns how the RunWisp CLI and daemon write logs: slog handler selection (level, text/json, timestamps) and TTY-aware color gating for the styled banner.
runwisp/internal/composespec
Package composespec is the only consumer of github.com/compose-spec/compose-go/v2 in the codebase.
Package composespec is the only consumer of github.com/compose-spec/compose-go/v2 in the codebase.
runwisp/internal/configedit
Package configedit writes RunWisp's config files.
Package configedit writes RunWisp's config files.
runwisp/internal/crashguard
Package crashguard turns a panic in a long-lived daemon goroutine into the daemon's ordinary fatal-shutdown path instead of an unrecovered runtime crash.
Package crashguard turns a panic in a long-lived daemon goroutine into the daemon's ordinary fatal-shutdown path instead of an unrecovered runtime crash.
runwisp/internal/cronprobe
Package cronprobe answers one question about the machine: is a system cron daemon live, and in what sense.
Package cronprobe answers one question about the machine: is a system cron daemon live, and in what sense.
runwisp/internal/cronspec
Package cronspec is the single definition of the cron grammar RunWisp accepts.
Package cronspec is the single definition of the cron grammar RunWisp accepts.
runwisp/internal/cutover
Package cutover answers one question: what would it take for RunWisp to become this box's only scheduler, and is any of it impossible?
Package cutover answers one question: what would it take for RunWisp to become this box's only scheduler, and is any of it impossible?
runwisp/internal/demo
Package demo provides the embedded "Acme Notes" demo configuration and a seeder that populates a throwaway data directory with believable historical runs.
Package demo provides the embedded "Acme Notes" demo configuration and a seeder that populates a throwaway data directory with believable historical runs.
runwisp/internal/importer
Package importer converts existing crontab and supervisord configuration into an annotated runwisp.toml.
Package importer converts existing crontab and supervisord configuration into an annotated runwisp.toml.
runwisp/internal/logsearch
Package logsearch performs on-demand substring or regex search across the on-disk log files of a task's runs.
Package logsearch performs on-demand substring or regex search across the on-disk log files of a task's runs.
runwisp/internal/netguard
Package netguard is the single SSRF gate shared by every outbound request the daemon makes on behalf of an untrusted peer: HTTP task execution (internal/executor) and control-plane log uploads (internal/station/logarchive).
Package netguard is the single SSRF gate shared by every outbound request the daemon makes on behalf of an untrusted peer: HTTP task execution (internal/executor) and control-plane log uploads (internal/station/logarchive).
runwisp/internal/notify
Package notify is the in-process notification subsystem: it consumes run lifecycle events from the daemon's event bus, routes them through configurable predicates, renders provider-specific messages, and surfaces outbound delivery failures back as in-app notifications.
Package notify is the in-process notification subsystem: it consumes run lifecycle events from the daemon's event bus, routes them through configurable predicates, renders provider-specific messages, and surfaces outbound delivery failures back as in-app notifications.
runwisp/internal/notify/channel
Package channel groups outbound notify channel implementations and the build-from-spec factory.
Package channel groups outbound notify channel implementations and the build-from-spec factory.
runwisp/internal/notify/channel/inapp
Package inapp is the in-app notification sink: it ingests events from the notify dispatcher (and synthesized delivery-failure events from the failure-sink path), runs them through the Coalescer, persists rows, and fans out via the Hub to TUI / Web UI subscribers.
Package inapp is the in-app notification sink: it ingests events from the notify dispatcher (and synthesized delivery-failure events from the failure-sink path), runs them through the Coalescer, persists rows, and fans out via the Hub to TUI / Web UI subscribers.
runwisp/internal/notify/channel/sendmail
Package sendmail implements the local-MTA notify channel: it composes a plain-text RFC 5322 message and pipes it to the host's sendmail binary, which is how crond has always delivered MAILTO output.
Package sendmail implements the local-MTA notify channel: it composes a plain-text RFC 5322 message and pipes it to the host's sendmail binary, which is how crond has always delivered MAILTO output.
runwisp/internal/notify/channel/smtp
Package smtp implements the email (SMTP) notify channel.
Package smtp implements the email (SMTP) notify channel.
runwisp/internal/notify/channel/telegram
Package telegram implements the Telegram bot notify channel.
Package telegram implements the Telegram bot notify channel.
runwisp/internal/notify/channel/webhook
Package webhook implements a generic HTTP webhook notify channel.
Package webhook implements a generic HTTP webhook notify channel.
runwisp/internal/notify/coalesce
Package coalesce wraps an outbound notify.Channel (Slack, Telegram, …) with fingerprint-based coalescing so a flapping task doesn't translate to one outbound delivery per failure.
Package coalesce wraps an outbound notify.Channel (Slack, Telegram, …) with fingerprint-based coalescing so a flapping task doesn't translate to one outbound delivery per failure.
runwisp/internal/notify/configload
Package configload turns a parsed config.NotifyConfig into the runtime objects the notify.Service needs: channel.NotifierSpec values and compiled notify.Rule predicates.
Package configload turns a parsed config.NotifyConfig into the runtime objects the notify.Service needs: channel.NotifierSpec values and compiled notify.Rule predicates.
runwisp/internal/notify/kinds
Package kinds is a leaf package containing only the canonical string values for notification event kinds.
Package kinds is a leaf package containing only the canonical string values for notification event kinds.
runwisp/internal/notify/render
Package render produces provider-specific message bodies from notify.Event values via Go text/template.
Package render produces provider-specific message bodies from notify.Event values via Go text/template.
runwisp/internal/notify/testutil
Package testutil holds fakes used across notify subpackages.
Package testutil holds fakes used across notify subpackages.
runwisp/internal/procstat
Package procstat samples the CPU and memory use of running shell runs, one process group per run.
Package procstat samples the CPU and memory use of running shell runs, one process group per run.
runwisp/internal/proxycidr
Package proxycidr normalises and validates trusted-proxy CIDR entries.
Package proxycidr normalises and validates trusted-proxy CIDR entries.
runwisp/internal/runlog
Package runlog emits one concise slog line per run lifecycle transition for the headless daemon.
Package runlog emits one concise slog line per run lifecycle transition for the headless daemon.
runwisp/internal/runtime/health
Package health decides, from a service's [services.*.health_check] probe, when one live instance is healthy and when it has to be stopped as unhealthy.
Package health decides, from a service's [services.*.health_check] probe, when one live instance is healthy and when it has to be stopped as unhealthy.
runwisp/internal/runtime/jitter
Package jitter computes deterministic per-task start slots within their jitter windows.
Package jitter computes deterministic per-task start slots within their jitter windows.
runwisp/internal/runtime/retry
Package retry holds pure policy functions for the runtime's retry and service-restart loops.
Package retry holds pure policy functions for the runtime's retry and service-restart loops.
runwisp/internal/runtime/services
Package services owns per-task supervisor state for service kinds: which instance slots are currently live and how many consecutive short-lived exits each slot has accumulated.
Package services owns per-task supervisor state for service kinds: which instance slots are currently live and how many consecutive short-lived exits each slot has accumulated.
runwisp/internal/server/auth
Package auth implements the daemon's CHAP login + session-token flow.
Package auth implements the daemon's CHAP login + session-token flow.
runwisp/internal/server/logstream
Package logstream drives a single SSE connection to a run's log.
Package logstream drives a single SSE connection to a run's log.
runwisp/internal/station/logarchive
Package logarchive uploads a single gzipped log file to a signed PUT URL supplied by the control plane in the dispatch payload.
Package logarchive uploads a single gzipped log file to a signed PUT URL supplied by the control plane in the dispatch payload.
runwisp/internal/testutil/fakeclock
Package fakeclock holds the hand-driven time fakes.
Package fakeclock holds the hand-driven time fakes.
runwisp/internal/textutil
Package textutil holds small string helpers shared across the daemon and CLI, kept dependency-free so any package can import it.
Package textutil holds small string helpers shared across the daemon and CLI, kept dependency-free so any package can import it.
runwisp/internal/tlscert
Package tlscert generates and persists the daemon's self-signed TLS certificate.
Package tlscert generates and persists the daemon's self-signed TLS certificate.
runwisp/internal/tui/keys
Package keys is the single source of truth for the TUI's keyboard help text.
Package keys is the single source of truth for the TUI's keyboard help text.
runwisp/internal/tui/uikit
Package uikit holds the shared styles, colours, message types, and helper functions used across every TUI view subpackage.
Package uikit holds the shared styles, colours, message types, and helper functions used across every TUI view subpackage.
runwisp/internal/tui/views/logpane
Package logpane is the scrollable log viewer shared by the exec-view and debug pages.
Package logpane is the scrollable log viewer shared by the exec-view and debug pages.
runwisp/internal/tui/views/logsearch
Package logsearch is the TUI overlay for the task-wide log-search feature.
Package logsearch is the TUI overlay for the task-wide log-search feature.
runwisp/internal/update
Package update polls concierge.runwisp.com for the latest published release and reports whether the running daemon is out of date.
Package update polls concierge.runwisp.com for the latest published release and reports whether the running daemon is out of date.
runwisp/internal/version
Package version exposes the daemon's release version.
Package version exposes the daemon's release version.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL