Add common role, scripts, and documentation

- ansible.cfg: Set remote_user to dlxadmin
- inventory: Add infrastructure, application hosts with IPs
- group_vars/all.yml: Set ansible_user to dlxadmin
- playbooks/site.yml: Enable common role
- roles/common: Baseline configuration role
  - Package installation (Debian/RedHat/Arch)
  - Timezone and locale setup
  - User management with SSH keys
  - SSH hardening
  - UFW firewall and security settings
- scripts/create-user.sh: Create ansible user on servers
- USAGE.md: Project usage documentation
- HOSTS.md: Infrastructure host inventory

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
directlx 2026-02-04 08:33:36 -05:00
parent 35d6965fab
commit 94be59bb26
17 changed files with 621 additions and 5 deletions

63
HOSTS.md Normal file
View File

@ -0,0 +1,63 @@
# Infrastructure Hosts
## Summary
| Host | IP | OS | Group |
|------|-----|-----|-------|
| ansible-node | 192.168.200.106 | Debian 18.0 | control |
| postgres | 192.168.200.103 | Debian 18.1 | dbservers |
| mysql | 192.168.200.110 | Debian 18.1 | dbservers |
| mongo | 192.168.200.111 | Ubuntu 24.10 | dbservers |
| nginx | 192.168.200.65 | Ubuntu 24.04 | webservers |
| npm | 192.168.200.71 | Debian 12.12 | webservers |
| docker | 192.168.200.200 | Ubuntu 25.04 | infrastructure |
| pihole | 192.168.200.100 | Ubuntu 24.04 | infrastructure |
| gitea | 192.168.200.102 | Debian 18.0 | infrastructure |
| jenkins | 192.168.200.91 | Ubuntu 24.04 | infrastructure |
| hiveops | 192.168.200.112 | Ubuntu 24.04 | application |
| smartjournal | 192.168.200.114 | Ubuntu 24.04 | application |
| odoo | 192.168.200.61 | Debian 18.0 | application |
| localhost | - | Pop!_OS 24.04 | local |
## Groups
### control
- ansible-node (192.168.200.106)
### dbservers
- postgres (192.168.200.103)
- mysql (192.168.200.110)
- mongo (192.168.200.111)
### webservers
- nginx (192.168.200.65)
- npm (192.168.200.71)
### infrastructure
- docker (192.168.200.200)
- pihole (192.168.200.100)
- gitea (192.168.200.102)
- jenkins (192.168.200.91)
### application
- hiveops (192.168.200.112)
- smartjournal (192.168.200.114)
- odoo (192.168.200.61)
## Ansible User
All hosts use `dlxadmin` as the ansible user with passwordless sudo.
## Quick Commands
```bash
# Test all hosts
ansible-playbook playbooks/ping.yml
# Test specific group
ansible-playbook playbooks/ping.yml -l dbservers
# Run ad-hoc command
ansible all -a "uptime"
ansible dbservers -a "df -h"
```

175
USAGE.md Normal file
View File

