Complete Ansible base configuration (#2)
Completed Issue #2: Ansible Base Configuration All objectives met: - ✅ Hetzner Cloud dynamic inventory (hcloud plugin) - ✅ Common role (SSH hardening, UFW firewall, fail2ban, auto-updates) - ✅ Docker role (Docker Engine + Compose + networks) - ✅ Traefik role (reverse proxy with Let's Encrypt SSL) - ✅ Setup playbook (orchestrates all base roles) - ✅ Successfully tested on live test server (91.99.210.204) Additional improvements: - Fixed ansible.cfg for Ansible 2.20+ compatibility - Updated ADR dates to 2025 - All roles follow Infrastructure Agent patterns Test Results: - SSH hardening applied (key-only auth) - UFW firewall active (ports 22, 80, 443) - Fail2ban protecting SSH - Automatic security updates enabled - Docker running with traefik network - Traefik deployed and ready for SSL Files added: - ansible/playbooks/setup.yml - ansible/roles/docker/* (complete) - ansible/roles/traefik/* (complete) Closes #2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
171cbfbb32
commit
4e72ddf4ef
13 changed files with 314 additions and 5 deletions
|
|
@ -12,7 +12,8 @@ fact_caching_connection = /tmp/ansible_facts
|
|||
fact_caching_timeout = 86400
|
||||
|
||||
# Output
|
||||
stdout_callback = yaml
|
||||
stdout_callback = default
|
||||
result_format = yaml
|
||||
bin_ansible_callbacks = True
|
||||
display_skipped_hosts = False
|
||||
|
||||
|
|
|
|||
40
ansible/playbooks/setup.yml
Normal file
40
ansible/playbooks/setup.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
# Initial server setup playbook
|
||||
# Provisions base infrastructure: hardening, Docker, Traefik
|
||||
|
||||
- name: Setup base infrastructure
|
||||
hosts: all
|
||||
become: yes
|
||||
|
||||
vars:
|
||||
# Override these in group_vars or host_vars
|
||||
traefik_acme_email: "admin@postxsociety.cloud"
|
||||
|
||||
pre_tasks:
|
||||
- name: Wait for system to be ready
|
||||
wait_for_connection:
|
||||
timeout: 300
|
||||
|
||||
- name: Gather facts
|
||||
setup:
|
||||
|
||||
roles:
|
||||
- role: common
|
||||
tags: ['common', 'security']
|
||||
|
||||
- role: docker
|
||||
tags: ['docker']
|
||||
|
||||
- role: traefik
|
||||
tags: ['traefik', 'proxy']
|
||||
|
||||
post_tasks:
|
||||
- name: Display server information
|
||||
debug:
|
||||
msg:
|
||||
- "✅ Server setup complete!"
|
||||
- "Hostname: {{ ansible_hostname }}"
|
||||
- "IP Address: {{ ansible_default_ipv4.address }}"
|
||||
- "SSH hardened, UFW enabled, fail2ban active"
|
||||
- "Docker installed and running"
|
||||
- "Traefik managing SSL certificates automatically"
|
||||
22
ansible/roles/docker/defaults/main.yml
Normal file
22
ansible/roles/docker/defaults/main.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
# Default variables for docker role
|
||||
|
||||
# Docker version (use 'latest' or pin specific version)
|
||||
docker_edition: "ce"
|
||||
docker_install_compose: true
|
||||
|
||||
# Docker daemon configuration
|
||||
docker_daemon_options:
|
||||
log-driver: "json-file"
|
||||
log-opts:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
storage-driver: "overlay2"
|
||||
|
||||
# Docker Compose version
|
||||
docker_compose_version: "2.24.0"
|
||||
|
||||
# Docker networks to create
|
||||
docker_networks:
|
||||
- name: "traefik"
|
||||
driver: "bridge"
|
||||
7
ansible/roles/docker/handlers/main.yml
Normal file
7
ansible/roles/docker/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
# Handlers for docker role
|
||||
|
||||
- name: Restart Docker
|
||||
service:
|
||||
name: docker
|
||||
state: restarted
|
||||
68
ansible/roles/docker/tasks/main.yml
Normal file
68
ansible/roles/docker/tasks/main.yml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
# Main tasks for docker role - install Docker and Docker Compose
|
||||
|
||||
- name: Install prerequisites
|
||||
apt:
|
||||
name:
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Add Docker GPG key
|
||||
apt_key:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
state: present
|
||||
|
||||
- name: Add Docker repository
|
||||
apt_repository:
|
||||
repo: "deb [arch=arm64,amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
state: present
|
||||
filename: docker
|
||||
|
||||
- name: Install Docker Engine
|
||||
apt:
|
||||
name:
|
||||
- docker-{{ docker_edition }}
|
||||
- docker-{{ docker_edition }}-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
update_cache: yes
|
||||
notify: Restart Docker
|
||||
|
||||
- name: Create Docker daemon configuration directory
|
||||
file:
|
||||
path: /etc/docker
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure Docker daemon
|
||||
template:
|
||||
src: daemon.json.j2
|
||||
dest: /etc/docker/daemon.json
|
||||
mode: '0644'
|
||||
notify: Restart Docker
|
||||
|
||||
- name: Create Docker networks
|
||||
community.docker.docker_network:
|
||||
name: "{{ item.name }}"
|
||||
driver: "{{ item.driver }}"
|
||||
state: present
|
||||
loop: "{{ docker_networks }}"
|
||||
|
||||
- name: Ensure Docker is running and enabled
|
||||
service:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Create /opt/docker directory for compose files
|
||||
file:
|
||||
path: /opt/docker
|
||||
state: directory
|
||||
mode: '0755'
|
||||
1
ansible/roles/docker/templates/daemon.json.j2
Normal file
1
ansible/roles/docker/templates/daemon.json.j2
Normal file
|
|
@ -0,0 +1 @@
|
|||
{{ docker_daemon_options | to_nice_json }}
|
||||
19
ansible/roles/traefik/defaults/main.yml
Normal file
19
ansible/roles/traefik/defaults/main.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
# Default variables for traefik role
|
||||
|
||||
# Traefik version
|
||||
traefik_version: "v3.0"
|
||||
|
||||
# Let's Encrypt configuration
|
||||
traefik_acme_email: "admin@example.com" # Override this!
|
||||
traefik_acme_staging: false # Set to true for testing
|
||||
|
||||
# Dashboard configuration
|
||||
traefik_dashboard_enabled: false
|
||||
traefik_dashboard_domain: "traefik.example.com"
|
||||
|
||||
# Network
|
||||
traefik_network: "traefik"
|
||||
|
||||
# Docker socket (for auto-discovery)
|
||||
traefik_docker_socket: "/var/run/docker.sock"
|
||||
7
ansible/roles/traefik/handlers/main.yml
Normal file
7
ansible/roles/traefik/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
# Handlers for traefik role
|
||||
|
||||
- name: Restart Traefik
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /opt/docker/traefik
|
||||
state: restarted
|
||||
37
ansible/roles/traefik/tasks/main.yml
Normal file
37
ansible/roles/traefik/tasks/main.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
# Main tasks for traefik role - deploy Traefik reverse proxy
|
||||
|
||||
- name: Create Traefik directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- /opt/docker/traefik
|
||||
- /opt/docker/traefik/letsencrypt
|
||||
|
||||
- name: Deploy Traefik static configuration
|
||||
template:
|
||||
src: traefik.yml.j2
|
||||
dest: /opt/docker/traefik/traefik.yml
|
||||
mode: '0644'
|
||||
notify: Restart Traefik
|
||||
|
||||
- name: Deploy Traefik dynamic configuration
|
||||
template:
|
||||
src: dynamic.yml.j2
|
||||
dest: /opt/docker/traefik/dynamic.yml
|
||||
mode: '0644'
|
||||
notify: Restart Traefik
|
||||
|
||||
- name: Deploy Traefik docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: /opt/docker/traefik/docker-compose.yml
|
||||
mode: '0644'
|
||||
notify: Restart Traefik
|
||||
|
||||
- name: Start Traefik via Docker Compose
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /opt/docker/traefik
|
||||
state: present
|
||||
36
ansible/roles/traefik/templates/docker-compose.yml.j2
Normal file
36
ansible/roles/traefik/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Traefik Reverse Proxy
|
||||
# Managed by Ansible - do not edit manually
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:{{ traefik_version }}
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
{% if traefik_dashboard_enabled %}
|
||||
- "8080:8080"
|
||||
{% endif %}
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- {{ traefik_docker_socket }}:{{ traefik_docker_socket }}:ro
|
||||
- ./traefik.yml:/etc/traefik/traefik.yml:ro
|
||||
- ./dynamic.yml:/etc/traefik/dynamic.yml:ro
|
||||
- ./letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- {{ traefik_network }}
|
||||
{% if traefik_dashboard_enabled %}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.dashboard.rule=Host(`{{ traefik_dashboard_domain }}`)"
|
||||
- "traefik.http.routers.dashboard.entrypoints=websecure"
|
||||
- "traefik.http.routers.dashboard.service=api@internal"
|
||||
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
|
||||
{% endif %}
|
||||
|
||||
networks:
|
||||
{{ traefik_network }}:
|
||||
external: true
|
||||
21
ansible/roles/traefik/templates/dynamic.yml.j2
Normal file
21
ansible/roles/traefik/templates/dynamic.yml.j2
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Traefik dynamic configuration
|
||||
# Managed by Ansible - do not edit manually
|
||||
|
||||
http:
|
||||
middlewares:
|
||||
# Security headers
|
||||
security-headers:
|
||||
headers:
|
||||
browserXssFilter: true
|
||||
contentTypeNosniff: true
|
||||
forceSTSHeader: true
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
stsSeconds: 31536000
|
||||
customFrameOptionsValue: "SAMEORIGIN"
|
||||
|
||||
# Rate limiting
|
||||
rate-limit:
|
||||
rateLimit:
|
||||
average: 100
|
||||
burst: 200
|
||||
50
ansible/roles/traefik/templates/traefik.yml.j2
Normal file
50
ansible/roles/traefik/templates/traefik.yml.j2
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Traefik static configuration
|
||||
# Managed by Ansible - do not edit manually
|
||||
|
||||
api:
|
||||
dashboard: {{ traefik_dashboard_enabled | lower }}
|
||||
{% if traefik_dashboard_enabled %}
|
||||
insecure: false
|
||||
{% endif %}
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
|
||||
websecure:
|
||||
address: ":443"
|
||||
http:
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix://{{ traefik_docker_socket }}"
|
||||
exposedByDefault: false
|
||||
network: {{ traefik_network }}
|
||||
|
||||
file:
|
||||
filename: /etc/traefik/dynamic.yml
|
||||
watch: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: {{ traefik_acme_email }}
|
||||
storage: /letsencrypt/acme.json
|
||||
{% if traefik_acme_staging %}
|
||||
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
{% endif %}
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
|
||||
log:
|
||||
level: INFO
|
||||
|
||||
accessLog:
|
||||
filePath: /var/log/traefik/access.log
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
## Post-X Society Multi-Tenant VPS Platform
|
||||
|
||||
**Document Status:** Living document
|
||||
**Created:** December 2024
|
||||
**Last Updated:** December 2024
|
||||
**Document Status:** Living document
|
||||
**Created:** December 2024
|
||||
**Last Updated:** December 2025
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -848,5 +848,5 @@ pipx inject ansible requests python-dateutil
|
|||
| 2024-12 | Switched from Terraform to OpenTofu (licensing concerns) | Pieter / Claude |
|
||||
| 2024-12 | Switched from HashiCorp Vault to SOPS + Age (simplicity, open source) | Pieter / Claude |
|
||||
| 2024-12 | Switched from Keycloak to Zitadel (Swiss company, GDPR jurisdiction) | Pieter / Claude |
|
||||
| 2024-12 | Adopted pipx for isolated Python tool environments (Ansible) | Pieter / Claude |
|
||||
| 2025-12 | Adopted pipx for isolated Python tool environments (Ansible) | Pieter / Claude |
|
||||
```
|
||||
Loading…
Add table
Reference in a new issue