Skip to content

v0.9.0

Choose a tag to compare

@snonux snonux released this 17 Sep 05:40
· 512 commits to main since this release

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 .timer and the oneshot .service, so you never hand-edit unit files.
  • Rich scheduling — WithOnCalendar (required), optional WithOnBootSec delay, and WithPersistent to catch up on missed runs while the machine was off.
  • Service dependencies — WithAfter and WithWants let you order the one-shot run relative to other units (for example, waiting for the network to be online).
  • Clear unit descriptions — WithDescription and WithServiceDescription set the [Unit] Description for the timer and the service separately, making systemctl output easier to read.
  • System or user units — WithUser targets ~/.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/.service suffix; 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_timer operation 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. SystemdTimer is 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"),
    )
})