@ -0,0 +1,175 @@
# Ansible Project Usage
## Quick Start
### 1. Setup SSH Access
```bash
# For existing user
./scripts/setup-ssh.sh <ip_address> <username>
# Create new user via admin account
./scripts/setup-ssh.sh <ip_address> <username> <admin_user>
# Examples
./scripts/setup-ssh.sh 192.168.200.103 ansible
./scripts/setup-ssh.sh 192.168.200.103 ansible root
```
### 2. Test Connectivity
```bash
# Test all hosts
ansible-playbook playbooks/ping.yml
# Test specific group
ansible-playbook playbooks/ping.yml -l dbservers
# Test single host
ansible-playbook playbooks/ping.yml -l postgres
# Quick ping (no playbook)
ansible all -m ping
```
### 3. Run Playbooks
```bash
# Apply common configuration to all hosts
ansible-playbook playbooks/site.yml
# Limit to specific group
ansible-playbook playbooks/site.yml -l dbservers
# Limit to specific host
ansible-playbook playbooks/site.yml -l postgres
# Dry run (check mode)
ansible-playbook playbooks/site.yml --check
# Run specific tags only
ansible-playbook playbooks/site.yml --tags packages
ansible-playbook playbooks/site.yml --tags security
ansible-playbook playbooks/site.yml --tags ssh
```
## Inventory
Hosts are defined in `inventory/hosts.yml`:
| Group | Host | IP |
|----------------|--------------|-----------------|
| control | ansible-node | 192.168.200.106 |
| dbservers | postgres | 192.168.200.103 |
| dbservers | mysql | 192.168.200.110 |
| dbservers | mongo | 192.168.200.111 |
| webservers | nginx | 192.168.200.65 |
| webservers | npm | 192.168.200.101 |
| infrastructure | docker | 192.168.200.200 |
| infrastructure | pihole | 192.168.200.100 |
### Target Hosts
```bash
# All hosts
ansible-playbook playbooks/site.yml
# By group
ansible-playbook playbooks/site.yml -l dbservers
ansible-playbook playbooks/site.yml -l webservers
ansible-playbook playbooks/site.yml -l infrastructure
# Multiple groups
ansible-playbook playbooks/site.yml -l "dbservers:webservers"
# Single host
ansible-playbook playbooks/site.yml -l postgres
```
## Common Role
The `common` role applies baseline configuration to all hosts.
### Features
- **Packages**: curl, wget, vim, htop, git, unzip, net-tools, tree, jq
- **Timezone**: Configurable (default: UTC)
- **SSH Hardening**: Disable root login, password auth, limit auth tries
- **Firewall**: UFW with configurable allowed ports
- **Auto Updates**: Unattended security upgrades
- **User Management**: Create users with SSH keys and sudo access
### Configuration
Override defaults in `group_vars/` or `host_vars/`:
```yaml
# group_vars/dbservers.yml
common_timezone: "America/New_York"
common_extra_packages:
- postgresql-client
common_firewall_allowed_ports:
- "22/tcp"
- "5432/tcp"
common_users:
- name: deploy
groups: ['sudo']
passwordless_sudo: true
ssh_keys:
- "ssh-ed25519 AAAA..."
```
### Available Tags
| Tag | Description |
|-----------|--------------------------------|
| packages | Install common packages |
| timezone | Set timezone and locale |
| users | Create users and SSH keys |
| ssh | SSH daemon hardening |
| security | Firewall, sysctl, auto-updates |
## Ad-hoc Commands
```bash
# Run command on all hosts
ansible all -a "uptime"
# Run command on group
ansible dbservers -a "df -h"
# Run with sudo
ansible all -b -a "apt update"
# Copy file
ansible all -m copy -a "src=/local/file dest=/remote/file"
# Install package
ansible dbservers -b -m apt -a "name=htop state=present"
# Restart service
ansible webservers -b -m service -a "name=nginx state=restarted"
```
## Directory Structure
```
dlx-ansible/
├── ansible.cfg # Ansible configuration
├── inventory/
│ └── hosts.yml # Host inventory
├── playbooks/
│ ├── site.yml # Main playbook
│ └── ping.yml # Connectivity test
├── roles/
│ └── common/ # Common baseline role
├── group_vars/
│ └── all.yml # Variables for all hosts
├── host_vars/ # Per-host variables
├── files/ # Static files
├── templates/ # Jinja2 templates
└── scripts/
└── setup-ssh.sh # SSH setup script
```

View File

@ -1,6 +1,7 @@
[defaults]
inventory = inventory/hosts.yml
roles_path = roles
remote_user = dlxadmin
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml

View File

@ -1,4 +1,3 @@
---
# Variables applied to all hosts
# ansible_user: your_ssh_user
# ansible_become: true
ansible_user: dlxadmin

View File

@ -20,7 +20,7 @@ all:
nginx:
ansible_host: 192.168.200.65
npm:
ansible_host: 192.168.200.101
ansible_host: 192.168.200.71
infrastructure:
hosts:
@ -28,8 +28,22 @@ all:
ansible_host: 192.168.200.200
pihole:
ansible_host: 192.168.200.100
gitea:
ansible_host: 192.168.200.102
jenkins:
ansible_host: 192.168.200.91
application:
hosts:
hiveops:
ansible_host: 192.168.200.112
smartjournal:
ansible_host: 192.168.200.114
odoo:
ansible_host: 192.168.200.61
local:
hosts:
localhost:
ansible_connection: local
ansible_become: false

View File

@ -2,8 +2,8 @@
# Main site playbook - orchestrates all plays
- name: Apply common configuration
hosts: all
roles: []
# - common
roles:
- common
# - name: Configure web servers
# hosts: webservers

View File

View File

@ -0,0 +1,53 @@
---
# Common role defaults
# Timezone
common_timezone: "UTC"
# Locale
common_locale: "en_US.UTF-8"
# Packages to install on all hosts
common_packages:
- curl
- wget
- vim
- htop
- git
- unzip
- net-tools
- tree
- jq
# Additional packages (override per host/group)
common_extra_packages: []
# SSH configuration
common_ssh_port: 22
common_ssh_permit_root_login: "no"
common_ssh_password_authentication: "no"
common_ssh_pubkey_authentication: "yes"
# Firewall (ufw for Debian/Ubuntu, firewalld for RHEL)
common_firewall_enabled: true
common_firewall_allowed_ports:
- "22/tcp"
# Automatic updates
common_auto_updates_enabled: true
# Users to create (override in group_vars/host_vars)
common_users: []
# Example:
# common_users:
# - name: deploy
# groups: ['sudo']
# shell: /bin/bash
# ssh_keys:
# - "ssh-ed25519 AAAA..."
# Sysctl settings
common_sysctl_settings:
net.ipv4.ip_forward: 0
net.ipv4.conf.all.send_redirects: 0
net.ipv4.conf.default.send_redirects: 0

