Funmill
Funmill provides one stable task API while execution is delegated to a
replaceable backend. The recommended local backend is self-hosted
Dagu, pinned to v2.16.3. Windmill remains
available for existing deployments.
client -> Funmill /v1 -> TaskBackend -> Dagu
-> Windmill
Funmill owns the public request and response models. Backend job IDs remain opaque strings, and no backend-specific routes or payloads are exposed to clients.
Start
Install the project and the Dagu binary. The installer supports macOS and Linux on Intel/AMD and ARM64:
uv sync
uv run funmill install dagu
Start Dagu. It stores state under
~/.farfarfun/funmill/services/dagu/data/ and needs no external database:
uv run funmill start dagu
Open http://localhost:8813, then start Funmill in another terminal:
FUNMILL_API_KEY='replace-me' \
FUNMILL_BACKEND=dagu \
DAGU_URL='http://127.0.0.1:8813' \
uv run funmill start
Local Dagu binds to loopback without authentication. If Dagu authentication is
enabled, set DAGU_TOKEN for both funmill start dagu and funmill start so
cross-run dependencies can query Dagu from worker processes.
Dagu runs submitted source with the service user's host permissions. Keep both services private and accept only trusted code; use isolated workers or containers before accepting untrusted jobs.
The Funmill API port is fixed at 8812; the active third-party UI/API port is
fixed at 8813. OpenAPI docs are at http://localhost:8812/docs. All /v1
routes require X-API-Key. See the
Windmill deployment guide when using
the optional Windmill backend.
Run the end-to-end task and DAG checks with:
FUNMILL_API_KEY=the-value-from-env ./scripts/smoke.sh
Set FUNMILL_CALLBACK_URL to test callbacks. The URL must be reachable from
the backend workers.
API
| Operation | Route |
|---|---|
| Submit Python/Bash | POST /v1/tasks |
| Submit a DAG | POST /v1/workflows |
| Status | GET /v1/tasks/{task_id} |
| Logs | GET /v1/tasks/{task_id}/logs |
| Progress | GET /v1/tasks/{task_id}/progress |
| Result | GET /v1/tasks/{task_id}/result |
| Cancel | POST /v1/tasks/{task_id}/cancel |
| Rerun | POST /v1/tasks/{task_id}/rerun |
Submit one task, optionally waiting for existing task IDs:
{
"language": "python",
"source": "def main(value: int):\n return value * 2\n",
"args": {"value": 21},
"depends_on": ["EXISTING_TASK_ID"],
"dependency_timeout_seconds": 3600,
"retry": {"attempts": 2, "delay_seconds": 5},
"timeout_seconds": 300,
"callback_url": "https://example.internal/task-callback"
}
Submit A -> [B, C] as one workflow:
{
"tasks": [
{"key": "a", "language": "python", "source": "def main(): return 1"},
{"key": "b", "language": "python", "source": "def main(): return 2", "depends_on": ["a"]},
{"key": "c", "language": "bash", "source": "main() { echo 3; }", "depends_on": ["a"]}
]
}
Dependencies inside a workflow are task keys. Top-level depends_on values are
IDs returned by earlier Funmill submissions. Backends check those dependencies
from worker jobs, so waiting does not hold the Funmill API process. Failure or
cancellation of a dependency fails the waiting task.
Workflow results and callback payloads are objects keyed by every workflow task
key. Each topological layer is a synchronization barrier; tasks in the same
layer run in parallel.
Callbacks contain task_id, status, and payload; success and failure
delivery retry three times. Delivery is at least once, so callback receivers
must be idempotent.
Backends
The public contract is TaskBackend in src/funmill/backends/base.py. Backend
selection uses FUNMILL_BACKEND; registration lives in
src/funmill/backends/__init__.py, following the same driver pattern as
fundrive. Each third-party adapter lives in its own directory, such as
src/funmill/backends/windmill/.
Changing the backend does not change /v1, but it does not migrate old jobs or
their IDs. Add a Funmill-owned ID mapping database only when jobs must remain
queryable after a live backend migration.
Operations
funmill services
funmill install dagu
funmill start dagu
funmill install windmill
funmill start windmill
funmill start
Both start commands run in the foreground and stop normally with Ctrl+C.
Use systemd or an existing process manager for long-running deployment. Add
TLS, PostgreSQL backups, callback egress restrictions, and a secrets manager
before network exposure.
Metadata
Release files for funmill 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| funmill-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Release files / funmill-0.1.5-py3-none-any.whl
| Download URL | funmill-0.1.5-py3-none-any.whl |
|---|---|
| Size | 22.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f47c2e6b4cb4b508e547edb861a49752df661f64f238524d4de43055e44b4919
|
|
BLAKE2b-256 checksum How to use checksums |
cb7115a8a06f0eda90613240693c0ea28ac8ffb7582e691bcf91d43a60356ef7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for funmill 0.1.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.