Console
ScalWS Console: configuration workflow
The console never edits the active configuration in place. Every change is a transaction
over the configuration file the controller reads (controller.config in
controller.yaml), and the controller turns the file into a new generation.
Current ─clone─▶ Draft ─edit (raw or structured)─▶ Validate ─▶ Diff + impact ─▶ Confirm
─▶ Apply: validating → preparing → applying → health_check → complete
└──▶ rolled_back / failedDrafts
- A draft is a copy of the current file (or of a history entry) with the file’s SHA-256 at that moment (its base). Drafts live in the console’s memory and end when it restarts.
- Settings pages: every field of scalws.yaml and controller.yaml, generated from the configuration’s JSON Schema and grouped as Server, Listeners & TLS, Tuning, Security, Cache, Logging & metrics, Sites, PHP / FastCGI, Proxy & applications, Advanced and Adaptive Runtime (controller.yaml). Each input shows the active value; an absent key shows the effective default; Reset removes a key, Undo returns to the active value; a search box filters one section, “search all” every section. Edits are saved to the draft half a second after typing stops.
- Raw editor: the YAML text of the same draft, comments kept. Saving from the forms
rewrites the file canonically (comments and inline
{…}style are not kept), which is why review lists changed settings first and folds the text diff. - Secrets (password/secret/token keys, every value under
env:) appear as__REDACTED_n__. The originals stay in the draft on the server; a placeholder left in place keeps its value.
Validation
scalws-config: parsing and the same semantic validationscalwsdand the controller use (errors with their path, warnings).scalwsd --checkwith the draft (the controller’swebd_binary): certificates load, profiles and runtimes are prepared, nothing is bound or started.- Impact: listener additions, moves and removals, HTTP/3 on/off (UDP socket and QUIC demux), TLS changes, applications added/changed/removed, server sections changed. The controller remains the judge: a change that needs a restart is rejected by its reload and the current generation stays.
- Changed settings (path, before, after; secret values shown as “(secret)”), the text diff against the current file (redacted), and whether the file is still the draft’s base.
Apply
| State | What happens | On failure |
|---|---|---|
| validating | the file’s hash must equal the draft’s base (else conflict); the draft must change something and validate | nothing written |
| preparing | controller status read; a rollout in progress refuses the apply; the current file is recorded in history | nothing written |
| applying | new file written atomically (temporary file in the same directory, original owner and mode, fsync, rename, directory fsync); POST /reload | reload refused (bad listener, port in use, restart needed): previous file restored, generation unchanged → failed |
| health_check | until the new generation has as many active instances as desired, nothing starting or draining and no instance of an older generation active (timeout --health-timeout-secs, default 120 s); a controller rollback of that generation or failed instances end it early | previous file restored, POST /reload, wait for that generation → rolled_back |
| complete | new history entry with the generation |
One apply runs at a time. Each apply carries an idempotency key: repeating the request (browser retry) returns the same operation instead of applying twice. A browser that loses the connection can find the operation under Configuration → Operations; the operation continues on the server.
History and rollback
Every configuration the file had is kept in /var/lib/scalws-console/history/<seq>.yaml
with an index (time, actor, kind, hash, generation): baseline (found at console start),
apply, rollback (automatic restore), external (the file changed outside the
console). Restoring an entry is an apply of that text: a new generation, the same checks;
history is never rewritten. Automatic rollbacks by the console and by the controller are
shown on the Overview.
Concurrency between operators
- Optimistic: an apply whose base is not the current file is refused (
conflict); the review page offers a new draft from the current file. - Drafts belong to their creator (administrators may act on any).
- Changes made outside the console (an editor on the host, configuration management) are
detected by hash and recorded as
externalhistory entries.
controller.yaml
Drafts of controller.yaml follow the same flow with two differences: validation runs
scalws-controller check-config (CPUs per instance, min/max, thresholds), and apply
restarts the controller with the operator-configured --controller-restart-command
(instances keep serving and are adopted), then waits until the controller answers with its
generation settled. If it does not settle in time, the previous file is restored and the
controller restarted again. Without a restart command the validated file is written and
takes effect at the next restart (said so before and after applying).
Runtime actions (no file change)
Desired instance count (within instances.min..max), autoscaling mode and cache purge go
straight to the controller API, are confirmed in the UI with their impact, and are
audited. instances.*, CPUs and autoscaling thresholds are edited under Configuration →
Adaptive Runtime (controller.yaml, above).