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

4.0 KiB

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.

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

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:

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

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.

{
  "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.