# scalws-controller

URL: https://scalws.com/docs/reference/programs/scalws-controller/
Source: `docs/reference/programs/scalws-controller.md` in the ScalWS repository

`scalws-controller` is the Adaptive Runtime controller: it starts, activates, drains and supervises `scalwsd` instances and never handles client traffic itself.

```text
scalws-controller [--socket PATH] <COMMAND>
```

Linux only. On other platforms it exits with
`scalws-controller: Linux only (SO_REUSEPORT, /proc, signals)`. Every command accepts
`--help`; `scalws-controller --version` prints the version.

The controller's own configuration is `controller.yaml`, see the
[controller.yaml reference](https://scalws.com/docs/reference/controller/) and
[Adaptive Runtime](https://scalws.com/docs/adaptive-runtime/).

## Global option

| Option | Default | Meaning |
|---|---|---|
| `--socket <PATH>` | `/run/scalws-controller/control.sock` | Control socket used by the client commands (`status`, `scale`, `reload`, `autoscaling`, `purge`). |

## Commands

| Command | Meaning |
|---|---|
| `run [-c, --config PATH]` | Runs the controller. The default configuration is `/etc/scalws/controller.yaml`. This is what `scalws-controller.service` starts. `--service-sandbox` and `--service-rw PATH` give systems without systemd the unit's write fence and system call settings (see [sandbox](https://scalws.com/docs/guide/sandbox/)). `--restrict-suid-sgid` refuses set-user/group-ID modes on files the controller and everything it starts create, like systemd's `RestrictSUIDSGID=`; the packaged unit passes it instead of that setting. |
| `check-config [-c, --config PATH]` | Validates a controller configuration file without running it. Prints `controller configuration OK`, or exits with 2 and the error. |
| `status` | Instances, phases and generation, as JSON. |
| `scale <INSTANCES>` | Sets the desired number of active instances, within [`instances.min`](https://scalws.com/docs/reference/controller/#instances.min)..[`instances.max`](https://scalws.com/docs/reference/controller/#instances.max). |
| `reload` | Validates the scalwsd configuration file ([`controller.config`](https://scalws.com/docs/reference/controller/#controller.config)) as a new generation and rolls the instances onto it. |
| `autoscaling <MODE>` | Sets the autoscaling mode: `off`, `observe` or `web-tier`. `off` is the emergency switch. |
| `purge <QUERY>` | Purges cached responses on every instance. `QUERY` is a URL query, for example `host=example.com&prefix=/blog/`, `tag=news` or `all=1`. |
| `quic-lb-status [--demux-socket PATH]` | Counters of the running QUIC demux. The default socket is `/var/lib/scalws-controller/quic-lb/quic-lb.sock`. |

The client commands print the controller's JSON answer, and exit with 1 if the request
failed or the socket could not be reached. `quic-lb` (the QUIC demux process, started by
`run`), `config-schema` (used by ScalWS Console) and `recorded-pids` (used by the OpenRC
scripts) are hidden and not meant to be run by
hand.

```sh
sudo scalws-controller check-config
sudo scalws-controller status
sudo scalws-controller reload            # after editing /etc/scalws/scalws.yaml
sudo scalws-controller autoscaling observe
```

The control socket belongs to user `scalws` with mode 0600. Run the client commands as root
or as `scalws`.

## run

`run` exits with 2 when the configuration cannot be loaded and with 1 when the control
socket cannot be bound or the controller cannot start. It stops on `SIGTERM` or `SIGINT`.
Stopping the controller leaves its instances running; a controller started later adopts
them.

## reload

A reload reads `controller.config` with its site files and refuses it when an application
uses a managed runtime (PHP without `fastcgi`, Node.js, Python or container runtimes),
because every instance would start its own pool
([ADR-0036](https://scalws.com/docs/adr/0036-multi-instance-state/)):

```text
<tenant>/<app>: managed <type> runtimes are not supported with several instances (ADR-0036); use an external endpoint (php: fastcgi, others: proxy)
```

New listening addresses are bound before the generation is created; a bind failure rejects
the reload and leaves every current socket as it was. Otherwise the configuration is stored,
with the site files expanded into it, as `<state_dir>/generations/<n>/scalws.yaml`, and
the instances are replaced one by one: a new instance starts and becomes active before an
old one drains. New instances run the `scalwsd` binary named by
[`controller.webd_binary`](https://scalws.com/docs/reference/controller/#controller.webd_binary) as it is on
disk at that moment.

If an instance of the new generation fails while an instance of an older generation still
serves, the controller goes back to that older generation and reports it as
`last_rollback` in `status`. There is no rollback command.

`scalwsctl reload` sends the same request when the controller runs, see
[scalwsctl](https://scalws.com/docs/reference/programs/scalwsctl/#under-scalws-controller).

## Upgrade roll

A package upgrade replaces `scalwsd` and restarts the controller, which adopts the running
instances. When an adopted Ready or Active instance of the current generation runs a
replaced binary (its `/proc/<pid>/exe` is `webd_binary` with ` (deleted)` appended), the
controller creates a new generation that is a copy of the current one and rolls the
instances onto the binary on disk, one at a time: a new instance becomes active before an
old one drains. The configuration file is not read again, so unapplied edits stay
unapplied. If the new binary cannot become ready, the rollback keeps the old instances
serving.

The roll needs one free slot below `instances.max` for the first new instance. When every
slot is taken, the controller does not roll and logs a warning
(`not rolled: instances.max leaves no spare slot`); raise `instances.max` or move the
instances with `scalws-controller reload`. `instances.roll_on_upgrade: false` in
`controller.yaml` turns the roll off. The controller then logs a warning that names the
instances; move them with `scalws-controller reload`.
`status` shows `"binary": "replaced"` or `"binary": "current"` for each instance.

## See also

* [Command-line programs](https://scalws.com/docs/guide/cli/)
* [scalwsctl](https://scalws.com/docs/reference/programs/scalwsctl/), [scalwsd](https://scalws.com/docs/reference/programs/scalwsd/), [scalws-console](https://scalws.com/docs/reference/programs/scalws-console/)
* [Adaptive Runtime](https://scalws.com/docs/adaptive-runtime/)
* [ADR-0035](https://scalws.com/docs/adr/0035-instance-lifecycle-and-config-generations/): instance lifecycle and configuration generations
