# Workers and scheduled jobs

> Run processes that answer no request, for good or at set times, beside a site or on their own.

Under the manifest's `services` key, a project runs processes that answer no request: a worker that runs for good, or a job at set times.

## Declare the services

Each entry names a service and takes a `start`. What else it declares sets its kind:

| Kind | Declares | Runs |
|---|---|---|
| Server | `port` | for good, listening on its port |
| Worker | `"internal": true`, no `port` | for good, listening on nothing |
| Scheduled | `schedule` | at the times it names, each run to its end |

A worker or a scheduled service goes under `services` even alone: a top-level `start` is always a server.

In `sitesolide.json`, a page, a worker and a job every quarter of an hour:

```json
{
  "slug": "background",
  "services": {
    "web": { "start": "/usr/local/bin/bun run web.ts", "port": 3041 },
    "worker": { "start": "/usr/local/bin/bun run worker.ts", "internal": true },
    "tally": { "start": "/usr/local/bin/bun run tally.ts", "schedule": "*:0/15", "timeout": "1m" }
  }
}
```

The repository's [background example](https://github.com/cthiriet/sitesolide/blob/main/examples/background/README.md) deploys as it is.

## Write a worker

A worker suits a queue's consumer, a bot holding a connection, or an agent.

- **It comes back.** systemd restarts it 2 seconds after a failure, then waits longer each time, up to 5 minutes.
- **It yields.** When they compete for the processor, a site gets five times a worker's share.
- **It stops on SIGTERM.** At a deploy or a restart, systemd sends SIGTERM, and kills it if it still runs 90 seconds later. Finish the current step and exit.

## Schedule a job

`schedule` takes a systemd calendar expression:

| `schedule` | Runs |
|---|---|
| `daily` | every day at midnight |
| `*:0/15` | every quarter of an hour |
| `Mon..Fri 08:00` | on weekdays at eight in the morning |
| `*-*-* 06:30 Europe/Paris` | every day at 6:30, Paris time |

Times are the server's, UTC on most cloud images, unless the expression names a time zone. `timeout` stops a run that hangs: `90s`, `15m`, `2h`, an hour by default and a day at most. A schedule systemd cannot read stops `deploy` before it writes anything.

- A deploy runs nothing: the first run comes at the next scheduled time.
- A time that comes during a run is skipped: runs never overlap.
- A failed run is not retried before its next time.
- A time missed while the server was down runs once, when it is back.

## Run a project with no page

A project whose services are all workers or scheduled jobs, with no `publicDir`, serves no page. Its address answers 200 with a line saying so, and it cannot have its own domain.

```json
{
  "slug": "nightly",
  "services": {
    "sync": { "start": "/usr/local/bin/bun run sync.ts", "schedule": "daily" }
  }
}
```

## Check the runs

`sitesolide status` shows each scheduled service's last run, how it ended, and its next run. `sitesolide logs` shows every run beside the project's other services.

```bash
sitesolide status
```

## Keep heavy work off the server

A background service shares the server with every site, and can slow them down. Light work fits, such as waiting on an outside API. These do not:

- **A headless browser.** The unit's confinement refuses Chromium's sandbox.
- **Heavy computing**, such as video encoding or a model run on the processor.
- **Mass email from the server's address.** Blocklists would hit every site: call a mail provider's API instead.

## Next steps

- [Calling outside services](network.md): let a service reach the APIs it calls.
- [Backups and restore](backups.md): see what the hourly snapshot keeps of the data folder.
- [Monitoring](monitoring.md): hear about a worker crashing in a loop, or a failed run.

Full reference: https://github.com/cthiriet/sitesolide/blob/main/docs/manifest.md#services
