Files
schedls/docs/architecture.md
mdaleo404 aded37d491
CI / test (3.11) (push) Canceled after 0s
CI / test (3.12) (push) Canceled after 0s
CI / test (3.13) (push) Canceled after 0s
CI / test (3.14) (push) Canceled after 0s
CI / package (push) Canceled after 0s
CI / precommit-and-security (push) Canceled after 11s
CI / typecheck (push) Canceled after 11s
Initial code commit
2026-09-25 11:16:19 +01:00

128 lines
4.0 KiB
Markdown

# Architecture
`schedls` is a thin CLI over two native scheduling backends. It holds no state
of its own: the scheduler configuration on the host is the source of truth.
```text
parse args
|
v
validate request
|
v
operation / service layer operations/
|
v
backend backends/systemd.py, backends/cron.py
|
v
result model models.py
|
v
formatter output.py
```
## Package layout
```text
src/schedls/
__init__.py version
__main__.py python -m schedls
cli.py argparse, dispatch, request building
errors.py exception hierarchy and exit codes
models.py common model (jobs, specs, schedules, commands)
output.py human tables, key/value views, JSON schema
runner.py the only place subprocesses are executed
security.py name validation, trusted paths, atomic writes
timefmt.py durations, timestamps, formatting
describe.py presentation-only schedule descriptions
convenience.py convenience flags -> native expressions
unitfile.py read-only systemd unit-file parser
interact.py confirmation handling
prompt.py opt-in interactive input collection (wizard)
backends/
base.py capability model, plans, mutation results
systemd.py discovery + transactional mutation
cron.py lossless crontab model + discovery + mutation
renderers/
systemd.py ExecStart quoting, unit rendering, ExecStart parsing
cron.py shell-safe command rendering, managed blocks
operations/
inspect.py collect, filter, list, show
calendar.py schedls calendar
doctor.py schedls doctor
mutate.py preview, confirm, apply
```
## Layers
- **Backends** implement discovery and mutation against native facilities and
never format output.
- **Renderers** are pure functions from an internal model to native text. They
are the most security-sensitive code and are covered by exhaustive tests.
- **Operations** orchestrate: gather, preview, confirm, apply.
- **CLI** translates arguments into a request, calls one operation, and maps
errors to exit codes. With `--interactive` it first fills missing fields via
**prompt** (line-based, terminal-only), then continues down the normal path.
- **Runner** is the single process-execution boundary. `shell=True` never
appears in production code.
## Mutation plans
Mutating commands do not write directly. A backend produces a `Plan`:
```text
Plan
action create | update | remove | enable | disable
summary key/value lines for the preview
files FileChange(path, content | None)
commands CommandPlan(argv, description, input_text)
warnings messages to show the user
payload backend-private state (scope, unit names, snapshots)
```
The caller previews the plan, obtains confirmation, then calls `apply`. Plans
snapshot existing content so a failed step can be rolled back. Cron plans also
record the previous crontab text and re-check it at apply time to detect
concurrent edits.
## Exit codes
```text
0 success
1 operational failure
2 invalid command-line input or invalid schedule
3 safety refusal / conflict
```
## JSON schema
`schedls --json` emits a single document on stdout; diagnostics go to stderr.
The schema version is `1`.
```json
{
"schema_version": 1,
"jobs": [
{
"name": "backup",
"backend": "systemd",
"scope": "user",
"managed": true,
"enabled": true,
"schedule": {"kind": "calendar", "expression": "*-*-* 02:00:00"},
"command": {"argv": ["/usr/local/bin/backup"], "shell": false, "raw": null},
"source": {"detail": "systemd user timer", "path": "...", "line": null},
"next_run": "2026-09-25T02:00:00+01:00",
"last_run": null,
"last_result": null,
"warnings": []
}
],
"warnings": []
}
```
Rules: no ANSI sequences; ISO 8601 timestamps with explicit offsets; stable
field names; missing data is `null`, never invented.