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
176 lines
7.8 KiB
Markdown
176 lines
7.8 KiB
Markdown
# 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`:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```json
|
|
{
|
|
"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.
|