File Processor
React to files being created, updated, or deleted in S3-compatible storage. When a file event arrives at the Decree webhook, minio-router matches the file path against registered processors and fans out one file-processor job per match. Each job downloads the file (or generates a signed URL if IS_PRE_SIGNED=true), runs the processor, and deletes the local copy.
How It Works
1. MinIO fires the event
When a file is created, updated, or deleted in a subscribed bucket, MinIO POSTs an S3-compatible JSON payload to http://decree-webhook:48880/minio:
{
"EventName": "s3:ObjectCreated:Put",
"Key": "mybucket/documents/report.pdf",
"Records": [...]
}
The request includes Authorization: Bearer <DECREE_MINIO_WEBHOOK_AUTH_TOKEN> — set as a custom header in the MinIO notification target config.
2. minio-router — match and fan out
minio-router parses the event, constructs FILE_SOURCE as <rclone_src>:<bucket>/<object-key> (e.g. minio:mybucket/documents/report.pdf), and scans every script in automations/lib/file-processors/ for a PATTERN= regex match. It also reads each processor's IS_PRE_SIGNED= setting to carry it into the job.
For each matching processor it writes one message to the Decree outbox:
---
routine: file-processor
rclone_path: minio:mybucket/documents/report.pdf
processor: my-processor
file_action: created
is_pre_signed: false
---
Multiple processors can match the same file — each runs independently as a separate Decree message.
3. file-processor — download (or reference), run, clean up
file-processor exports the standard FILE_* env vars, runs the matched processor script, then deletes any local temp file.
- Default (
IS_PRE_SIGNED=false): downloads the file viarclone copytoto a temp path;FILE_PATHis the local file. - Pre-signed (
IS_PRE_SIGNED=true): skips the download and instead callsrclone linkto generate a signed URL;PRE_SIGNED_URLis set to that URL andFILE_PATHremains empty. Useful when the processor needs to hand off the file to an external service rather than read it locally. removedevents: download is always skipped;FILE_PATHis empty.
Adding a File Processor
Create automations/lib/file-processors/<name>.sh:
#!/usr/bin/env bash
# PATTERN is matched against FILE_SOURCE: "<rclone_src>:<bucket>/<object-key>"
PATTERN="minio:documents/.*\.pdf$"
IS_PRE_SIGNED=false
set -euo pipefail
if [ "$FILE_ACTION" = "removed" ]; then
echo "Deleted: $FILE_KEY"
exit 0
fi
# FILE_PATH is the local temp file (do not delete — file-processor handles cleanup).
# PRE_SIGNED_URL is set instead of FILE_PATH when IS_PRE_SIGNED=true.
echo "Processing $FILE_PATH"
# your logic here — call opencode, run a script, POST to an API, etc.
Available env vars:
| Variable | Example | Description |
|---|---|---|
FILE_SOURCE | minio:mybucket/docs/file.pdf | Full rclone source path |
FILE_KEY | mybucket/docs/file.pdf | Path after the remote: prefix |
FILE_ACTION | created | removed | Event type |
FILE_PATH | /tmp/file.pdf.xK3rQp | Local temp file (empty for removed or when IS_PRE_SIGNED=true) |
PRE_SIGNED_URL | https://… | Signed URL (set when IS_PRE_SIGNED=true, otherwise empty) |
Script settings:
| Setting | Default | Description |
|---|---|---|
PATTERN | (required) | Regex matched against FILE_SOURCE |
IS_PRE_SIGNED | false | If true, skip download and set PRE_SIGNED_URL instead |
Pattern tips:
PATTERN="minio:photos/.*\.(jpg|jpeg|png)$" # specific bucket + extension
PATTERN="minio:.*\.pdf$" # any bucket, PDFs only
PATTERN="minio:invoices/.*" # everything in the invoices bucket
PATTERN=".*\.csv$" # any rclone remote, CSVs
Decree picks up the new file immediately — no restart needed.
MinIO Setup
Step 1 — Configure the webhook notification target
In the MinIO console go to Administrator → Events and add a new webhook endpoint:
| Field | Value |
|---|---|
| Identifier | DECREE |
| Endpoint | http://decree-webhook:48880/minio |
| Auth Token | your DECREE_MINIO_WEBHOOK_AUTH_TOKEN value |
Save and verify the target shows as reachable. The identifier DECREE is used in the next step — MinIO will expose the ARN arn:minio:sqs::DECREE:webhook.
:::warning One target is not enough Adding the webhook target under Events only registers the endpoint. MinIO will not send any events until you subscribe individual buckets to it in the next step. :::
Step 2 — Subscribe buckets to events
For each bucket you want to monitor:
- Go to Buckets → [bucket name] → Events
- Click Subscribe to Event
- Select the ARN
arn:minio:sqs::DECREE:webhook - Configure the subscription:
- Prefix — optional path filter (e.g.
uploads/to only watch that folder) - Suffix — optional extension filter (e.g.
.pdf) - Events — check
PUTfor creates/updates,DELETEfor deletions
- Prefix — optional path filter (e.g.
- Save
Repeat for each bucket. Each bucket subscription sends events independently to the same webhook endpoint.
Step 3 — Configure rclone
The decree container uses /secrets/rclone/rclone.conf for all rclone operations. Add a MinIO remote if you haven't already:
./existential.sh setup rclone
Name the remote minio (or update rclone_src in services/decree/webhook/config.yml to match your remote name).
Testing
Send a test event directly to the webhook to verify routing without needing a real MinIO event:
curl -X POST http://localhost:48880/minio \
-H "Authorization: Bearer <DECREE_MINIO_WEBHOOK_AUTH_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"EventName":"s3:ObjectCreated:Put","Key":"mybucket/documents/hello.txt","Records":[]}'
Watch the routing happen in real time:
docker logs -f decree
Inspect the run log after it completes:
docker exec decree decree status
docker exec decree decree log <id-prefix>
To test just the routing stage (without rclone), check the inbox after the curl — minio-router will have written outbox messages even if file-processor fails:
ls automations/runs/
Verifying Routine Pre-checks
docker exec decree decree routine minio-router
docker exec decree decree routine file-processor