Learn to install Ansible on Linux, configure secure SSH authentication, create production-ready playbooks with Vault encryption, and implement roles for scalable infrastructure automation.
Prerequisites
- Multiple Linux servers with SSH access
- Basic understanding of YAML syntax
- Root or sudo access on managed hosts
What this solves
Ansible automates server configuration, application deployment, and infrastructure management without requiring agents on target systems. This tutorial shows you how to set up a secure Ansible control node with SSH key authentication, encrypted secrets management using Ansible Vault, and reusable roles for consistent automation across your infrastructure.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions and security patches.
sudo apt update && sudo apt upgrade -y
Install Python and pip dependencies
Ansible requires Python 3.8+ and pip for package management. Install the required dependencies for your distribution.
sudo apt install -y python3 python3-pip python3-venv software-properties-common
Install Ansible using pip
Install Ansible using pip to get the latest stable version with all features. This method works consistently across all distributions.
python3 -m pip install --user ansible ansible-core
echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.bashrc
source ~/.bashrc
Verify Ansible installation
Check that Ansible is installed correctly and view the version information.
ansible --version
ansible-playbook --version
Generate SSH key for authentication
Create an SSH key pair for secure, passwordless authentication to managed hosts. Use ED25519 for better security and performance.
ssh-keygen -t ed25519 -C "ansible-control-$(whoami)@$(hostname)" -f ~/.ssh/ansible_ed25519
chmod 600 ~/.ssh/ansible_ed25519
chmod 644 ~/.ssh/ansible_ed25519.pub
Create Ansible directory structure
Set up a proper directory structure following Ansible best practices for organizing playbooks, roles, and inventory files.
mkdir -p ~/ansible/{playbooks,roles,inventory,group_vars,host_vars}
cd ~/ansible
Configure Ansible settings
Create a configuration file to set secure defaults and optimize Ansible behavior for your environment.
[defaults]
inventory = ./inventory/hosts.yml
remote_user = ansible
private_key_file = ~/.ssh/ansible_ed25519
host_key_checking = False
timeout = 30
forks = 10
log_path = ./ansible.log
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null
pipelining = True
Create inventory file
Define your managed hosts using YAML inventory format with groups for better organization and variable management.
all:
children:
webservers:
hosts:
web01:
ansible_host: 203.0.113.10
web02:
ansible_host: 203.0.113.11
vars:
ansible_user: ansible
http_port: 80
databases:
hosts:
db01:
ansible_host: 203.0.113.20
vars:
ansible_user: ansible
db_port: 5432
Set up managed host user
Create a dedicated ansible user on each managed host with sudo privileges. Run these commands on each target server.
sudo useradd -m -s /bin/bash ansible
sudo mkdir /home/ansible/.ssh
sudo chmod 700 /home/ansible/.ssh
echo "ansible ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/ansible
Copy SSH key to managed hosts
Deploy your public key to each managed host for passwordless authentication. Replace the IP addresses with your actual server IPs.
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub ansible@203.0.113.10
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub ansible@203.0.113.11
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub ansible@203.0.113.20
Test inventory connectivity
Verify that Ansible can connect to all managed hosts using the ping module.
ansible all -m ping
ansible webservers -m setup --limit web01
Create your first playbook
Create a basic system update playbook
Write a simple playbook that updates packages across all managed systems with proper error handling and notifications.
---
- name: Update system packages
hosts: all
become: yes
vars:
max_fail_percentage: 10
tasks:
- name: Update package cache (Ubuntu/Debian)
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Update package cache (RHEL/CentOS)
dnf:
update_cache: yes
when: ansible_os_family == "RedHat"
- name: Upgrade all packages (Ubuntu/Debian)
apt:
upgrade: dist
autoremove: yes
when: ansible_os_family == "Debian"
register: apt_upgrade
- name: Upgrade all packages (RHEL/CentOS)
dnf:
name: "*"
state: latest
when: ansible_os_family == "RedHat"
register: dnf_upgrade
- name: Check if reboot required (Ubuntu/Debian)
stat:
path: /var/run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
- name: Notify when reboot needed
debug:
msg: "Reboot required on {{ inventory_hostname }}"
when: reboot_required is defined and reboot_required.stat.exists
Create group variables
Define variables specific to server groups for consistent configuration management.
---
Web server specific variables
nginx_version: latest
http_port: 80
https_port: 443
max_clients: 200
Security settings
firewall_allowed_ports:
- 22
- 80
- 443
Backup settings
backup_enabled: true
backup_retention_days: 7
Run your first playbook
Execute the playbook with verbose output to see detailed information about each task execution.
ansible-playbook playbooks/system-update.yml --check --diff
ansible-playbook playbooks/system-update.yml -v
Implement Ansible Vault for secrets
Create encrypted variables file
Use Ansible Vault to securely store sensitive information like passwords, API keys, and certificates.
ansible-vault create group_vars/all/vault.yml
Enter a strong vault password when prompted, then add your secrets:
---
vault_database_password: "SecureDbPass2024!"
vault_api_key: "abc123def456ghi789"
vault_ssl_private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
-----END PRIVATE KEY-----
Create vault password file
Store your vault password securely for automated playbook execution. Protect this file with strict permissions.
echo "YourVaultPasswordHere2024" > ~/.ansible-vault-pass
chmod 600 ~/.ansible-vault-pass
Update Ansible configuration for vault
Configure Ansible to automatically use your vault password file for decryption.
[defaults]
inventory = ./inventory/hosts.yml
remote_user = ansible
private_key_file = ~/.ssh/ansible_ed25519
host_key_checking = False
timeout = 30
forks = 10
log_path = ./ansible.log
vault_password_file = ~/.ansible-vault-pass
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null
pipelining = True
Create playbook using vault variables
Demonstrate using encrypted variables in a playbook for secure database configuration.
---
- name: Deploy application with secure credentials
hosts: webservers
become: yes
vars:
app_database_password: "{{ vault_database_password }}"
app_api_key: "{{ vault_api_key }}"
tasks:
- name: Create application config directory
file:
path: /etc/myapp
state: directory
owner: root
group: root
mode: '0755'
- name: Deploy application configuration
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
owner: root
group: myapp
mode: '0640'
notify: restart myapp
handlers:
- name: restart myapp
systemd:
name: myapp
state: restarted
Create reusable roles
Generate role structure
Create a standardized role structure for installing and configuring NGINX with security best practices.
cd ~/ansible
ansible-galaxy init roles/nginx
Define role tasks
Create the main tasks for the NGINX role with proper error handling and idempotency.
---
- name: Install NGINX (Ubuntu/Debian)
apt:
name: nginx
state: "{{ nginx_version | default('present') }}"
update_cache: yes
when: ansible_os_family == "Debian"
notify: restart nginx
- name: Install NGINX (RHEL/CentOS)
dnf:
name: nginx
state: "{{ nginx_version | default('present') }}"
when: ansible_os_family == "RedHat"
notify: restart nginx
- name: Create nginx directories
file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: '0755'
loop:
- /etc/nginx/sites-available
- /etc/nginx/sites-enabled
- /var/log/nginx
- /var/www/html
- name: Deploy NGINX main configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
notify: restart nginx
- name: Deploy default site configuration
template:
src: default.conf.j2
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: '0644'
notify: restart nginx
- name: Enable default site
file:
src: /etc/nginx/sites-available/default
dest: /etc/nginx/sites-enabled/default
state: link
notify: restart nginx
- name: Start and enable NGINX
systemd:
name: nginx
state: started
enabled: yes
daemon_reload: yes
Create role handlers
Define handlers for service management and configuration validation.
---
- name: restart nginx
systemd:
name: nginx
state: restarted
listen: restart nginx
- name: reload nginx
systemd:
name: nginx
state: reloaded
listen: reload nginx
Define role variables
Set default variables that can be overridden by users of the role.
---
NGINX version
nginx_version: present
Worker processes
nginx_worker_processes: auto
nginx_worker_connections: 1024
Security headers
nginx_security_headers:
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: "1; mode=block"
Referrer-Policy: strict-origin-when-cross-origin
Rate limiting
nginx_rate_limit: "10r/s"
SSL settings
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
Create role templates
Create a Jinja2 template for the NGINX configuration with security hardening.
user www-data;
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Security headers
{% for header, value in nginx_security_headers.items() %}
add_header {{ header }} "{{ value }}" always;
{% endfor %}
# Hide server version
server_tokens off;
# Rate limiting
limit_req_zone $binary_remote_addr zone=global:10m rate={{ nginx_rate_limit }};
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
include /etc/nginx/sites-enabled/*;
}
Create playbook using the role
Create a playbook that uses your custom role to deploy NGINX across web servers.
---
- name: Configure web servers
hosts: webservers
become: yes
vars:
nginx_worker_processes: 2
nginx_rate_limit: "20r/s"
roles:
- role: nginx
tags: ['nginx', 'webserver']
post_tasks:
- name: Ensure firewall allows HTTP traffic
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- '80'
- '443'
when: ansible_os_family == "Debian"
- name: Open firewall ports (RHEL/CentOS)
firewalld:
port: "{{ item }}/tcp"
permanent: yes
state: enabled
immediate: yes
loop:
- '80'
- '443'
when: ansible_os_family == "RedHat"
Run playbook with roles
Execute the playbook to deploy your role across the web servers with specific tags for selective execution.
ansible-playbook playbooks/webserver-setup.yml --check
ansible-playbook playbooks/webserver-setup.yml --tags nginx
ansible-playbook playbooks/webserver-setup.yml
Security hardening and best practices
Create security hardening role
Implement a comprehensive security role that can be applied to all managed hosts.
ansible-galaxy init roles/security-hardening
Implement security tasks
Create security hardening tasks including user management, SSH configuration, and system updates. You can integrate this with Fail2ban for additional protection.
---
- name: Update all packages to latest version
package:
name: "*"
state: latest
when: security_update_packages | default(true)
- name: Remove unnecessary packages
package:
name: "{{ security_packages_remove | default([]) }}"
state: absent
- name: Install security packages
package:
name: "{{ security_packages_install }}"
state: present
- name: Secure SSH configuration
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
backup: yes
loop:
- { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
- { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
- { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
- { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 2' }
notify: restart sshd
- name: Set proper permissions on sensitive files
file:
path: "{{ item.path }}"
mode: "{{ item.mode }}"
owner: "{{ item.owner | default('root') }}"
group: "{{ item.group | default('root') }}"
loop:
- { path: '/etc/ssh/sshd_config', mode: '0644' }
- { path: '/etc/passwd', mode: '0644' }
- { path: '/etc/shadow', mode: '0640', group: 'shadow' }
- { path: '/etc/gshadow', mode: '0640', group: 'shadow' }
- name: Configure automatic security updates
template:
src: 20auto-upgrades.j2
dest: /etc/apt/apt.conf.d/20auto-upgrades
mode: '0644'
when: ansible_os_family == "Debian" and security_auto_updates | default(true)
- name: Enable and configure auditd
systemd:
name: auditd
enabled: yes
state: started
when: security_enable_audit | default(true)
Create comprehensive site playbook
Combine multiple roles into a site-wide playbook for complete infrastructure deployment.
---
- name: Apply security hardening to all hosts
hosts: all
become: yes
roles:
- role: security-hardening
tags: ['security', 'hardening']
- name: Configure web servers
hosts: webservers
become: yes
roles:
- role: nginx
tags: ['nginx', 'webserver']
- name: Configure database servers
hosts: databases
become: yes
vars:
postgresql_version: 15
tasks:
- name: Placeholder for database configuration
debug:
msg: "Database configuration would go here"
tags: ['database']
Set up Ansible collections
Install useful collections from Ansible Galaxy for extended functionality and cloud integrations.
ansible-galaxy collection install community.general
ansible-galaxy collection install ansible.posix
ansible-galaxy collection install community.crypto
ansible-galaxy collection list
Create requirements file
Document all required roles and collections for consistent deployment across environments.
---
collections:
- name: community.general
version: ">=5.0.0"
- name: ansible.posix
version: ">=1.4.0"
- name: community.crypto
version: ">=2.0.0"
roles:
- name: geerlingguy.security
src: https://github.com/geerlingguy/ansible-role-security
- name: nginxinc.nginx
src: https://github.com/nginxinc/ansible-role-nginx
Verify your setup
Test your complete Ansible configuration to ensure everything works correctly.
ansible --version
ansible all -m ping
ansible-playbook site.yml --check --diff
ansible-inventory --list
ansible-vault --help
ansible-doc -l | grep -i user
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| SSH connection refused | Wrong SSH key or user | Verify key with ssh -i ~/.ssh/ansible_ed25519 ansible@host |
| Permission denied (sudo) | User lacks sudo privileges | Add user to sudoers: echo "ansible ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/ansible |
| Vault decryption failed | Wrong vault password | Check password file permissions: chmod 600 ~/.ansible-vault-pass |
| Host key verification failed | SSH host key changed | Remove old key: ssh-keygen -R hostname |
| Playbook syntax error | YAML indentation issues | Validate syntax: ansible-playbook playbook.yml --syntax-check |
| Module not found | Missing collection | Install collection: ansible-galaxy collection install collection.name |
Next steps
- Set up Consul for service discovery to complement your Ansible infrastructure automation
- Deploy HashiCorp Vault for enterprise secrets management beyond Ansible Vault
- Configure Ansible AWX/Tower for enterprise automation
- Implement Ansible testing with Molecule and TestInfra
- Configure dynamic inventory for AWS, Azure, and GCP
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
warn() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" >&2
}
# Usage message
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Install and configure Ansible with security best practices
OPTIONS:
-u, --user USER Create ansible user (default: ansible)
-k, --key-comment SSH key comment suffix (default: hostname)
-h, --help Show this help message
Examples:
$0
$0 --user automation
$0 --user ansible --key-comment "production-control"
EOF
}
# Default values
ANSIBLE_USER="ansible"
KEY_COMMENT=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-u|--user)
ANSIBLE_USER="$2"
shift 2
;;
-k|--key-comment)
KEY_COMMENT="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Cleanup function for rollback
cleanup() {
warn "Installation failed. Cleaning up..."
rm -f /tmp/ansible_install.lock
if [[ -d "$HOME/.ansible_backup" ]]; then
log "Restoring previous configuration..."
rm -rf "$HOME/ansible" "$HOME/.ansible"
mv "$HOME/.ansible_backup" "$HOME/ansible" 2>/dev/null || true
fi
}
trap cleanup ERR
# Check prerequisites
check_prerequisites() {
log "[1/10] Checking prerequisites..."
if [[ $EUID -eq 0 ]]; then
error "This script should not be run as root for security reasons"
exit 1
fi
if ! command -v sudo >/dev/null 2>&1; then
error "sudo is required but not installed"
exit 1
fi
# Check if user has sudo access
if ! sudo -n true 2>/dev/null; then
warn "This script requires sudo privileges. You may be prompted for your password."
fi
# Create lock file
touch /tmp/ansible_install.lock
}
# Detect distribution
detect_distro() {
log "[2/10] Detecting distribution..."
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect distribution - /etc/os-release not found"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
PYTHON_PKG="python3 python3-pip python3-venv software-properties-common"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PYTHON_PKG="python3 python3-pip python3-virtualenv"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PYTHON_PKG="python3 python3-pip python3-virtualenv"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
PYTHON_PKG="python3 python3-pip"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
log "Detected: $PRETTY_NAME using $PKG_MGR"
}
# Update system packages
update_system() {
log "[3/10] Updating system packages..."
sudo $PKG_UPDATE
}
# Install Python dependencies
install_python() {
log "[4/10] Installing Python and pip dependencies..."
sudo $PKG_INSTALL $PYTHON_PKG
# Verify Python version
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
PYTHON_MAJOR=$(echo $PYTHON_VERSION | cut -d'.' -f1)
PYTHON_MINOR=$(echo $PYTHON_VERSION | cut -d'.' -f2)
if [[ $PYTHON_MAJOR -lt 3 ]] || [[ $PYTHON_MAJOR -eq 3 && $PYTHON_MINOR -lt 8 ]]; then
error "Python 3.8+ is required. Found: $PYTHON_VERSION"
exit 1
fi
log "Python $PYTHON_VERSION verified"
}
# Install Ansible
install_ansible() {
log "[5/10] Installing Ansible..."
# Backup existing ansible directory if it exists
if [[ -d "$HOME/ansible" ]]; then
warn "Backing up existing ansible directory"
mv "$HOME/ansible" "$HOME/.ansible_backup"
fi
python3 -m pip install --user ansible ansible-core
# Add to PATH if not already there
if ! grep -q '$HOME/.local/bin' "$HOME/.bashrc"; then
echo 'export PATH=$PATH:$HOME/.local/bin' >> "$HOME/.bashrc"
fi
export PATH=$PATH:$HOME/.local/bin
}
# Generate SSH key
generate_ssh_key() {
log "[6/10] Generating SSH key for authentication..."
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
local comment_suffix="${KEY_COMMENT:-$(hostname)}"
local key_path="$HOME/.ssh/ansible_ed25519"
if [[ -f "$key_path" ]]; then
warn "SSH key already exists at $key_path"
return 0
fi
ssh-keygen -t ed25519 -C "ansible-control-$(whoami)@${comment_suffix}" -f "$key_path" -N ""
chmod 600 "$key_path"
chmod 644 "${key_path}.pub"
log "SSH key generated: $key_path"
}
# Create directory structure
create_directory_structure() {
log "[7/10] Creating Ansible directory structure..."
mkdir -p "$HOME/ansible"/{playbooks,roles,inventory,group_vars,host_vars}
chmod 755 "$HOME/ansible"
find "$HOME/ansible" -type d -exec chmod 755 {} \;
cd "$HOME/ansible"
}
# Configure Ansible
configure_ansible() {
log "[8/10] Configuring Ansible settings..."
cat > "$HOME/ansible/ansible.cfg" << EOF
[defaults]
inventory = ./inventory/hosts.yml
remote_user = $ANSIBLE_USER
private_key_file = ~/.ssh/ansible_ed25519
host_key_checking = False
timeout = 30
forks = 10
log_path = ./ansible.log
gathering = smart
fact_caching = memory
retry_files_enabled = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
pipelining = True
control_path = ~/.ssh/ansible-%%r@%%h:%%p
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
EOF
chmod 644 "$HOME/ansible/ansible.cfg"
}
# Create sample inventory
create_sample_inventory() {
log "[9/10] Creating sample inventory file..."
cat > "$HOME/ansible/inventory/hosts.yml" << EOF
# Sample Ansible inventory file
# Replace with your actual server details
all:
children:
webservers:
hosts:
web01:
ansible_host: 203.0.113.10
web02:
ansible_host: 203.0.113.11
vars:
ansible_user: $ANSIBLE_USER
http_port: 80
databases:
hosts:
db01:
ansible_host: 203.0.113.20
vars:
ansible_user: $ANSIBLE_USER
db_port: 5432
EOF
chmod 644 "$HOME/ansible/inventory/hosts.yml"
# Create sample playbook
cat > "$HOME/ansible/playbooks/system-update.yml" << EOF
---
- name: Update system packages
hosts: all
become: true
tasks:
- name: Update package cache (Debian/Ubuntu)
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Update all packages (Debian/Ubuntu)
ansible.builtin.apt:
upgrade: dist
autoremove: true
autoclean: true
when: ansible_os_family == "Debian"
- name: Update all packages (RHEL/CentOS)
ansible.builtin.dnf:
name: "*"
state: latest
update_cache: true
when: ansible_os_family == "RedHat"
EOF
chmod 644 "$HOME/ansible/playbooks/system-update.yml"
}
# Verify installation
verify_installation() {
log "[10/10] Verifying Ansible installation..."
if ! command -v ansible >/dev/null 2>&1; then
error "Ansible command not found in PATH"
exit 1
fi
if ! command -v ansible-playbook >/dev/null 2>&1; then
error "ansible-playbook command not found in PATH"
exit 1
fi
log "Ansible version: $(ansible --version | head -n1)"
log "Ansible configuration file: $HOME/ansible/ansible.cfg"
log "SSH key location: $HOME/.ssh/ansible_ed25519"
# Test basic ansible functionality
cd "$HOME/ansible"
if ansible localhost -m ping -c local &>/dev/null; then
log "Basic Ansible functionality verified"
else
warn "Basic ping test failed - this is normal if no inventory is configured"
fi
# Clean up
rm -f /tmp/ansible_install.lock
rm -rf "$HOME/.ansible_backup" 2>/dev/null || true
}
# Main execution
main() {
log "Starting Ansible installation with security best practices"
check_prerequisites
detect_distro
update_system
install_python
install_ansible
generate_ssh_key
create_directory_structure
configure_ansible
create_sample_inventory
verify_installation
log "${GREEN}Ansible installation completed successfully!${NC}"
echo
echo -e "${BLUE}Next steps:${NC}"
echo "1. Edit $HOME/ansible/inventory/hosts.yml with your server details"
echo "2. Copy SSH key to managed hosts:"
echo " ssh-copy-id -i ~/.ssh/ansible_ed25519.pub $ANSIBLE_USER@<target-host>"
echo "3. Test connectivity: cd ~/ansible && ansible all -m ping"
echo "4. Run your first playbook: ansible-playbook playbooks/system-update.yml"
echo
echo -e "${YELLOW}Security reminders:${NC}"
echo "- Create dedicated '$ANSIBLE_USER' user on target hosts with NOPASSWD sudo"
echo "- Use Ansible Vault for sensitive data: ansible-vault create secrets.yml"
echo "- Regularly rotate SSH keys and review access permissions"
}
main "$@"
Review the script before running. Execute with: bash install.sh