From f31560350ffd995bf3352f6c3cf2aba733bf6ff7 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Fri, 2 Oct 2026 17:29:14 +0100 Subject: [PATCH] Ensure pgBackRest schedules have a running cron service --- CHANGELOG.md | 4 +++ README.md | 4 +++ lib/active_postgres/components/pgbackrest.rb | 10 ++++++ test/components/pgbackrest_scheduler_test.rb | 35 ++++++++++++++++++++ test/components/pgbackrest_test.rb | 1 + 5 files changed, 54 insertions(+) create mode 100644 test/components/pgbackrest_scheduler_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0c569..d9941f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project follows [Semantic Versioning](https://semver.org/). ## Unreleased +### Fixed + +- Install and enable cron for pgBackRest backup and PostgreSQL log-archive schedules, and fail setup if the scheduler is not active. + ## 0.9.8 - 2026-10-02 ### Fixed diff --git a/README.md b/README.md index 057df38..8c685c3 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,10 @@ components: # schedule_incremental: "0 * * * *" ``` +pgBackRest setup installs cron, enables it at boot, and checks that it is active. +Backup schedules run only on the primary; configured log-archive schedules run +on each database host. + Run a PITR restore: ```bash diff --git a/lib/active_postgres/components/pgbackrest.rb b/lib/active_postgres/components/pgbackrest.rb index 250493c..866c588 100644 --- a/lib/active_postgres/components/pgbackrest.rb +++ b/lib/active_postgres/components/pgbackrest.rb @@ -151,6 +151,16 @@ def install_on_host(host, create_stanza: true) else remove_backup_schedule(host) end + + ensure_scheduler(host) + end + + def ensure_scheduler(host) + install_apt_packages(host, 'cron') + ssh_executor.execute_on_host(host) do + execute :sudo, 'systemctl', 'enable', '--now', 'cron' + execute :sudo, 'systemctl', 'is-active', '--quiet', 'cron' + end end def setup_backup_schedules(host, pgbackrest_config) diff --git a/test/components/pgbackrest_scheduler_test.rb b/test/components/pgbackrest_scheduler_test.rb new file mode 100644 index 0000000..92726c8 --- /dev/null +++ b/test/components/pgbackrest_scheduler_test.rb @@ -0,0 +1,35 @@ +require 'test_helper' + +class PgBackRestSchedulerTest < Minitest::Test + def test_install_requires_running_cron_after_configuring_primary_and_standby_schedules + [true, false].each do |primary| + host = 'db.example.com' + config = stub_config(primary_host: host, component_config: { + pgbackrest: { repo_type: 'local', schedule_full: '0 2 * * 0' } + }) + calls = [] + backend = Object.new + backend.define_singleton_method(:execute) { |*args| calls << args } + executor = Object.new + executor.define_singleton_method(:execute_on_host) { |_host, &block| backend.instance_exec(&block) } + executor.define_singleton_method(:upload_file) { |_host, _content, path, **_options| calls << [:upload, path] } + component = ActivePostgres::Components::PgBackRest.new(config, executor, ActivePostgres::Secrets.new(config)) + component.define_singleton_method(:upload_template) { |*_args, **_options| nil } + + component.send(:install_on_host, host, create_stanza: primary) + + cron_install = calls.index { |args| args.include?('apt-get') && args.last == 'cron' } + cron_enable = calls.index([:sudo, 'systemctl', 'enable', '--now', 'cron']) + cron_check = calls.index([:sudo, 'systemctl', 'is-active', '--quiet', 'cron']) + schedule = if primary + calls.index([:upload, '/etc/cron.d/pgbackrest-backup']) + else + calls.index([:sudo, 'rm', '-f', '/etc/cron.d/pgbackrest-backup-incremental']) + end + assert_operator schedule, :<, cron_install + assert_operator cron_install, :<, cron_enable + assert_operator cron_enable, :<, cron_check + refute_includes calls, [:sudo, 'systemctl', 'restart', 'postgresql'] + end + end +end diff --git a/test/components/pgbackrest_test.rb b/test/components/pgbackrest_test.rb index 8c2a330..11ab83c 100644 --- a/test/components/pgbackrest_test.rb +++ b/test/components/pgbackrest_test.rb @@ -288,6 +288,7 @@ def test_install_on_standby_removes_stale_backup_schedules ssh_executor = Minitest::Mock.new ssh_executor.expect(:execute_on_host, nil, ['standby.example.com']) + ssh_executor.expect(:execute_on_host, nil, ['standby.example.com']) secrets = ActivePostgres::Secrets.new(config) pgbackrest = ActivePostgres::Components::PgBackRest.new(config, ssh_executor, secrets) calls = []