Files
docker-images/python-tools/README.md
T
ajp_anton cb80d420c0
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8 context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; } name:ph… (push) Successful in 42s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18 POSTGIS_VERSION=3 VCHORD_VERSION=0.5.3 context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 53s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3 context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; } name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 1m18s
Build custom container images / build (map[base_image:python:3.12-slim build_args:PYTHON_VERSION=3.12-slim context:linkki-tiedotus fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; } name:linkki-tiedotus oci_labels:… (push) Successful in 26s
Simplify python-tools task configuration
2026-08-24 15:40:31 +00:00

7.8 KiB

Python Tools

This image is a Python 3 task runner with ExifTool and a small web control panel for running known, mounted Python maintenance tasks. It starts the control panel with python -m app.

Control Panel

The application discovers direct *.py files in each non-hidden directory under /opt/tasks. Each directory is a task group. Tasks in one group run sequentially; different groups may run concurrently. It records run history and combined output in SQLite under /var/lib/server-maintenance, and supports cancellation of active task process groups.

The image contains application code only. Mount these paths at runtime:

  • /opt/tasks read-only: reviewed task scripts and their input manifests.
  • /opt/credentials read-only: named credential files used by those scripts.
  • /var/lib/server-maintenance: persistent SQLite data, logs, and task state.

Each group's durable task data lives under /var/lib/server-maintenance/tasks/<group-id>. When a task group disappears from the mounted task directory, its state is retained for 14 days and then removed automatically. Tasks that need more focused retention should clean up their own state within that group.

Task processes receive only runner-provided paths for input, artifacts, credentials, and state. Keep task-specific non-secret configuration in an optional task-config.json beside that group's scripts, and credentials in /opt/credentials. The runner does not interpret task-config.json; its schema belongs to the task group that reads it.

Tasks are never imported during discovery. Their optional description is the first line of their module docstring. A task must not require stdin or command line arguments, should write useful output, and must return a non-zero exit code on failure.

Task definitions

Each non-hidden directory directly below /opt/tasks is a task group. Its direct *.py files are the exposed tasks. Use an optional task-inputs.json beside those files to define the group's title, task titles and descriptions, task order, and web form fields. It is parsed as data only and currently uses version 1:

{
  "version": 1,
  "title": "Example Tasks",
  "tasks": {
    "inspect.py": {
      "title": "Inspect item",
      "description": "Checks one item without changing it.",
      "inputs": [
        {"name": "identifier", "label": "Identifier", "type": "text", "required": true},
        {"name": "attempts", "label": "Attempts", "type": "integer", "minimum": 1, "maximum": 5, "default": 2},
        {"name": "mode", "label": "Mode", "type": "choice", "options": ["standard", "extended"]},
        {"name": "alerts", "label": "Alerts", "type": "multi_choice", "options": ["email", "webhook"]},
        {"name": "review_csv", "label": "Review CSV", "type": "file", "accept": [".csv"], "maximum_bytes": 10485760}
      ],
      "wait_for_result": true,
      "execution": {
        "field": "mode",
        "dry_run_value": "review",
        "dry_run_label": "Review",
        "execute_value": "apply",
        "execute_label": "Execute"
      }
    }
  }
}

title is optional on a group and task. A group otherwise uses its directory name; a task otherwise uses its filename without .py, with underscores made readable. description is optional on a task and otherwise comes from the first non-empty line of its module docstring. The order of keys in tasks controls the listed tasks' display order. Include a task as {} when only ordering is needed. Unlisted direct Python files follow alphabetically.

Each task declaration may contain title, description, inputs, wait_for_result, download_artifacts, and execution. Set wait_for_result for short tasks where the form should remain blocked until it reports success or failure. The control panel disables its controls and warns before page unload while such a task runs. download_artifacts additionally starts the first non-JSON artifact download after a successful browser run; reserve it for files such as archives that the browser should download automatically.

Every input has name, label, and type; required defaults to false. default is allowed for every type except file.

  • text: optional pattern, a Python-compatible regular expression.
  • integer: optional minimum, maximum, and positive step.
  • date and datetime: ISO-formatted browser date controls.
  • choice: options is required, as strings or { "value", "label" } objects.
  • multi_choice: the same required options; its default is an array.
  • file: optional lowercase extension list in accept and positive byte cap in maximum_bytes.

Set sensitive to true on text or file inputs that must not appear in run history. Text is masked, the database records [redacted], and the original value is available only through a private execution-input file. Uploaded files are likewise retained privately for the run and then removed.

For review-first tasks, use execution. It produces one split button: click the main part to use its current mode, or use the arrow to select the other mode. A successful review run can be repeated with its saved inputs in the execution mode from the run-detail page. The task must still revalidate the live filesystem before changing anything.

{
  "field": "mode",
  "dry_run_value": "review",
  "dry_run_label": "Review",
  "execute_value": "apply",
  "execute_label": "Execute"
}

The server validates every submitted value before a run is queued. It records the validated object in the run database and writes it to a per-run file. Sensitive fields are redacted in the database and use a private 0600 execution file instead. Tasks read the applicable file path from SERVER_MAINTENANCE_INPUT; no user value is appended to the command line. A task without an entry in the manifest gets an empty JSON object. Uploaded files are stored in a private per-run directory; their absolute paths are provided in the input JSON and the files are removed when the task finishes, fails, or is cancelled.

A group may also include a private task-config.json for stable local details such as mounted filesystem roots. This is not part of discovery or the web form schema: task code reads and validates it itself. Keeping it beside the group makes a task's runtime requirements clear and avoids a global task-environment allowlist.

Artifacts

Tasks can write files to the directory named by SERVER_MAINTENANCE_ARTIFACTS. Run-detail pages show them when the task finishes. JSON artifacts offer an Open link that renders the JSON in a new tab and leaves the artifact available; other artifacts offer a download link. The server deletes an artifact after its download transfer ends; any artifact not downloaded is removed after 24 hours. A browser cannot confirm that a downloaded file was retained on the client device.

Background tasks

An optional background-tasks.json beside a group's task files declares internal recurring scripts. These scripts must live below the group's internal/ directory, are not exposed in the UI, and are launched at most once per configured interval. Their latest output is retained below the persistent state directory rather than being added to normal run history.

{
  "version": 1,
  "tasks": [
    {"id": "poll", "script": "internal/poll.py", "interval_seconds": 60}
  ]
}

compose.example.yaml is suitable for adding the service to an existing Compose stack. Set PYTHON_TOOLS_VOLUME_ROOT to the host directory that will hold state, tasks, and credentials; set SERVER_MAINTENANCE_ALLOWED_PROXY_IPS to the reverse proxy's direct peer address. Keep the application behind an authenticated reverse proxy.

Tasks receive a group-specific writable directory in SERVER_MAINTENANCE_STATE. Tasks which intentionally maintain application-wide state, such as run-history maintenance, can use SERVER_MAINTENANCE_STATE_ROOT; normal task state should remain group-specific.