Initial code commit
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
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
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user