Skip to content

Switching aide scheduling from systemd to cron stops but does not disable puppet_aide.timer #169

Description

@hcaballero2

Updated 2026-07-07 with a root-cause investigation. The original framing
("stopped but still enabled", "does not disable an already-enabled unit")
was inaccurate — the units are static. Corrected analysis below.

Summary

In manifests/set_schedule.pp the timer is declared via systemd::timer (set_schedule.pp:71-76):

systemd::timer { 'puppet_aide.timer':
  active => ($method == 'systemd'),   # false in root/etc (cron) modes
  enable => ($method == 'systemd'),   # false ← intent: disable the timer
}

In cron modes the observed state on AlmaLinux 9/10 is:

puppet_aide.timer:   { ensure: stopped, enable: 'true', provider: systemd }
puppet_aide.service: { ensure: stopped, enable: 'true', provider: systemd }

ensure => stopped is enforced; enable => false is not.

Root cause

  • Neither unit has an [Install] section. The heredocs (set_schedule.pp:57-69) contain only [Timer]/OnCalendar= and [Service]/Type=oneshot — no [Install] WantedBy=. A systemd unit with no [Install] section is static and cannot be enabled or disabled.
  • A real disable is declared. systemd::timersystemd::unit_file emits service { 'puppet_aide.timer': ensure => false, enable => false, provider => 'systemd' } (puppet-systemd manifests/unit_file.pp), so Puppet does run systemctl disable — but that is a no-op on a static unit.
  • The provider reports static as enabled. systemctl is-enabled exits 0 for a static unit, and the systemd service provider returns :true whenever the exit code is 0 (OpenVox/Puppet lib/puppet/provider/service/systemd.rb, enabled?: return :true if code == 0). So puppet resource service always shows enable: 'true', and enable => false can never converge.

Consequence: the enable parameter is effectively inert for these units in every mode, not just cron modes.

Impact (re-assessed)

The original "AIDE scheduled twice after a reboot" concern most likely does not occur: a static timer has no WantedBy=timers.target, so systemd does not pull it in at boot — it will not auto-start. The enable: 'true' is a provider reporting artifact for static units, not real boot-persistence.

There is, however, a latent bug in the opposite direction that is arguably more serious: in the default systemd scheduling mode, enable => true on a static timer is also a no-op (the provider already sees :true and takes no action; active => true is what starts it). A timer that is running now will not be re-activated on the next boot because it is not wired into timers.target — so AIDE's scheduled check may silently stop running after a reboot in the default mode. (Needs beaker verification with an actual reboot; the acceptance suite never reboots, so it does not currently catch this.)

Fix

Give the timer an [Install] section so it becomes a real, enable/disable-able unit:

[Timer]
OnCalendar=${_systemd_calendar}

[Install]
WantedBy=timers.target

Then:

  • systemd mode: enable => true creates the timers.target symlink → the timer genuinely persists across reboots.
  • cron modes: enable => false removes the symlink → is-enabled returns disabled (exit 1) → the provider reports :false.

The oneshot service should stay static (timer-triggered services are not enabled directly), so the service enable assertion should be dropped rather than restored — the meaningful signal lives on the timer.

Follow-up in the acceptance suite

Once the module fix lands, in spec/acceptance/suites/default/05_schedule_spec.rb:

  • Restore the timer enable => 'false' assertion in the root/etc contexts (relaxed in Harden and repair the acceptance suite #172).
  • Add a systemd-mode assertion that the timer is genuinely enabled (WantedBy symlink present), and consider a reboot-based beaker test to lock in boot-persistence.

Where it was found

The enable => 'false' assertions in 05_schedule_spec.rb were dead (expect { … } with no matcher chained) until revived during test hardening; they then failed on AlmaLinux 9/10. They were relaxed to the deterministic guarantee (ensure => 'stopped') in #172 to keep that PR test-only and green; this issue tracks the module-side fix.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions