Restore generic Python tools documentation and tests
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 43s
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 54s
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: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 43s
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 54s
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
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# 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.
|
||||
|
||||
Task processes receive a minimal environment. To forward non-secret container
|
||||
settings to tasks, list their names in `SERVER_MAINTENANCE_TASK_ENV`, separated
|
||||
by commas. This allowlist prevents unrelated container configuration from
|
||||
being exposed to every task; credentials should remain in `/opt/credentials`.
|
||||
|
||||
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 inputs
|
||||
|
||||
An optional `task-inputs.json` beside a group's task files declares the fields
|
||||
for its web forms and its task order. It is parsed as data only and uses version
|
||||
`1`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"tasks": {
|
||||
"task.py": {
|
||||
"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"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported field types are `text`, `integer`, `date`, `datetime`, `choice`,
|
||||
`multi_choice`, and `file`. Text may also define a regular-expression
|
||||
`pattern`; integer fields accept `minimum`, `maximum`, and `step`. Choices may
|
||||
use strings or objects with `value` and `label`. File fields accept a list of
|
||||
lowercase filename extensions in `accept` and a byte limit in `maximum_bytes`.
|
||||
An input can define `required` and `default`.
|
||||
Set `sensitive` to `true` for a value or upload that must not appear in run
|
||||
history. The form masks sensitive text, the database records `[redacted]`, and
|
||||
the original value is available only through a private execution input file.
|
||||
Tasks listed in the manifest appear in JSON order. A listed task may use `{}`
|
||||
when it only needs ordering; unlisted scripts appear afterward in alphabetical
|
||||
order.
|
||||
Set `wait_for_result` to `true` for a short task whose form should wait for its
|
||||
final success or failure status. This disables the form and warns before
|
||||
leaving the page while the task is running; tasks without it keep the normal
|
||||
queued-run behavior.
|
||||
|
||||
For a task with a review-first operation, declare `execution` with the input
|
||||
field and its review and execution values. The control panel generates the
|
||||
dropdown. A successful review run offers a button that repeats the same saved
|
||||
inputs using the execution value; it does not reuse a stale filesystem
|
||||
snapshot, so the task must still validate its inputs before changing anything.
|
||||
|
||||
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.
|
||||
|
||||
## Downloads
|
||||
|
||||
Tasks can write downloadable files to the directory named by
|
||||
`SERVER_MAINTENANCE_ARTIFACTS`. Set `download_artifacts` to `true` alongside
|
||||
`wait_for_result` to start the first artifact download after a successful web
|
||||
run. Run-detail pages also show available downloads. The server deletes an
|
||||
artifact after its HTTP transfer ends; any undownloaded artifact 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.
|
||||
Reference in New Issue
Block a user