A systemd-based service that runs Ansible playbooks periodically.
This basically flips ansible on its head, instead of you pushing configs from a central location to remote machine, this continuously pulls a git repo and runs the playbooks.
Essentially, this creates a git-ops driven configuration philosophy.
An example layout:
your-ansible-repo/
├── vars.yml # Global variables
├── handlers.yml # Global handlers (optional)
├── app1/
│ ├── task.yml # task.yml files are whats executed
│ ├── vars.yml # vars.yml hold variables (can be vault encrypted)
├── app2/
│ ├── task.yml
├── system-quadlets/ # System-level quadlet (rootfull) will be placed in /etc/containers/systemd
│ ├── myapp.container # Supports .container .build .volume .network .env .kube .pod .image files
| ├── myapp.volume
| ├── myapp.pod
│ └── vars.yml
├── user-quadlets/ # User quadlet (rootless) .containers will be placed in ~/.config/containers/systemd
│ └── user123/
│ ├── someapp.container.j2 # Jinja templates files will be processed with variables loaded via vars.yml files
│ └── vars.yml # Quadlet-specific variables
└── finally/
└── finally.yml # finally.yml always runs last
By default the service runs in one of two modes:
- Changes mode: Runs every 15 minutes for quick updates to specific changed directories
- Full mode: Runs weekly on Sunday at 2 AM for complete system configuration
Obviously those times are configurable, but those are the defaults.
Key Features:
- Git Repository Management: Automatically pulls configuration from git repositories
- Task Management: Finds and executes
task.ymlfiles from user repositories - Podman Quadlets: Manages system and user container services via Podman quadlets
- Variable Integration: Loads variables from
vars.ymlfiles with hierarchical support (access viaenv_vars.variable_namein tasks) - Handler Support: Global
handlers.ymlfile at repository root for reusable handlers - Template Processing: Supports Jinja2 templates for dynamic configuration
- User Services: Manages per-user container services with proper ownership
- Service Auto-start: Automatically starts and enables container services
- Authentication Support: HTTPS and SSH authentication for private repositories
- Dual operational modes for different update frequencies
- Systemd timer integration for reliable scheduling
- Comprehensive logging with timestamped log files
- Self-healing playbook and inventory creation
- RPM packaging for easy deployment on RHEL/CentOS/Fedora
-
Build the RPM:
make rpm
-
Install the package:
sudo dnf install rpmbuild/RPMS/noarch/ansible-periodic-service-*.rpm -
Configure git repository (see Configuration section below)
-
Enable and start the timers:
sudo systemctl enable --now ansible-periodic.timer sudo systemctl enable --now ansible-periodic-full.timer
If you prefer manual installation, copy the systemd unit files to /usr/lib/systemd/system/ and the script to /usr/libexec/ansible-periodic/.
Once installed and enabled, the service runs automatically:
- Changes mode: Every 15 minutes (with 3-minute randomized delay)
- Full mode: Weekly on Sunday at 2 AM (with 2-hour randomized delay)
Run specific modes manually:
# Run changes mode
sudo systemctl start ansible-periodic@changes.service
# Run full mode
sudo systemctl start ansible-periodic@full.serviceCheck service status:
sudo systemctl status ansible-periodic.timer
sudo systemctl status ansible-periodic-full.timerView logs:
# Systemd journal
sudo journalctl -u ansible-periodic@changes.service
sudo journalctl -u ansible-periodic@full.service
# Application logs
sudo tail -f /var/log/ansible-periodic/ansible-periodic-*.logThe service automatically pulls configuration from a git repository. Configure this in /etc/ansible-periodic/ansible-periodic.conf:
# REQUIRED: Set your git repository URL
GIT_REPO_URL="https://github.com/yourusername/ansible-configs.git"
GIT_REPO_BRANCH="main"
# Authentication (choose one method):
# Method 1: HTTPS with username/password or personal access token
GIT_USERNAME="yourusername"
GIT_PASSWORD="your_token_here"
# Method 2: SSH with private key
# GIT_SSH_KEY="/var/lib/ansible-periodic/.ssh/id_rsa"
# Method 3: Public repository (no authentication needed)
# Leave GIT_USERNAME and GIT_PASSWORD emptyAuthentication Examples:
For GitHub with personal access token:
GIT_REPO_URL="https://github.com/yourusername/ansible-configs.git"
GIT_USERNAME="yourusername"
GIT_PASSWORD="ghp_your_personal_access_token"For GitLab with deploy token:
GIT_REPO_URL="https://gitlab.com/yourusername/ansible-configs.git"
GIT_USERNAME="gitlab+deploy-token-123"
GIT_PASSWORD="your_deploy_token"For SSH access:
GIT_REPO_URL="git@github.com:yourusername/ansible-configs.git"
GIT_SSH_KEY="/var/lib/ansible-periodic/.ssh/id_rsa"Important Notes:
- The
/var/ansible-repo/directory is automatically managed by git operations - Content is pulled fresh on each run (changes mode detects modified directories)
- SSH keys should be readable by the root user (service runs as root)
- For private repositories, ensure authentication is properly configured
/usr/libexec/ansible-periodic/- Main execution script/usr/share/ansible-periodic/playbooks/- Main playbook (main.yml)/etc/ansible-periodic/- Configuration files and inventoryansible-periodic.conf- Main configuration filehosts- Ansible inventory (auto-created)
/var/lib/ansible-periodic/- Runtime data/var/log/ansible-periodic/- Log files
-
Edit configuration at
/etc/ansible-periodic/ansible-periodic.conf:- Modify paths, playbook names, and Ansible settings
- Enable/disable auto-creation of missing files
-
Set up your git repository with the following structure:
- Create
task.ymlfiles in subdirectories for application tasks - Add
system-quadlets/directories with Podman container configs - Add
user-quadlets/USERNAME/directories for user-specific containers - Include
vars.ymlfiles for variables (global and per-directory) - Use
.j2templates for dynamic configuration
- Create
-
Modify inventory at
/etc/ansible-periodic/hosts -
Create git repository structure (minimal example):
your-ansible-repo/ ├── vars.yml # Global variables (optional) ├── config/ # This folder can be named anything │ ├── task.yml # task.yml files are what is run │ ├── vars.yml # vars specific for this context (optional) ├── config2/ │ ├── task.yml ├── etc..This repository will be automatically cloned to
/var/ansible-repo/during service execution. -
Using variables from vars.yml files:
- In
task.ymlfiles: Access variables viaenv_vars.variable_name- name: Use variable in task command: echo "{{ env_vars.database_url }}" loop: "{{ env_vars.zerotier_networks }}"
- In
.j2template files: Access directly asvariable_nameor viaenv_vars.variable_nameEnvironment=DATABASE_URL={{ database_url }} # or Environment=DATABASE_URL={{ env_vars.database_url }}
- In
-
Using handlers from handlers.yml:
- Create a
handlers.ymlfile at the repository root with your handlers:--- - name: restart gdm systemd: name: gdm state: restarted - name: restart nginx systemd: name: nginx state: restarted
- Notify handlers from any
task.ymlfile:- name: Update configuration template: src: config.j2 dest: /etc/app/config notify: restart nginx
- Handlers execute at the end of the playbook run (or use
meta: flush_handlersfor immediate execution)
- Create a
-
Adjust schedules using systemd override files (see Customizing Schedules below)
Instead of editing the package-installed timer files directly, use systemd override files to customize schedules. This ensures your changes persist through package upgrades.
# Customize the changes timer (runs every 15 minutes by default)
sudo systemctl edit ansible-periodic.timer
# Customize the full timer (runs weekly on Sunday at 2 AM by default)
sudo systemctl edit ansible-periodic-full.timerThis opens an editor where you can add override settings:
[Timer]
# Change changes mode to run every 5 minutes instead of 15
OnUnitActiveSec=5min
OnBootSec=2min
# Reduce randomized delay
RandomizedDelaySec=1minCreate override directories and files manually:
# Create override directory for changes timer
sudo mkdir -p /etc/systemd/system/ansible-periodic.timer.d
# Create override configuration
sudo tee /etc/systemd/system/ansible-periodic.timer.d/schedule.conf << EOF
[Timer]
# Run every 10 minutes instead of 15
OnUnitActiveSec=10min
OnBootSec=3min
RandomizedDelaySec=2min
EOF
# Create override for full timer
sudo mkdir -p /etc/systemd/system/ansible-periodic-full.timer.d
sudo tee /etc/systemd/system/ansible-periodic-full.timer.d/schedule.conf << EOF
[Timer]
# Run daily at 2 AM instead of weekly on Sunday
OnCalendar=*-*-* 02:00:00
# Start 15 minutes after boot instead of 30
OnBootSec=15min
# Reduce randomized delay to 1 hour
RandomizedDelaySec=1h
EOFAfter creating override files, reload systemd and restart timers:
sudo systemctl daemon-reload
sudo systemctl restart ansible-periodic.timer
sudo systemctl restart ansible-periodic-full.timerCheck what settings are actually being used:
# Show effective timer configuration
systemctl cat ansible-periodic.timer
systemctl cat ansible-periodic-full.timer
# Show timer status and next run times
systemctl list-timers ansible-periodic*More Frequent Changes Mode:
[Timer]
OnUnitActiveSec=5min
OnBootSec=1min
RandomizedDelaySec=30sDifferent Daily Time:
[Timer]
OnCalendar=*-*-* 01:30:00
OnBootSec=20min
RandomizedDelaySec=30minWeekly Full Runs:
[Timer]
OnCalendar=Sun *-*-* 03:00:00
OnBootSec=30min
RandomizedDelaySec=2hDisable a Timer:
[Timer]
OnCalendar=
OnUnitActiveSec=
OnBootSec=To remove customizations and return to package defaults:
# Remove specific override files
sudo rm -rf /etc/systemd/system/ansible-periodic.timer.d
sudo rm -rf /etc/systemd/system/ansible-periodic-full.timer.d
# Or use systemctl revert
sudo systemctl revert ansible-periodic.timer
sudo systemctl revert ansible-periodic-full.timer
# Reload and restart
sudo systemctl daemon-reload
sudo systemctl restart ansible-periodic.timer
sudo systemctl restart ansible-periodic-full.timer# Install build dependencies
sudo dnf install rpm-build rpmlint
# Build RPM
make rpm
# Clean build artifacts
make cleanansible-periodic-service.spec- RPM specificationansible-periodic@.service- Parameterized systemd serviceansible-periodic.timer- Timer for changes modeansible-periodic-full.timer- Timer for full moderun-ansible-periodic.sh- Main execution scriptMakefile- Build automation
MIT License - see LICENSE file for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Test the RPM build and installation
- Submit a pull request