View File

@ -0,0 +1,7 @@
---
# Common role handlers
- name: Restart sshd
ansible.builtin.service:
name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}"
state: restarted

View File

@ -0,0 +1,32 @@
---
# Common role - main tasks
- name: Include OS-specific variables
ansible.builtin.include_vars: "{{ item }}"
with_first_found:
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version }}.yml"
- "{{ ansible_distribution | lower }}.yml"
- "{{ ansible_os_family | lower }}.yml"
- "default.yml"
ignore_errors: true
- name: Run package tasks
ansible.builtin.include_tasks: packages.yml
tags: [packages]
- name: Run timezone tasks
ansible.builtin.include_tasks: timezone.yml
tags: [timezone]
- name: Run user tasks
ansible.builtin.include_tasks: users.yml
tags: [users]
when: common_users | length > 0
- name: Run SSH hardening tasks
ansible.builtin.include_tasks: ssh.yml
tags: [ssh, security]
- name: Run security tasks
ansible.builtin.include_tasks: security.yml
tags: [security]

View File

@ -0,0 +1,26 @@
---
# Package installation tasks
- name: Update apt cache (Debian/Ubuntu)
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install common packages (Debian/Ubuntu)
ansible.builtin.apt:
name: "{{ common_packages + common_extra_packages }}"
state: present
when: ansible_os_family == "Debian"
- name: Install common packages (RedHat/CentOS)
ansible.builtin.dnf:
name: "{{ common_packages + common_extra_packages }}"
state: present
when: ansible_os_family == "RedHat"
- name: Install common packages (Arch)
community.general.pacman:
name: "{{ common_packages + common_extra_packages }}"
state: present
when: ansible_os_family == "Archlinux"

View File

@ -0,0 +1,66 @@
---
# Security hardening tasks
- name: Apply sysctl settings
ansible.posix.sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
sysctl_set: true
state: present
reload: true
loop: "{{ common_sysctl_settings | dict2items }}"
- name: Install UFW (Debian/Ubuntu)
ansible.builtin.apt:
name: ufw
state: present
when:
- ansible_os_family == "Debian"
- common_firewall_enabled
- name: Configure UFW defaults
community.general.ufw:
direction: "{{ item.direction }}"
policy: "{{ item.policy }}"
loop:
- { direction: 'incoming', policy: 'deny' }
- { direction: 'outgoing', policy: 'allow' }
when:
- ansible_os_family == "Debian"
- common_firewall_enabled
- name: Allow firewall ports (UFW)
community.general.ufw:
rule: allow
port: "{{ item.split('/')[0] }}"
proto: "{{ item.split('/')[1] | default('tcp') }}"
loop: "{{ common_firewall_allowed_ports }}"
when:
- ansible_os_family == "Debian"
- common_firewall_enabled
- name: Enable UFW
community.general.ufw:
state: enabled
when:
- ansible_os_family == "Debian"
- common_firewall_enabled
- name: Install automatic security updates (Debian/Ubuntu)
ansible.builtin.apt:
name: unattended-upgrades
state: present
when:
- ansible_os_family == "Debian"
- common_auto_updates_enabled
- name: Enable automatic security updates (Debian/Ubuntu)
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
mode: '0644'
when:
- ansible_os_family == "Debian"
- common_auto_updates_enabled

View File

@ -0,0 +1,28 @@
---
# SSH hardening tasks
- name: Configure SSH daemon
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
validate: 'sshd -t -f %s'
loop:
- regexp: "^#?Port"
line: "Port {{ common_ssh_port }}"
- regexp: "^#?PermitRootLogin"
line: "PermitRootLogin {{ common_ssh_permit_root_login }}"
- regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication {{ common_ssh_password_authentication }}"
- regexp: "^#?PubkeyAuthentication"
line: "PubkeyAuthentication {{ common_ssh_pubkey_authentication }}"
- regexp: "^#?X11Forwarding"
line: "X11Forwarding no"
- regexp: "^#?MaxAuthTries"
line: "MaxAuthTries 3"
- regexp: "^#?ClientAliveInterval"
line: "ClientAliveInterval 300"
- regexp: "^#?ClientAliveCountMax"
line: "ClientAliveCountMax 2"
notify: Restart sshd

View File

