Feature list
Everything Walis does today, grouped by what you would go looking for. Each line is a feature that exists in the shipping build — not a roadmap.
Jobs and scripts
| Feature | Notes |
|---|---|
| Two authoring modes | Script mode is a shell buffer you edit directly. Flow mode builds the script from actions. |
| 29 built-in actions | Eight categories: general, files, Git, Node, .NET, containers, deployment, checks. See Actions. |
| Custom actions | A parameter declaration plus a shell template, stored as JSON. No code. |
| Compiled, not interpreted | A flow compiles into Jobs.Command on save, so runners never need to know actions exist. |
| AI script review | Sends the script to your configured provider and reports back what looks wrong. |
| Import and export | Jobs and custom actions as JSON, matched by name, with an overwrite switch. Identifiers and build history stay put. |
Switching from flow mode back to script mode keeps the compiled script; switching the other way restores the steps. Manual edits made in script mode are not preserved across a switch back to flow.
Builds and steps
| Feature | Notes |
|---|---|
| Per-step status | Every step keeps its own status, output, exit code and duration. |
| Independent shells | Steps share a working directory but not environment variables. Append KEY=VALUE to $WALIS_ENV_FILE to pass values on. |
| Parallel groups | Mark a step "run alongside the previous step"; consecutive marks form one group run with Task.WhenAll. |
| Build matrix | {"NODE":["20","22"],"OS":["mac"]} fans out into one build per combination, capped at 50. |
| Build cache | Named paths are saved on success and restored before the next run — after the workspace is cleaned, not before. |
| Artifacts | Copied out on success, capped at 200 files / 64 MB, downloadable from the log window. |
| Live output | stdout and stderr interleave in real order; the log window re-fetches every two seconds and follows the tail until you scroll up. |
| Timeout | A whole-build budget. Each step receives whatever time is left. |
| Retention | Builds, artifacts and audit entries share one retention setting, swept hourly. |
continueOnError on an action lets the build carry on past a failure; without it, later steps are marked Skipped.
Triggers
- Manual — a button on the job list and job detail.
- Push —
POST /api/webhooks/pushaccepts GitHub, GitLab and a plain{repository, branch}body, matched against jobs by branch and normalised repository address. Signatures verified viaX-Hub-Signature-256,X-Gitlab-TokenorX-Walis-Tokenonce a webhook secret is set. - Cron — five-field expressions evaluated once a minute in the configured time zone.
All three can be enabled on the same job.
Runners
| Feature | Notes |
|---|---|
| Built-in local runner | Runs in the API process. Concurrency comes from the system setting. |
| Remote agents | walis-runner polls, claims work, and reports back. No runtime to install — it is a self-contained binary. |
| Shared step loop | BuildStepSequence is the same code locally and remotely; only the sink differs (database vs HTTP). |
| Labels and slots | Free-form labels, plus a slot count per runner. |
| Heartbeat | Reported every minute; runners silent for five minutes are marked offline and can raise a notification. |
Databases and high availability
| Provider | Multiple nodes | Notes |
|---|---|---|
| SQLite | No | The default. One file, nothing to run. |
| PostgreSQL | Yes | |
| MySQL / MariaDB | Yes | |
| SQL Server | Yes | Azure SQL included. |
Point several API nodes at one server database and they share the work:
- Atomic build claiming — two nodes can race for the same queued build; exactly one wins the
Queued→Runningtransition. - Lease-based leader election — cron evaluation, offline detection, retention cleanup and scheduled backups run on one node at a time. Leases expire, so a dead holder is replaced without a separate coordination service.
- Cross-node polling — a build queued on one node is picked up by whichever node has capacity.
- Per-node recovery — a restarting node recovers only its own interrupted builds.
GET /health reports the provider, the node name and whether the deployment is clustered. Migrating from SQLite is a backup and a restore, because the archive is engine-agnostic. See High availability.
Isolation and build safety
Builds run as the same user as the service, so these are the defences that matter.
On every platform:
- Allowed working directories — when the list is non-empty, a build whose working directory is outside it fails immediately rather than running somewhere unexpected.
- Environment scrubbing —
ConnectionStrings__*is removed from the build environment, so a build cannot discover where the database lives. - Workspace cleaning — clears directory contents only, and refuses disk roots, home directories and suspiciously shallow paths.
On macOS, additionally:
- Sandbox — with
IsolateBuildson (the default), each step is wrapped insandbox-execand cannot read or write the Walis data directory or any path you list. This layer is macOS-only:sandbox-exechas no equivalent that Walis uses on Windows or Linux, and on those platforms the setting simply has no effect. - Symlink resolution — sandbox paths are resolved segment by segment, so rules on
/tmpand/var/foldersactually bite.
Neither layer is real isolation — same user, same machine. They stop a build script from casually reading the database or credentials. For stronger boundaries, run builds on a dedicated runner under its own account, or in a container your script starts.
Access control
| Feature | Notes |
|---|---|
| Optional sign-in | Off by default. With it off, everyone is treated as an administrator. |
| Password storage | PBKDF2-SHA256. Sessions live in the database. |
| One-time passwords | TOTP with backup codes; can be required for all accounts. |
| Roles | viewer, operator, administrator. |
| Per-job permissions | Each job has a view role and a run role. |
| Quiet 404s | A job you cannot see returns 404, not 403 — the identifier does not confirm the job exists. |
| API keys | walis_<prefix>_<secret>, PBKDF2-hashed, shown once. A key cannot mint another key. |
Plugins
Action plugins run in Jint, a pure-C# JavaScript interpreter, and return a shell snippet rather than behaviour — so you can read what a plugin will run before installing it.
- Declared capabilities only — network access is limited to named domains; secrets can only be injected into headers via
withSecret, never read by the plugin. - Resource ceilings — 10 s timeout, 8 MB memory, 500 000 statements, 64 recursion levels.
- AI drafting — generated plugins have permissions cleared, type and version forced, and are never auto-installed.
- Administrator only — installing a plugin means adding code that will run.
Notifications
Email, Slack (incoming webhook) and a custom JSON webhook, each independently switchable and testable. Five events: build failed, build recovered, every successful build, build timed out, runner offline. Quiet hours suppress everything except failures. Delivery history is kept.
AI assistance
Claude, Gemini and Codex. Claude uses the official Anthropic SDK; the other two use their REST endpoints. Used for script review, build failure analysis and plugin drafting. Every call is logged with provider, model, purpose, duration, token usage and result — prompt content is never stored.
Backup and restore
- Scheduled by cron, encrypted with AES-256-CBC in OpenSSL's
Salted__format — restoring does not require Walis. - Packs the database (
VACUUM INTO), artifacts, extra paths you list, and optionally PostgreSQL viapg_dump. - Destinations: local path, S3-compatible (self-signed SigV4), Dropbox, Google Drive, OneDrive. None shell out to an external binary.
- Restore is database-agnostic and can target a different database engine than the source.
A passphrase is mandatory once a remote destination exists, because the archive contains password hashes and credentials.
Observability
- Dashboard — active jobs, 7-day success rate, running count, failure count, build trend, recent builds, runner load and per-job success rate.
- Prometheus —
GET /metrics, outside/api, so scrapers need no key. - Audit log — jobs, builds, runners, environment variables, settings, notifications and translations, sharing the retention setting.
Integrations
- REST API for everything the UI does.
- MCP served at
/mcpby the API itself, so an agent needs only a URL and a key — nothing installed locally.Source/Walis.Mcpis a stdio bridge for clients that cannot speak HTTP MCP. - Portability — export and import jobs and custom actions as reviewable JSON.
Localization
Traditional Chinese is the source language; English and Japanese ship as dictionaries, overlaid at load time with any overrides you set. Translations are editable in the app, importable and exportable as JSON, with an auto-translate pass for missing entries.
Deployment
deploy/release.sh publishes self-contained binaries — the target machine needs no .NET — for Windows (x64, arm64), macOS (Intel, Apple Silicon) and Linux (x64, arm64, 32-bit arm for Raspberry Pi, and musl/Alpine for both architectures). Each target gets an archive containing the API with the admin UI built in, plus the runner, and the release ships a SHA256SUMS.txt.
On macOS, deploy/install.sh registers two LaunchDaemons: the service (running as an ordinary account, not root) and a restarter watching a trigger file. On other platforms, run the binary under whatever init system you already use. The built-in self-update job pulls, builds, swaps a symlink and touches that file — restarts happen outside the build's process tree, so the build survives long enough to record its own result.
The API serves the admin UI from wwwroot, so the interface and the API share an origin and a port.