Repository navigation
v0.9.0
Release v0.9.0
Release Notes
gonf v0.9.0 adds a new SystemdTimer resource that lets you declare a scheduled job in a few lines of Go and have gonf generate, install, and maintain both systemd unit files for you. The new resource is fully integrated into the plan engine, so it works identically for local runs and remote pushes to a fleet of hosts.
Features
Declarative systemd timers
The headline addition is the SystemdTimer resource, alongside its counterpart NoSystemdTimer. Previously, scheduling a recurring job with systemd meant manually juggling several moving parts: writing a .timer file, writing a companion oneshot .service file, running daemon-reload, and enabling the timer. With SystemdTimer, you describe the job once and gonf handles all of it:
SystemdTimer("backup",
WithCommand("/usr/local/bin/backup.sh"),
WithOnCalendar("*-*-* 03:00:00"),
WithOnBootSec("10min"),
WithPersistent,
WithDescription("Nightly backup"),
WithAfter("network-online.target"),
)This is useful because it keeps scheduled jobs fully declarative and idempotent. gonf writes the unit files, runs daemon-reload only when they actually change, and enables and starts the timer. When a timer is absent, NoSystemdTimer stops, disables, and cleanly removes both unit files.
Key capabilities:
- Full unit generation — gonf generates both the
.timerand the oneshot.service, so you never hand-edit unit files. - Rich scheduling —
WithOnCalendar(required), optionalWithOnBootSecdelay, andWithPersistentto catch up on missed runs while the machine was off. - Service dependencies —
WithAfterandWithWantslet you order the one-shot run relative to other units (for example, waiting for the network to be online). - Clear unit descriptions —
WithDescriptionandWithServiceDescriptionset the[Unit] Descriptionfor the timer and the service separately, makingsystemctloutput easier to read. - System or user units —
WithUsertargets~/.config/systemd/user/for user-level timers instead of/etc/systemd/system/. - Optional name suffix — you can name the resource with or without the
.timer/.servicesuffix; gonf normalizes it. - Required-field validation — a present timer must declare both a command and a calendar expression, and errors surface clearly and early.
Plan-engine support for timers
The new timer is a first-class citizen in gonf's plan pipeline. Recording a run now emits a systemd_timer plan operation that carries the command, calendar, schedule options, descriptions, and dependencies, and applying that plan installs the units exactly as the local run would. This means scheduled jobs defined with SystemdTimer behave the same way whether applied directly on a machine or pushed over SSH to a remote fleet.
Improvements
Expanded option surface for timers
A set of new, reusable options has been added for timers: WithOnCalendar, WithOnBootSec, WithPersistent, WithDescription, WithServiceDescription, WithAfter, and WithWants. These follow the same composable options pattern used elsewhere in gonf, keeping the API consistent and giving you fine-grained control without boilerplate. Existing timer options (WithRestart, WithEnableOnly, WithUser) now also apply to the new resource.
Clearer guidance on which timer to use
The documentation now explains when to prefer SystemdTimer (gonf owns the unit content) versus the existing Timer resource (you install units from a source tree and only want to enable/start them). This helps you pick the right tool and avoid redundant or conflicting unit management.
Compatibility and Migration
- Plan schema version bumped to 7. The new
systemd_timeroperation is introduced under schema version 7. This release continues to apply plans recorded with versions 1–6, so existing recorded plans remain valid. Older gonf binaries, however, will refuse version 7 plans up front rather than failing at apply time, so remote targets running an older binary cannot silently misinterpret a new plan. - No breaking changes to existing APIs. All previously documented resources and options continue to work unchanged.
SystemdTimeris purely additive.
Example
A complete scheduled backup job, system or user scope:
Task("backup", "nightly backup", func() {
SystemdTimer("backup",
WithCommand("/usr/local/bin/backup.sh"),
WithOnCalendar("*-*-* 03:00:00"),
WithOnBootSec("10min"),
WithPersistent,
WithDescription("Nightly backup"),
WithServiceDescription("Run backup.sh once"),
WithAfter("network-online.target"),
WithWants("network-online.target"),
)
})