Releases: netresearch/ofelia
v0.23.1
What's Changed
Security
- Migrate
go-viper/mapstructurev1 to v2.4.0 — fixes GO-2025-3787 and GO-2025-3900 (sensitive information leak in logs when processing malformed data) (#544)
Fixed
- Release pipeline migrated from
slsa-github-generatortoactions/attest-build-provenancevia org-wide reusable workflow atnetresearch/.github(#542). This fixes the v0.23.0 release which had no binaries due to SHA-pinning conflict.
Note: v0.23.0 was released without binaries due to the pipeline issue fixed in this release. Users should use v0.23.1 instead.
v0.23.0
What's Changed
Environment File & Container Env Support
Load environment variables from external sources for all job types (#540, closes #314, #336, #351):
env-file: LoadKEY=VALUEpairs from files, like Docker's--env-file. Supports multiple files, quoted values,exportprefix, and special characters.env-from: Copy environment variables from a running Docker container at job execution time.
Merge order (last wins): env-file < env-from < environment (explicit always wins).
Bug Fixes
- Environment variable values containing
#or;are no longer truncated by INI comment parsing (#539, fixes #538) - Environment variable expansion now works in webhook config values, section names, and the
log-levelpre-parse path (#539)
Security
- SHA-pin all GitHub Actions and add Dependabot for actions updates (#536)
v0.22.0
What's Changed
Environment Variable Substitution in INI Config
Support ${VAR} and ${VAR:-default} syntax in INI configuration files (#532, closes #362).
Keep secrets out of version-controlled config files:
[global]
smtp-password = ${SMTP_PASS}
[job-run "backup"]
image = ${BACKUP_IMAGE:-postgres:15}
command = pg_dump ${DB_NAME:-mydb}${VAR}— replaced if defined and non-empty; kept literal if undefined (typos visible)${VAR:-default}— uses default when undefined or empty$VAR(no braces) — not substituted, keeping cron expressions and shell commands safe
Thanks to @nut-neek for describing their use case.
Dependencies
- Bump OpenTelemetry modules to v1.42.0 (#533)
- Bump
step-security/harden-runnerto v2.16.0 (#533) - Bump
aquasecurity/trivy-actionto v0.35.0 (#532) - Bump
google.golang.org/grpcto v1.79.3 (#531)
Full Changelog: v0.21.5...v0.22.0
Verification
All binaries include SLSA Level 3 provenance attestations.
Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofeliaVerify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.22.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txtIncluded in this release
v0.21.5
What's Changed
Two new features for this release.
Thanks to @sethlinnkuleuven for reporting #527.
Added
ofelia versioncommand (#528): Print version information viaofelia versionorofelia --version. Release builds show tag and commit; dev builds fall back to Go build info.- Volume support for
job-service-run(#529, closes #527): Mount host directories and named volumes into swarm service containers using thevolumeconfig key. Samesource:target[:ro|rw]format asjob-run.
[job-service-run "backup"]
schedule = @daily
image = postgres:15
network = cluster
volume = /host/script.sh:/script.sh
volume = backups:/backups:rw
command = /script.shFull Changelog: v0.21.4...v0.21.5
Verification
All binaries include SLSA Level 3 provenance attestations.
Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofeliaVerify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.5" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txtIncluded in this release
v0.21.4
What's Changed
Fix job-service-run network attachment being silently dropped — services now correctly join the specified network.
Thanks to @sethlinnkuleuven for reporting the issue.
Fixed
- Service network not attached (#524):
convertToSwarmSpecnow reads networks from bothServiceSpec.NetworksandTaskTemplate.Networks, fixing a mismatch introduced during the domain/adapter refactor - Service inspect missing fields:
convertFromSwarmServicenow converts Mounts, RestartPolicy, Resources, Networks, Mode, Placement, LogDriver, and EndpointSpec — all were previously lost silently
Added
- Swarm service adapter now converts Placement, LogDriver, and EndpointSpec in both directions — these domain types existed but had no conversion code
- 13 round-trip tests verifying every service spec field survives the domain→swarm→domain conversion cycle
Full Changelog: v0.21.3...v0.21.4
Verification
All binaries include SLSA Level 3 provenance attestations.
Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofeliaVerify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.4" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txtIncluded in this release
v0.21.3
What's Changed
Wire missing container spec fields that the Docker API already supports but job types didn't expose, fixing environment not working for job-service-run (#519).
Thanks to @sethlinnkuleuven for reporting the issue.
Fixed
- job-service-run:
environment,hostname, anddirconfig keys now work — passed through to Docker Swarm ContainerSpec - job-run:
working-dirconfig key now sets the container working directory;volumes-fromwas defined in the struct but never wired to Docker — now functional - job-exec:
privilegedconfig key now enables privileged exec mode - Corrected misleading documentation that claimed
job-service-runinherits fromRunJob(it embedsBareJob) - Removed non-existent swarm fields (replicas, placement-constraints, resource limits) from documentation examples
Full Changelog: v0.21.2...v0.21.3
Included in this release
v0.21.2
Highlights
This security-focused release addresses 5 vulnerabilities and 6 stability issues discovered during a comprehensive code review.
Security hardening
- Credential leak prevention:
/api/configno longer exposesWebPasswordHashandWebSecretKey - CSRF bypass removed: The
X-Requested-Withheader bypass has been eliminated - Rate limiter DoS fix: Stale entries are now cleaned up to prevent unbounded memory growth
- IP spoofing prevention:
X-Forwarded-ForandX-Real-IPheaders are only trusted from loopback or explicitly configured proxies - Configurable trusted proxies: New
web-trusted-proxiesoption for deployments behind reverse proxies in non-loopback networks
Stability improvements
- Context propagation to Docker API calls — scheduler shutdown, job removal, and max-runtime cancellation now reach Docker containers
- Double-close panic on daemon done channel fixed with
sync.Once - Concurrent map access crash in Config protected with mutex
- Shutdown hooks execute in priority groups instead of all concurrently
- Shutdown timeout now enforced even when hooks ignore context cancellation
- Swarm services correctly return
NonZeroExitErrorfor non-zero exit codes
Changes
Security
- fix(security): hide WebPasswordHash and WebSecretKey from /api/config (#511)
- fix(security): remove CSRF bypass via X-Requested-With header (#511)
- fix(security): implement rate limiter cleanup to prevent memory DoS (#511)
- fix(security): only trust forwarded headers from trusted proxies (#511)
- fix(security): make trusted proxies configurable (#511)
- fix(security): also check X-Real-IP in rate limiter middleware (#511)
Bug Fixes
- fix: propagate context to Docker API calls for cancellation support (#511)
- fix: prevent double-close panic on daemon done channel (#511)
- fix: add mutex to Config to prevent concurrent map access crash (#511)
- fix: execute shutdown hooks in priority groups (#511)
- fix: enforce shutdown timeout even when hooks ignore context (#511)
- fix: return NonZeroExitError for non-zero Swarm service exit codes (#511)
Dependencies
- chore(deps): bump golang.org/x/crypto from 0.48.0 to 0.49.0 (#512)
- chore(deps): bump github.com/netresearch/go-cron from 0.13.0 to 0.13.1 (#514)
- chore(deps): bump golang.org/x/time from 0.14.0 to 0.15.0 (#515)
Verification
All binaries include SLSA Level 3 provenance attestations.
Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofeliaVerify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.2" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txtIncluded in this release
v0.21.1
Highlights
Fix duration string parsing in configuration (#510)
Duration fields like max-runtime, notification-cooldown, and webhook timeout now correctly accept Go duration strings (1h, 30m, 55s, 1h30m) in INI config files and Docker labels. Previously, these values failed with strconv.ParseInt errors.
Before (broken):
[job-run "my-job"]
schedule = @hourly
image = myimage
max-runtime = 1h # ❌ strconv.ParseInt: parsing "1h": invalid syntaxAfter (fixed):
[job-run "my-job"]
schedule = @hourly
image = myimage
max-runtime = 1h # ✅ works as expectedAll 10 time.Duration config fields are fixed: max-runtime, notification-cooldown, config-poll-interval, docker-poll-interval, polling-fallback, poll-interval, restore-history-max-age, webhook timeout, retry-delay, preset-cache-ttl.
Fixes #509. Thanks to @azlux for reporting the issue!
Changes
Bug Fixes
- fix: add
StringToTimeDurationHookFuncto mapstructure decoders (#510)
Verification
All binaries include SLSA Level 3 provenance attestations.
Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofeliaVerify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.1" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txtIncluded in this release
v0.21.0
Highlights
Native go-cron DAG workflow engine (#499)
Replaced the custom WorkflowOrchestrator with go-cron's native DAG engine. Job dependencies, on-success, and on-failure chains now execute through go-cron's built-in directed acyclic graph scheduler, removing significant custom code while improving reliability.
Native pause/resume for job disable/enable (#497)
Job enable/disable now uses go-cron's native pause/resume API instead of removing and re-adding cron entries. This preserves job state and is more efficient. Disabled (paused) jobs correctly skip NextRuns/PrevRuns in the API.
Schedule introspection API (#495)
New PrevN/NextN endpoints expose upcoming and past scheduled run times for any job via the web API, useful for dashboards and monitoring.
Workflow observability (#496)
Added workflow completion hooks and metrics — track DAG workflow execution, step durations, and outcomes through the metrics subsystem.
Rate limiting via go-cron middleware (#500)
Replaced the custom scheduler semaphore with go-cron's MaxConcurrentSkip middleware for cleaner concurrency control.
Go 1.26.1 security update (#506)
Updated to Go 1.26.1 fixing 5 stdlib vulnerabilities: crypto/x509 certificate handling, net/url IPv6 parsing, os root escape, and html/template XSS.
Test coverage 60% → 86% (#502, #503)
Comprehensive test coverage improvements across core, CLI, web, middlewares, and Docker adapter packages.
Changes
Features
- feat: add PrevN/NextN schedule introspection to web API (#495)
- feat: add workflow completion observability hooks and metrics (#496)
- feat: use native go-cron pause/resume for job disable/enable (#497)
- feat: use native go-cron
@triggeredschedule instead of manual handling (#498) - feat: replace custom WorkflowOrchestrator with go-cron native DAG engine (#499)
- feat: replace scheduler semaphore with go-cron MaxConcurrentSkip middleware (#500)
Bug Fixes
- fix: don't track internal labels as config params (#485)
- fix: add allow-list for global config keys from Docker labels (#487)
- fix: update Go to 1.26.1 to fix stdlib vulnerabilities (#506)
- fix: eliminate data races detected by -race flag
- fix(web): improve API error handling, status codes, auth panic, rate limiter leak
- fix(core): nil safety, idempotent EnableJob, remove dead WaitGroup
Testing
- test: comprehensive test coverage improvement 60% → 82.5% (#502)
- test: increase coverage from 82.5% to 86.2% (#503)
Dependencies
- chore(deps): bump go-cron from 0.11.0 → 0.13.0 (quoted TZ values, DAG engine, pause/resume)
- chore(deps): bump docker/cli to 29.3.0
- chore: add SPDX headers and DCO enforcement (#501)
- chore: remove flaky CodSpeed benchmarks from CI (#507)
Full Changelog: v0.20.0...v0.21.0
Verification
All binaries include SLSA Level 3 provenance attestations.
Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofeliaVerify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txtIncluded in this release
v0.20.0
Highlights
Webhook notifications from Docker labels (#481)
Docker labels like ofelia.webhook.slack-alerts.preset=slack are now fully functional. Define named webhooks, global webhook settings, and per-job webhook assignments entirely via container labels — no INI file required. INI-defined webhooks take precedence over labels for security, and webhook configs are dynamically synced on container changes. Reported by @julian-heng in #467.
Allow job-run on stopped containers (#461)
The job-run type can now be defined on stopped containers via --docker-include-stopped. Running container definitions take precedence over stopped ones. Other job types (exec, local, service-run, compose) from stopped containers are ignored. Contributed by @nekrich.
Replace logrus with stdlib log/slog (#480)
Replaced the logrus dependency with Go's standard library log/slog package. This reduces external dependencies, improves structured logging, and aligns with the Go ecosystem's direction.
Go 1.26 modernization (#475, #477)
Upgraded to Go 1.26 with modern features: errors.AsType[T], reflect.Type.Fields() iterators, t.TempDir() in tests, and go-cron v0.11.0 scheduler enhancements (JobWithContext, WaitForJobByName, UpsertJob).
Mutation testing at 85%+ efficacy (#479)
Added mutation tests across all packages to validate test suite quality, reaching 85%+ test efficacy.
Changes
Features
- feat: allow job-run on non-service and stopped containers (#461) — @nekrich
- feat: modernize scheduler to leverage go-cron v0.11.0 features (#476)
- feat(api): expose job running status via
IsJobRunningByName - feat: implement
JobWithContexton jobWrapper for per-entry context - feat: wait for running jobs before removal with
WaitForJobByName - feat(hooks): add YAML and actionlint pre-commit checks (#474)
Bug Fixes
- fix: parse webhook config from Docker labels (#481, #467) — reported by @julian-heng
- fix: protect INI webhooks from label overwrite during sync
- fix: propagate
context.Contextthrough RunJob and workflow APIs - fix(ci): update golangci-lint to v2.9.0 for Go 1.26 support
- fix(ci): skip auto-approve for external contributors (#464)
- fix(ci): fix dependency review YAML and increase smoke test timeout
Refactoring
- refactor: replace logrus with stdlib
log/slog(#480) - refactor: modernize dependencies and adopt newer API features (#478)
- refactor: remove go-dockerclient dependency
- refactor: adopt Go 1.26 features and clean up test temp files (#477)
- refactor: use
errors.AsType[T]from Go 1.26 - refactor: replace hand-rolled cron validation with
cron.ValidateSpec - refactor: use
UpdateEntryJobByNamefor atomic job updates in web API - refactor: unify Docker label decoding with INI config path
Testing
- test: add mutation tests to reach 85%+ test efficacy (#479)
- test: add comprehensive webhook label tests and documentation (#482)
- test: add IPv6 URL coverage for Go 1.26 stricter parsing
Performance
- perf: add
WithCapacityto cron scheduler initialization
CI/CD
- chore(deps): upgrade Go to 1.26 and all dependencies (#475)
- chore(ci): update CodeQL Action from v3 to v4
- chore(ci): add codecov config, make project coverage informational (#465)
- chore: limit the number of Docker events (#466) — @nekrich
Full Changelog: v0.19.2...v0.20.0
Contributors
Thanks to the contributors who made this release possible:
- @nekrich — code contributor: stopped container support (#461), Docker events limit (#466)
- @julian-heng — bug reporter: webhook labels ignored (#467)