@ -0,0 +1,21 @@
---
# Timezone and locale configuration
- name: Set timezone
ansible.builtin.timezone:
name: "{{ common_timezone }}"
- name: Generate locale (Debian/Ubuntu)
community.general.locale_gen:
name: "{{ common_locale }}"
state: present
when: ansible_os_family == "Debian"
- name: Set default locale
ansible.builtin.lineinfile:
path: /etc/default/locale
regexp: "^LANG="
line: "LANG={{ common_locale }}"
create: true
mode: '0644'
when: ansible_os_family == "Debian"

View File

@ -0,0 +1,35 @@
---
# User management tasks
- name: Create user groups
ansible.builtin.group:
name: "{{ item.name }}"
state: present
loop: "{{ common_users }}"
when: item.create_group | default(true)
- name: Create users
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups | default([]) }}"
shell: "{{ item.shell | default('/bin/bash') }}"
create_home: true
state: present
loop: "{{ common_users }}"
- name: Set authorized keys for users
ansible.posix.authorized_key:
user: "{{ item.0.name }}"
key: "{{ item.1 }}"
state: present
loop: "{{ common_users | subelements('ssh_keys', skip_missing=True) }}"
- name: Configure passwordless sudo for users
ansible.builtin.lineinfile:
path: "/etc/sudoers.d/{{ item.name }}"
line: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL"
create: true
mode: '0440'
validate: 'visudo -cf %s'
loop: "{{ common_users }}"
when: item.passwordless_sudo | default(false)

View File

@ -0,0 +1,2 @@
---
# Default variables (fallback)

94
scripts/create-user.sh Executable file
View File

@ -0,0 +1,94 @@
#!/bin/bash
# Create a user with sudo privileges
# Usage: ./create-user.sh [username]
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default username
USERNAME="${1:-dlxadmin}"
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Please run as root or with sudo${NC}"
exit 1
fi
echo -e "${GREEN}Creating user: ${USERNAME}${NC}"
# Check if user already exists
if id "$USERNAME" &>/dev/null; then
echo -e "${YELLOW}User ${USERNAME} already exists${NC}"
else
# Create user
useradd -m -s /bin/bash "$USERNAME"
echo -e "${GREEN}User ${USERNAME} created${NC}"
fi
# Set password
echo -e "${YELLOW}Set password for ${USERNAME}:${NC}"
passwd "$USERNAME"
# Add to sudo group (Debian/Ubuntu) or wheel (RHEL/CentOS)
if getent group sudo &>/dev/null; then
usermod -aG sudo "$USERNAME"
echo -e "${GREEN}Added ${USERNAME} to sudo group${NC}"
elif getent group wheel &>/dev/null; then
usermod -aG wheel "$USERNAME"
echo -e "${GREEN}Added ${USERNAME} to wheel group${NC}"
else
echo -e "${RED}Neither sudo nor wheel group found${NC}"
fi
# Configure passwordless sudo
if [ -d /etc/sudoers.d ]; then
echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/"$USERNAME"
chmod 440 /etc/sudoers.d/"$USERNAME"
echo -e "${GREEN}Configured passwordless sudo for ${USERNAME}${NC}"
else
# Fallback: add to sudoers file directly
if ! grep -q "^$USERNAME" /etc/sudoers; then
echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
echo -e "${GREEN}Added ${USERNAME} to /etc/sudoers${NC}"
fi
fi
# Check and install SSH server
if ! command -v sshd &>/dev/null && ! systemctl list-unit-files | grep -q sshd; then
echo -e "${YELLOW}SSH server not found. Installing...${NC}"
if command -v apt-get &>/dev/null; then
apt-get update && apt-get install -y openssh-server
elif command -v dnf &>/dev/null; then
dnf install -y openssh-server
elif command -v yum &>/dev/null; then
yum install -y openssh-server
elif command -v pacman &>/dev/null; then
pacman -Sy --noconfirm openssh
else
echo -e "${RED}Could not install SSH server. Please install manually.${NC}"
fi
fi
# Enable and start SSH service
if command -v systemctl &>/dev/null; then
systemctl enable --now sshd 2>/dev/null || systemctl enable --now ssh 2>/dev/null
echo -e "${GREEN}SSH service enabled and started${NC}"
fi
# Setup .ssh directory
mkdir -p /home/"$USERNAME"/.ssh
chmod 700 /home/"$USERNAME"/.ssh
touch /home/"$USERNAME"/.ssh/authorized_keys
chmod 600 /home/"$USERNAME"/.ssh/authorized_keys
chown -R "$USERNAME":"$USERNAME" /home/"$USERNAME"/.ssh
echo -e "${GREEN}Created .ssh directory${NC}"
echo -e "${GREEN}✓ User ${USERNAME} setup complete!${NC}"
echo -e "${YELLOW}Now run from your ansible workstation:${NC}"
echo -e " ./scripts/setup-ssh.sh <this-server-ip> ${USERNAME}"