| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| checksums-sha256.txt | 2026-05-13 | 370 Bytes | |
| runwisp-darwin-arm64.tar.gz | 2026-05-13 | 7.9 MB | |
| runwisp-darwin-x64.tar.gz | 2026-05-13 | 8.5 MB | |
| runwisp-linux-arm64.tar.gz | 2026-05-13 | 7.6 MB | |
| runwisp-linux-x64.tar.gz | 2026-05-13 | 8.3 MB | |
| README.md | 2026-05-13 | 16.7 kB | |
| v0.5.0 - Docs source code.tar.gz | 2026-05-13 | 1.9 MB | |
| v0.5.0 - Docs source code.zip | 2026-05-13 | 2.3 MB | |
| Totals: 8 Items | 36.5 MB | 0 | |
Added
-
Local CLI/TUI connects over a Unix socket — no password needed. The daemon now exposes
<datadir>/runwisp.sock(mode0600inside the0700data dir).runwisp,runwisp tui,runwisp list, andrunwisp execautomatically connect over the socket and bypass the password/JWT flow entirely. Access is gated by filesystem permissions and aSO_PEERCRED/LOCAL_PEERCREDcheck at accept time, so only the user that started the daemon can drive it locally. The browser launch-ticket flow keeps working: the TUI mints a ticket via the socket; the browser redeems it on127.0.0.1. Remote network clients (the Web UI, remote REST) still use the password + JWT cookie path; nothing changes for them. -
runwisp passwordprints the ephemeral password for a second-device login. When the daemon mints a fresh in-memory password at boot, the value never appears in a log line, the startup banner, or a TUI frame. Runrunwisp passwordfrom the same data dir to print it to stdout (pipe towl-copy/pbcopy/xclipto keep it out of scrollback). The TUI's Home view shows a masked••…field; press Enter to copy. The endpoint is Unix-socket only — it returns 403 to a TCP request even with a valid JWT, and 404 when the daemon is configured withRUNWISP_PASSWORD(operator-supplied passwords are never disclosed by the daemon). -
Download a run's full log from the Web UI and the TUI. The Web UI's run detail panel gains a Download log button next to the live-stream toggle; the TUI binds
dto the same action on the exec view. The download is the rotated-out segment plus the current segment as onetext/plainfile — what you'd get fromcat *.log.prev *.log, but in one click. The TUI download works over SSH and tmux, not just graphical sessions. -
runwisp validate <path>now uses the same loader as the daemon. A file the validator accepts is a file the daemon will accept — same parser, same rule set, same error messages. The success path prints✓and a summary (task / service / notifier counts plus the resolved scheduler timezone); the failure path prints each error with its TOML key and adid you mean…?hint where the migration is mechanical. Exits 0 on success, 1 on any error. -
TUI startup and Web UI header show the resolved scheduler timezone. Plain info, not a warning —
Europe/Bratislava (system)if the daemon picked it up from the host,(config)if[scheduler] timezonewas set explicitly. The same fields are surfaced on the health endpoint so a homebrewed reverse proxy or status check can read them. -
Inline notification targets in
notify_on_failure/notify_on_success. A token of the form"<id>:<target>"overrides the parent notifier's channel (Slack) or chat_id (Telegram) for that route only —notify_on_failure = ["slack-ops:#deploys"]reuses the credentials ofslack-opsbut posts to#deploys. Bare ids and the literal"inapp"keep working unchanged. Notifier ids are now disallowed from containing:(the new separator); rename any existing colon-bearing ids before upgrading.
Removed
-
Password and JWT secret are no longer written to disk (BREAKING). The daemon password now comes from
RUNWISP_PASSWORD(in-memory only) or is freshly generated every boot when the env var is unset. The JWT signing key is derived from the password via HKDF, salted by the per-install fingerprint, so a stableRUNWISP_PASSWORDkeeps browser sessions stable across restarts without persisting any secret. SetRUNWISP_PASSWORD(via a Docker secret, systemdLoadCredential=, or your sealed-secrets workflow) to keep network sessions stable; leave it unset and every restart rotates the password (and every JWT). Thepassword,jwt_secret, andenv_password_hashrows in the SQLiteconfig_entriestable are no longer read or written — pre-existing rows in upgraded data dirs are harmless leftovers and can be deleted by hand. -
data/passwordfile removed. Previously stored under the data dir, now never written. -
runwisp initis gone. Runningrunwispin a directory without arunwisp.tomlnow offers to scaffold a small starter for you (Create a starter with one example task? [Y/n]) and continues straight into the TUI. The previous flow — a separaterunwisp initcommand that wrote a 60-line file dominated by a commented schema reference, plus a "Generated password" that the daemon never actually used — is replaced by one prompt and a smaller, sharper starter file. Headless launches (runwisp daemon, Docker, systemd) skip the prompt; ifrunwisp.tomlis missing they now exit with an error instead of falling back to a demo task. -
runwisp run-taskis gone.runwisp exec <task>now handles both modes: it auto-detects whether a daemon is running on the data dir and dispatches through its REST API (streaming the live log to your terminal) or, with no daemon up, runs the task in-process. Both paths produce the same visible output and propagate the run's exit code. Use--daemonto require a running daemon, or--standaloneto require none. -
runwisp.example.tomlis gone. The starterrunwisp.tomland the docs site are the single reference; the parallel annotated example file is no longer maintained.
Changed
replica_indexis nowinstance_indexeverywhere. The Run JSON field, the SQL column, the Web UI rendering, and the TUI labels all use the new name. Operators upgrading an existing data directory have the column renamed in place by an idempotent migration; fresh installs see no migration. The terminology now matchesinstanceson[services.*].append_notifiersis nowglobal_notifiers. The same precedence applies (["inapp"]default; explicit[]silences the bell) but the name finally reflects what the key does: define the channels that fire for every failure. TOML using the old key is rejected at load time.queue_sizeis gone from[notify]. The notify ingress and per-action worker buffers are now fixed internal defaults. Configs that still setqueue_sizeare rejected as an unknown key.history_keepdefaults to1024andhistory_keep_fordefaults to90d. The bell history is bounded out of the box — leave both unset and you get a sensible cap. Override either explicitly to lift or lower the limits.- Services can be stopped permanently from the Web UI and TUI. A new Stop Service button (
sin the TUI) cancels every instance and tells the supervisor not to refill the slots. Restart Service (rin the TUI) brings everything back. The stop flag is in memory only — restart the daemon and the service comes back up on its own. -
Run / Stop / Restart buttons are contextual. On a service execution the Web UI and TUI show Stop while the service is up and Restart once it's been stopped. On a task that isn't launchable (API-trigger disabled, max concurrency reached) the Run Task button is now greyed out with a tooltip instead of failing on click.
-
max_catch_up_runsno longer capped at 10000. Pick any positive integer that suits your workload — the daemon trusts you. -
Run log file paths now use UTC timestamps. A host that flips DST (or relocates) no longer shifts where new runs land on disk.
-
parallelism→max_concurrenton[tasks.*](BREAKING). The new name matches what the field actually means: the maximum number of overlapping runs of a task. The field is removed entirely from[services.*]— replica count isinstances, and there is no other concurrency knob on a service. Configs that still spellparallelismare rejected at config load with adid you mean 'max_concurrent'?hint. -
graceful_stopgoverns every kill path. New per-task field, default"5s". When the daemon needs to stop a running task —timeout,on_overlap = "terminate", manual stop, or daemon shutdown — it sendsSIGTERMto the task's process group, waits up tograceful_stop, thenSIGKILLs the survivors. Set"0s"to keep the previous abrupt-kill behaviour. The previous unconditional immediate-SIGKILLis gone; long-running tasks get a chance to flush their state. -
Daemon shutdown is bounded by
[daemon] shutdown_timeout. New top-level field, default"10s"(matches Docker's stop grace period). OnSIGTERMthe daemon fans out per-task shutdown in parallel, waits up toshutdown_timeoutfor everything to settle, thenSIGKILLs the rest and exits. Boot emits a warning if any task'sgraceful_stopexceedsshutdown_timeout, since that task can never finish its grace window during a daemon stop. Unfinished runs are recorded withend_reason = "daemon_stopped". -
Service restart backoff is configurable. New
[defaults] backoff_reset_after = "60s"plus per-service override. A replica that runs at least this long resets its restart counter, so a service that finally stabilises returns to fast-restart behaviour on its next failure. Replaces the previously hardcoded 60-second threshold. -
DST fall-back days no longer double-fire On the autumn transition,
0 2 * * *used to fire twice — once before the rewind, once after. The scheduler now dedupes by wall-clock minute: a firing whose minute matches the previous one is recorded withend_reason = "dst_skipped"and the underlying command is not run. Spring-forward firings continue to fire at the next valid local time. -
[scheduler] timezonedefaults to the host's system timezone. When the field is omitted, the daemon picks up the host's IANA zone and falls back toUTConly if it can't be detected. The resolved zone is surfaced in the TUI banner and the Web UI header so the operator can see what's in effect. Per-tasktimezoneoverrides still work. -
Numeric config fields take literal values; no more keywords or sentinels (BREAKING). Every numeric field —
keep_runs,keep_for,log_max_size,max_concurrent,queue_max,max_catch_up_runs,retry_attempts,instances— accepts an integer (or duration / size). The"unlimited"and"inherit"keywords are gone; the magic-int sentinels (0= inherit,-1= unlimited) are gone; omit the field to inherit the default. Each field has a hard internal cap (max_concurrent1024;queue_maxandmax_catch_up_runs10 000;retry_attempts100;keep_runs1 000 000;instances1–64). Out-of-range values are rejected at config load with adid you mean N?hint. -
log_on_full = "kill_task"records killed runs aslog_overflow. A new end reason that names exactly what happened, instead of genericfailedorstopped. Retries,notify_on_failure, and dashboards still treat it as a failure. -
Log index sidecars (
.idx,.tidx) are no longer created for short runs. A run that emits fewer than 1 024 lines now leaves no sidecar files on disk; the index is only opened when there's actually something to index. Arunwisp.tomlfull of small tasks no longer creates 4 × N sidecar files for runs that don't need them. -
Schema and CLI tidy-up before 1.0 (BREAKING). A coordinated rename pass that removes accumulated naming inconsistencies. None of these change what the daemon can do — only how you spell it.
retry_backoffandrestart_backoffnow share the same enum:constant/linear/exponential. The old""(constant) and"none"(constant) values are rejected. Services gainlinearas a valid restart curve.- The CLI is reorganised around one verb for ad-hoc runs:
runwisp exec <task>auto-detects whether a daemon is running on the data dir and either dispatches through its REST API (streaming the live log to your terminal) or runs in-process when no daemon is up. The oldrunwisp trigger,runwisp run, andrunwisp run-taskare removed;runwisp daemonremains the headless launcher for systemd/Docker. -
Notification model:
[notify] disable_inapp = trueis replaced by[notify] append_notifiers = []. The same setting now also lets you redirect the zero-config catch-all (e.g.append_notifiers = ["slack-ops"]to make every failure page Slack instead of, or alongside, the in-app bell). Per-tasknotify_on_failure/notify_on_successcontinue to work; the contents ofappend_notifiersare appended to each per-task list (deduped against the explicit ids), so the bell keeps lighting up unless you opt out. -
Log streaming redesigned around absolute line numbers (BREAKING). The REST and SSE log surface now speaks lines, not bytes. New endpoints:
GET /api/tasks/{name}/runs/{id}/log— JSON page of{n, ts, stream, text}entries withfrom/limitquery parameters (fromaccepts negative values for tail-from-end).GET /api/tasks/{name}/runs/{id}/log/raw— concatenates the rotated-away segment and the current segment astext/plainfor download /cat/grep.GET /api/tasks/{name}/runs/{id}/log/stream— line-numbered SSE withevent: line | rotated | dropped | done. Each event carries anid:so EventSource's nativeLast-Event-IDresume works on reconnect. The previous/log-streamendpoint and thestart_line/end_line/tailquery shape on/logare removed. A single SSE call now serves "tail then follow" via?from=-1000; lines longer than 64 KB are split withcontinued: trueon segments 2..N.
Fixed
- Outbound notifiers (Slack, Telegram) now coalesce by default. A flapping task used to translate to one Slack message per failure — exactly the surface that pages humans and gets rate-limited by the provider. Outbound deliveries now share the in-app coalescer's fingerprint (task name + event kind + end reason): the first event in
coalesce_window(default1h) is delivered immediately, repeats are suppressed, and either the Nth event (occurrence_ring, default10) or a window-close summary fires withcoalesced_countso the operator still sees the rhythm. Set[notify] coalesce_outbound = falseto opt out. - Skipped runs are now recorded as
end_reason = "skipped". A run thaton_overlap = "skip"rejects used to be persisted asend_reason = "failed", which made a* * * * *health probe with chronic overlap pose as a real failure to dashboards, retries, andnotify_on_failure(Slack/Telegram). The newskippedend-reason is terminal, distinct fromfailed/stopped/crashed, never triggers retries, and never fires failure notifications. RUNWISP_PASSWORDis no longer written back todata/password. Operators who pass the password via Docker secrets, systemdLoadCredential=, or sealed-secrets do so specifically to keep credentials off disk; the daemon used to mirror the env var intodata/passwordon every start, defeating that intent. The env var is now in-memory only. The TUI and CLI must obtain the password the same way (env var or--password) when nodata/passwordfile is present — falling back to the file from a different shell would have been misleading anyway.notify.history_keep_fornow accepts day and week units. The docs and the example config advertised"30d"for in-app notification retention, but the parser rejected anything beyond Go's stock duration syntax (h/m/s). The field now uses the same extended parser askeep_for, sodandwwork everywhere a duration sets a retention window.min_free_spaceno longer silently overrideslog_on_full = "kill_task". When the disk-pressure threshold trips during a run, the daemon now honours the task's overflow policy:kill_taskcancels the run (loud failure, as the operator chose);drop_newanddrop_oldkeep running but stop logging. In all cases the daemon raises a newlog.disk_pressurenotification (severitywarn, fired once per run, withfree_bytes/min_free_bytes/killed_taskin the payload) so the silenced output is never invisible.- SSE log stream no longer cuts off large outputs. When a run produced more than 1 MB of un-emitted log data before finishing, the SSE stream would emit a single 1 MB chunk and immediately send
done, discarding the rest. The stream now drains the full log file before signalling completion. - TUI and Web UI open long logs at the tail. Opening a finished execution with a large log used to replay the entire file from the start, making the operator wait for the viewport to catch up. Both UIs now land at the tail in a single round-trip and lazily load older content when the user scrolls up.