Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped

This commit is contained in:
Gitea Mirror Bot
2026-04-20 01:32:01 +00:00
commit e7652c8dab
1445 changed files with 364095 additions and 0 deletions

206
ansible/homelab/README.md Normal file
View File

@@ -0,0 +1,206 @@
# Homelab Ansible Playbooks
Automated deployment and management of all homelab services across all hosts.
## 📁 Directory Structure
```
ansible/homelab/
├── ansible.cfg # Ansible configuration
├── inventory.yml # All hosts inventory
├── site.yml # Master playbook
├── generate_playbooks.py # Script to regenerate playbooks from compose files
├── group_vars/ # Variables by group
│ ├── all.yml # Global variables
│ ├── synology.yml # Synology NAS specific
│ └── vms.yml # Virtual machines specific
├── host_vars/ # Variables per host (auto-generated)
│ ├── atlantis.yml # 53 services
│ ├── calypso.yml # 24 services
│ ├── homelab_vm.yml # 33 services
│ └── ...
├── playbooks/ # Individual playbooks
│ ├── common/ # Shared playbooks
│ │ ├── install_docker.yml
│ │ └── setup_directories.yml
│ ├── deploy_atlantis.yml
│ ├── deploy_calypso.yml
│ └── ...
└── roles/ # Reusable roles
├── docker_stack/ # Deploy docker-compose stacks
└── directory_setup/ # Create directory structures
```
## 🚀 Quick Start
### Prerequisites
- Ansible 2.12+
- SSH access to all hosts (via Tailscale)
- Python 3.8+
### Installation
```bash
pip install ansible
```
### Deploy Everything
```bash
cd ansible/homelab
ansible-playbook site.yml
```
### Deploy to Specific Host
```bash
ansible-playbook site.yml --limit atlantis
```
### Deploy by Category
```bash
# Deploy all Synology hosts
ansible-playbook site.yml --tags synology
# Deploy all VMs
ansible-playbook site.yml --tags vms
```
### Check Mode (Dry Run)
```bash
ansible-playbook site.yml --check --diff
```
## 📋 Host Inventory
| Host | Category | Services | Description |
|------|----------|----------|-------------|
| atlantis | synology | 53 | Primary NAS (DS1823xs+) |
| calypso | synology | 24 | Secondary NAS (DS920+) |
| setillo | synology | 2 | Remote NAS |
| guava | physical | 8 | TrueNAS Scale |
| concord_nuc | physical | 11 | Intel NUC |
| homelab_vm | vms | 33 | Primary VM |
| rpi5_vish | edge | 3 | Raspberry Pi 5 |
## 🔧 Configuration
### Vault Secrets
Sensitive data should be stored in Ansible Vault:
```bash
# Create vault password file (DO NOT commit this)
echo "your-vault-password" > .vault_pass
# Encrypt a variable
ansible-vault encrypt_string 'my-secret' --name 'api_key'
# Run playbook with vault
ansible-playbook site.yml --vault-password-file .vault_pass
```
### Environment Variables
Create a `.env` file for each service or use host_vars:
```yaml
# host_vars/atlantis.yml
vault_plex_claim_token: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
```
## 📝 Adding New Services
### Method 1: Add docker-compose file
1. Add your `docker-compose.yml` to `hosts/<category>/<host>/<service>/`
2. Run the generator:
```bash
python3 generate_playbooks.py
```
### Method 2: Manual addition
1. Add service to `host_vars/<host>.yml`:
```yaml
host_services:
- name: my_service
stack_dir: my_service
compose_file: hosts/synology/atlantis/my_service.yaml
enabled: true
```
## 🏷️ Tags
| Tag | Description |
|-----|-------------|
| `synology` | All Synology NAS hosts |
| `vms` | All virtual machines |
| `physical` | Physical servers |
| `edge` | Edge devices (RPi, etc.) |
| `arr-suite` | Media management (Sonarr, Radarr, etc.) |
| `monitoring` | Prometheus, Grafana, etc. |
## 📊 Service Categories
### Media & Entertainment
- Plex, Jellyfin, Tautulli
- Sonarr, Radarr, Lidarr, Prowlarr
- Jellyseerr, Overseerr
### Productivity
- Paperless-ngx, Stirling PDF
- Joplin, Dokuwiki
- Syncthing
### Infrastructure
- Nginx Proxy Manager
- Traefik, Cloudflare Tunnel
- AdGuard Home, Pi-hole
### Monitoring
- Prometheus, Grafana
- Uptime Kuma, Dozzle
- Node Exporter
### Security
- Vaultwarden
- Authentik
- Headscale
## 🔄 Regenerating Playbooks
If you modify docker-compose files directly:
```bash
python3 generate_playbooks.py
```
This will:
1. Scan all `hosts/` directories for compose files
2. Update `host_vars/` with service lists
3. Regenerate individual host playbooks
4. Update the master `site.yml`
## 🐛 Troubleshooting
### Test connectivity
```bash
ansible all -m ping
```
### Test specific host
```bash
ansible atlantis -m ping
```
### Verbose output
```bash
ansible-playbook site.yml -vvv
```
### List tasks without running
```bash
ansible-playbook site.yml --list-tasks
```
## 📚 Resources
- [Ansible Documentation](https://docs.ansible.com/)
- [Docker Compose Reference](https://docs.docker.com/compose/compose-file/)
- [Tailscale Documentation](https://tailscale.com/kb/)

View File

@@ -0,0 +1,18 @@
[defaults]
inventory = inventory.yml
roles_path = roles
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
stdout_callback = yaml
interpreter_python = auto_silent
[privilege_escalation]
become = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

View File

@@ -0,0 +1,296 @@
#!/usr/bin/env python3
"""
Generate Ansible playbooks from existing docker-compose files in the homelab repo.
This script scans the hosts/ directory and creates deployment playbooks.
"""
import os
import yaml
from pathlib import Path
from collections import defaultdict
REPO_ROOT = Path(__file__).parent.parent.parent
HOSTS_DIR = REPO_ROOT / "hosts"
ANSIBLE_DIR = Path(__file__).parent
PLAYBOOKS_DIR = ANSIBLE_DIR / "playbooks"
HOST_VARS_DIR = ANSIBLE_DIR / "host_vars"
# Mapping of directory names to ansible host names
HOST_MAPPING = {
"atlantis": "atlantis",
"calypso": "calypso",
"setillo": "setillo",
"guava": "guava",
"concord-nuc": "concord_nuc",
"anubis": "anubis",
"homelab-vm": "homelab_vm",
"chicago-vm": "chicago_vm",
"bulgaria-vm": "bulgaria_vm",
"contabo-vm": "contabo_vm",
"rpi5-vish": "rpi5_vish",
"tdarr-node": "tdarr_node",
}
# Host categories for grouping
HOST_CATEGORIES = {
"synology": ["atlantis", "calypso", "setillo"],
"physical": ["guava", "concord-nuc", "anubis"],
"vms": ["homelab-vm", "chicago-vm", "bulgaria-vm", "contabo-vm", "matrix-ubuntu-vm"],
"edge": ["rpi5-vish", "nvidia_shield"],
"proxmox": ["tdarr-node"],
}
def find_compose_files():
"""Find all docker-compose files in the hosts directory."""
compose_files = defaultdict(list)
for yaml_file in HOSTS_DIR.rglob("*.yaml"):
if ".git" in str(yaml_file):
continue
compose_files[yaml_file.parent].append(yaml_file)
for yml_file in HOSTS_DIR.rglob("*.yml"):
if ".git" in str(yml_file):
continue
compose_files[yml_file.parent].append(yml_file)
return compose_files
def get_host_from_path(file_path):
"""Extract REDACTED_APP_PASSWORD path."""
parts = file_path.relative_to(HOSTS_DIR).parts
# Structure: hosts/<category>/<host>/...
if len(parts) >= 2:
category = parts[0]
host = parts[1]
return category, host
return None, None
def extract_service_name(file_path):
"""Extract service name from file path."""
# Get the service name from parent directory or filename
if file_path.name in ["docker-compose.yml", "docker-compose.yaml"]:
return file_path.parent.name
else:
return file_path.stem.replace("-", "_").replace(".", "_")
def is_compose_file(file_path):
"""Check if file looks like a docker-compose file."""
try:
with open(file_path, 'r') as f:
content = yaml.safe_load(f)
if content and isinstance(content, dict):
return 'services' in content or 'version' in content
except:
pass
return False
def generate_service_vars(host, services):
"""Generate host_vars with service definitions."""
service_list = []
for service_path, service_name in services:
rel_path = service_path.relative_to(REPO_ROOT)
# Determine the stack directory name
if service_path.name in ["docker-compose.yml", "docker-compose.yaml"]:
stack_dir = service_path.parent.name
else:
stack_dir = service_name
service_entry = {
"name": service_name,
"stack_dir": stack_dir,
"compose_file": str(rel_path),
"enabled": True,
}
# Check for .env file
env_file = service_path.parent / ".env"
stack_env = service_path.parent / "stack.env"
if env_file.exists():
service_entry["env_file"] = str(env_file.relative_to(REPO_ROOT))
elif stack_env.exists():
service_entry["env_file"] = str(stack_env.relative_to(REPO_ROOT))
service_list.append(service_entry)
return service_list
def generate_host_playbook(host_name, ansible_host, services, category):
"""Generate a playbook for a specific host."""
# Create header comment
header = f"""---
# Deployment playbook for {host_name}
# Category: {category}
# Services: {len(services)}
#
# Usage:
# ansible-playbook playbooks/deploy_{ansible_host}.yml
# ansible-playbook playbooks/deploy_{ansible_host}.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_{ansible_host}.yml --check
"""
playbook = [
{
"name": f"Deploy services to {host_name}",
"hosts": ansible_host,
"gather_facts": True,
"vars": {
"services": "{{ host_services | default([]) }}"
},
"tasks": [
{
"name": "Display deployment info",
"ansible.builtin.debug": {
"msg": "Deploying {{ services | length }} services to {{ inventory_hostname }}"
}
},
{
"name": "Ensure docker data directory exists",
"ansible.builtin.file": {
"path": "{{ docker_data_path }}",
"state": "directory",
"mode": "0755"
}
},
{
"name": "Deploy each enabled service",
"ansible.builtin.include_role": {
"name": "docker_stack"
},
"vars": {
"stack_name": "{{ item.stack_dir }}",
"stack_compose_file": "{{ item.compose_file }}",
"stack_env_file": "{{ item.env_file | default(omit) }}"
},
"loop": "{{ services }}",
"loop_control": {
"label": "{{ item.name }}"
},
"when": "item.enabled | default(true)"
}
]
}
]
return header, playbook
def main():
"""Main function to generate all playbooks."""
print("=" * 60)
print("Generating Ansible Playbooks from Homelab Repository")
print("=" * 60)
# Ensure directories exist
PLAYBOOKS_DIR.mkdir(parents=True, exist_ok=True)
HOST_VARS_DIR.mkdir(parents=True, exist_ok=True)
# Find all compose files
compose_files = find_compose_files()
# Organize by host
hosts_services = defaultdict(list)
for directory, files in compose_files.items():
category, host = get_host_from_path(directory)
if not host:
continue
for f in files:
if is_compose_file(f):
service_name = extract_service_name(f)
hosts_services[(category, host)].append((f, service_name))
# Generate playbooks and host_vars
all_hosts = {}
for (category, host), services in sorted(hosts_services.items()):
ansible_host = HOST_MAPPING.get(host, host.replace("-", "_"))
print(f"\n[{category}/{host}] Found {len(services)} services:")
for service_path, service_name in services:
print(f" - {service_name}")
# Generate host_vars
service_vars = generate_service_vars(host, services)
host_vars = {
"host_services": service_vars
}
host_vars_file = HOST_VARS_DIR / f"{ansible_host}.yml"
with open(host_vars_file, 'w') as f:
f.write("---\n")
f.write(f"# Auto-generated host variables for {host}\n")
f.write(f"# Services deployed to this host\n\n")
yaml.dump(host_vars, f, default_flow_style=False, sort_keys=False)
# Generate individual host playbook
header, playbook = generate_host_playbook(host, ansible_host, services, category)
playbook_file = PLAYBOOKS_DIR / f"deploy_{ansible_host}.yml"
with open(playbook_file, 'w') as f:
f.write(header)
yaml.dump(playbook, f, default_flow_style=False, sort_keys=False)
all_hosts[ansible_host] = {
"category": category,
"host": host,
"services": len(services)
}
# Generate master playbook
master_playbook = [
{
"name": "Deploy all homelab services",
"hosts": "localhost",
"gather_facts": False,
"tasks": [
{
"name": "Display deployment plan",
"ansible.builtin.debug": {
"msg": "Deploying services to all hosts. Use --limit to target specific hosts."
}
}
]
}
]
# Add imports for each host
for ansible_host, info in sorted(all_hosts.items()):
master_playbook.append({
"name": f"Deploy to {info['host']} ({info['services']} services)",
"ansible.builtin.import_playbook": f"playbooks/deploy_{ansible_host}.yml",
"tags": [info['category'], ansible_host]
})
master_file = ANSIBLE_DIR / "site.yml"
with open(master_file, 'w') as f:
f.write("---\n")
f.write("# Master Homelab Deployment Playbook\n")
f.write("# Auto-generated from docker-compose files\n")
f.write("#\n")
f.write("# Usage:\n")
f.write("# Deploy everything: ansible-playbook site.yml\n")
f.write("# Deploy specific host: ansible-playbook site.yml --limit atlantis\n")
f.write("# Deploy by category: ansible-playbook site.yml --tags synology\n")
f.write("#\n\n")
yaml.dump(master_playbook, f, default_flow_style=False, sort_keys=False)
print(f"\n{'=' * 60}")
print(f"Generated playbooks for {len(all_hosts)} hosts")
print(f"Master playbook: {master_file}")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,205 @@
---
# Homelab Ansible Inventory
# All hosts accessible via Tailscale (tail.vish.gg)
# Last reconciled: 2026-03-13
#
# This inventory is used by ansible/homelab/ deployment playbooks.
# It is kept consistent with ansible/automation/hosts.ini.
# hosts.ini is the canonical reference — update both when adding hosts.
#
# Host naming convention:
# Matches automation/hosts.ini names where possible.
# Underscores used where hyphens would break Ansible variable names.
all:
vars:
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
docker_compose_version: "2"
children:
# -------------------------------------------------------------------------
# Synology NAS devices
# ansible_become: false — Synology DSM does not use standard sudo
# docker_data_path: /volume1/docker — DSM package manager path
# -------------------------------------------------------------------------
synology:
vars:
docker_data_path: /volume1/docker
ansible_become: false
docker_socket: /var/run/docker.sock
docker_bin: sudo /var/packages/REDACTED_APP_PASSWORD/usr/bin/docker
hosts:
atlantis:
ansible_host: 100.83.230.112
ansible_user: vish
ansible_port: 60000
hostname: atlantis.vish.local
description: "Primary NAS — Synology DS1823xs+"
calypso:
ansible_host: 100.103.48.78
ansible_user: Vish
ansible_port: 62000
hostname: calypso.vish.local
description: "Secondary NAS — Synology DS920+"
setillo:
ansible_host: 100.125.0.20
ansible_user: vish
ansible_port: 22
hostname: setillo.vish.local
description: "Remote NAS — Synology (Seattle offsite)"
# -------------------------------------------------------------------------
# Raspberry Pi nodes
# -------------------------------------------------------------------------
rpi:
vars:
docker_data_path: /opt/docker
ansible_become: true
docker_bin: docker
hosts:
pi-5:
ansible_host: 100.77.151.40
ansible_user: vish
hostname: pi-5.vish.local
description: "Raspberry Pi 5 — uptime-kuma, monitoring"
pi-5-kevin:
ansible_host: 100.123.246.75
ansible_user: vish
hostname: pi-5-kevin.vish.local
description: "Raspberry Pi 5 (Kevin's)"
# Note: frequently offline
# -------------------------------------------------------------------------
# Hypervisors and infrastructure hosts
# -------------------------------------------------------------------------
hypervisors:
vars:
docker_data_path: /opt/docker
ansible_become: true
docker_bin: docker
hosts:
pve:
ansible_host: 100.87.12.28
ansible_user: root
hostname: pve.vish.local
description: "Proxmox VE hypervisor"
# LXC 103: tdarr-node at 192.168.0.180 (LAN-only, no Tailscale)
# LXC 104: headscale-test
truenas-scale:
ansible_host: 100.75.252.64
ansible_user: vish
hostname: guava.vish.local
description: "TrueNAS Scale — guava"
docker_data_path: /mnt/pool/docker
# WARNING: do NOT run apt update on TrueNAS — use web UI only
homeassistant:
ansible_host: 100.112.186.90
ansible_user: hassio
hostname: homeassistant.vish.local
description: "Home Assistant OS"
# WARNING: exclude from apt updates — HA manages its own packages
# -------------------------------------------------------------------------
# Remote and physical compute hosts
# -------------------------------------------------------------------------
remote:
vars:
docker_data_path: /opt/docker
ansible_become: true
docker_bin: docker
hosts:
vish-concord-nuc:
ansible_host: 100.72.55.21
ansible_user: vish
hostname: concord-nuc.vish.local
description: "Intel NUC — concord"
seattle:
ansible_host: 100.82.197.124
ansible_user: root
hostname: seattle.vish.local
description: "Seattle VPS (Contabo) — bookstack, surmai, pufferpanel"
# -------------------------------------------------------------------------
# Local VMs on-site
# -------------------------------------------------------------------------
local_vms:
vars:
docker_data_path: /opt/docker
ansible_become: true
docker_bin: docker
hosts:
homelab:
ansible_host: 100.67.40.126
ansible_user: homelab
hostname: homelab-vm.vish.local
description: "Primary homelab VM — this machine"
matrix-ubuntu:
ansible_host: 100.85.21.51
ansible_user: test
hostname: matrix-ubuntu.vish.local
description: "Matrix/Mattermost Ubuntu VM"
# LAN: 192.168.0.154
# -------------------------------------------------------------------------
# Functional groups (mirrors automation/hosts.ini grouping)
# -------------------------------------------------------------------------
# All reachable managed hosts — use this for most playbooks
active:
children:
homelab_group:
synology:
rpi:
hypervisors:
remote:
local_vms:
# Hosts using Calypso as APT proxy (apt-cacher-ng)
debian_clients:
hosts:
homelab:
pi-5:
pi-5-kevin:
vish-concord-nuc:
pve:
homeassistant:
truenas-scale:
# Hosts running Portainer edge agents
portainer_edge_agents:
hosts:
homelab:
vish-concord-nuc:
pi-5:
calypso:
# Legacy compatibility group
homelab_linux:
children:
homelab_group:
synology:
rpi:
hypervisors:
remote:
# Internal group to avoid name collision between host 'homelab' and group
homelab_group:
hosts:
homelab:
# -------------------------------------------------------------------------
# Offline / LAN-only hosts — not reachable via Tailscale
# Documented here for reference, not targeted by playbooks
# -------------------------------------------------------------------------
# tdarr_node (LXC 103): 192.168.0.180 — access via: ssh pve "pct exec 103 -- <cmd>"
# anubis: unknown IP — not in Tailscale
# pi-5-kevin: 100.123.246.75 — frequently offline

View File

@@ -0,0 +1,48 @@
---
# Backup all docker-compose configs and data
- name: Backup Docker configurations
hosts: "{{ target_host | default('all') }}"
gather_facts: true
vars:
backup_dest: "{{ backup_path | default('/backup') }}"
backup_timestamp: "{{ ansible_date_time.date }}_{{ ansible_date_time.hour }}{{ ansible_date_time.minute }}"
tasks:
- name: Create backup directory
ansible.builtin.file:
path: "{{ backup_dest }}/{{ inventory_hostname }}"
state: directory
mode: '0755'
become: "{{ ansible_become | default(false) }}"
delegate_to: localhost
- name: Find all docker-compose files
ansible.builtin.find:
paths: "{{ docker_data_path }}"
patterns: "docker-compose.yml,docker-compose.yaml,.env"
recurse: true
register: compose_files
- name: Archive docker configs
ansible.builtin.archive:
path: "{{ docker_data_path }}"
dest: "/tmp/{{ inventory_hostname }}_configs_{{ backup_timestamp }}.tar.gz"
format: gz
exclude_path:
- "*/data/*"
- "*/logs/*"
- "*/cache/*"
become: "{{ ansible_become | default(false) }}"
- name: Fetch backup to control node
ansible.builtin.fetch:
src: "/tmp/{{ inventory_hostname }}_configs_{{ backup_timestamp }}.tar.gz"
dest: "{{ backup_dest }}/{{ inventory_hostname }}/"
flat: true
- name: Clean up remote archive
ansible.builtin.file:
path: "/tmp/{{ inventory_hostname }}_configs_{{ backup_timestamp }}.tar.gz"
state: absent
become: "{{ ansible_become | default(false) }}"

View File

@@ -0,0 +1,55 @@
---
# Install Docker on a host (for non-Synology systems)
- name: Install Docker
hosts: "{{ target_host | default('all:!synology') }}"
become: true
gather_facts: true
tasks:
- name: Install prerequisites
ansible.builtin.apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- python3-pip
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Add Docker GPG key
ansible.builtin.apt_key:
url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
state: present
when: ansible_os_family == "Debian"
- name: Add Docker repository
ansible.builtin.apt_repository:
repo: "deb https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
state: present
when: ansible_os_family == "Debian"
- name: Install Docker
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Ensure Docker service is running
ansible.builtin.service:
name: docker
state: started
enabled: true
- name: Add user to docker group
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: true

View File

@@ -0,0 +1,27 @@
---
# View logs for a specific service
# Usage: ansible-playbook playbooks/common/logs.yml -e "service_name=plex" -e "target_host=atlantis"
- name: View service logs
hosts: "{{ target_host }}"
gather_facts: false
vars:
log_lines: 100
follow_logs: false
tasks:
- name: Validate service_name is provided
ansible.builtin.fail:
msg: "service_name variable is required. Use -e 'service_name=<name>'"
when: service_name is not defined
- name: Get service logs
ansible.builtin.command:
cmd: "docker compose logs --tail={{ log_lines }} {{ '--follow' if follow_logs else '' }}"
chdir: "{{ docker_data_path }}/{{ service_name }}"
register: logs_result
become: "{{ ansible_become | default(false) }}"
- name: Display logs
ansible.builtin.debug:
msg: "{{ logs_result.stdout }}"

View File

@@ -0,0 +1,23 @@
---
# Restart a specific service
# Usage: ansible-playbook playbooks/common/restart_service.yml -e "service_name=plex" -e "target_host=atlantis"
- name: Restart Docker service
hosts: "{{ target_host }}"
gather_facts: false
tasks:
- name: Validate service_name is provided
ansible.builtin.fail:
msg: "service_name variable is required. Use -e 'service_name=<name>'"
when: service_name is not defined
- name: Restart service
ansible.builtin.command:
cmd: docker compose restart
chdir: "{{ docker_data_path }}/{{ service_name }}"
register: restart_result
become: "{{ ansible_become | default(false) }}"
- name: Display result
ansible.builtin.debug:
msg: "Service {{ service_name }} restarted on {{ inventory_hostname }}"

View File

@@ -0,0 +1,34 @@
---
# Setup base directories for Docker services
- name: Setup Docker directories
hosts: "{{ target_host | default('all') }}"
gather_facts: true
tasks:
- name: Create base docker directory
ansible.builtin.file:
path: "{{ docker_data_path }}"
state: directory
mode: '0755'
become: "{{ ansible_become | default(false) }}"
- name: Create common directories
ansible.builtin.file:
path: "{{ docker_data_path }}/{{ item }}"
state: directory
mode: '0755'
loop:
- configs
- data
- logs
- backups
become: "{{ ansible_become | default(false) }}"
- name: Create service directories from host_services
ansible.builtin.file:
path: "{{ docker_data_path }}/{{ item.stack_dir }}"
state: directory
mode: '0755'
loop: "{{ host_services | default([]) }}"
when: host_services is defined
become: "{{ ansible_become | default(false) }}"

View File

@@ -0,0 +1,49 @@
---
# Check status of all Docker containers
- name: Check container status
hosts: "{{ target_host | default('all') }}"
gather_facts: true
tasks:
- name: Get list of running containers
ansible.builtin.command:
cmd: docker ps --format "table {{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Status{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}"
register: docker_ps
changed_when: false
become: "{{ ansible_become | default(false) }}"
- name: Display running containers
ansible.builtin.debug:
msg: |
=== {{ inventory_hostname }} ===
{{ docker_ps.stdout }}
- name: Get stopped/exited containers
ansible.builtin.command:
cmd: docker ps -a --filter "status=exited" --format "table {{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Status{{ '}}' }}"
register: docker_exited
changed_when: false
become: "{{ ansible_become | default(false) }}"
- name: Display stopped containers
ansible.builtin.debug:
msg: |
=== Stopped containers on {{ inventory_hostname }} ===
{{ docker_exited.stdout }}
when: docker_exited.stdout_lines | length > 1
- name: Get disk usage
ansible.builtin.command:
cmd: docker system df
register: docker_df
changed_when: false
become: "{{ ansible_become | default(false) }}"
- name: Display disk usage
ansible.builtin.debug:
msg: |
=== Docker disk usage on {{ inventory_hostname }} ===
{{ docker_df.stdout }}

View File

@@ -0,0 +1,46 @@
---
# Update all Docker containers (pull new images and recreate)
- name: Update Docker containers
hosts: "{{ target_host | default('all') }}"
gather_facts: true
vars:
services: "{{ host_services | default([]) }}"
tasks:
- name: Display update info
ansible.builtin.debug:
msg: "Updating {{ services | length }} services on {{ inventory_hostname }}"
- name: Pull latest images for each service
ansible.builtin.command:
cmd: docker compose pull
chdir: "{{ docker_data_path }}/{{ item.stack_dir }}"
loop: "{{ services }}"
loop_control:
label: "{{ item.name }}"
when: item.enabled | default(true)
register: pull_result
changed_when: "'Downloaded' in pull_result.stdout"
failed_when: false
become: "{{ ansible_become | default(false) }}"
- name: Recreate containers with new images
ansible.builtin.command:
cmd: docker compose up -d --remove-orphans
chdir: "{{ docker_data_path }}/{{ item.stack_dir }}"
loop: "{{ services }}"
loop_control:
label: "{{ item.name }}"
when: item.enabled | default(true)
register: up_result
changed_when: "'Started' in up_result.stdout or 'Recreated' in up_result.stdout"
failed_when: false
become: "{{ ansible_become | default(false) }}"
- name: Clean up unused images
ansible.builtin.command:
cmd: docker image prune -af
when: prune_images | default(true)
changed_when: false
become: "{{ ansible_become | default(false) }}"

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for anubis
# Category: physical
# Services: 8
#
# Usage:
# ansible-playbook playbooks/deploy_anubis.yml
# ansible-playbook playbooks/deploy_anubis.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_anubis.yml --check
- name: Deploy services to anubis
hosts: anubis
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for bulgaria-vm
# Category: vms
# Services: 12
#
# Usage:
# ansible-playbook playbooks/deploy_bulgaria_vm.yml
# ansible-playbook playbooks/deploy_bulgaria_vm.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_bulgaria_vm.yml --check
- name: Deploy services to bulgaria-vm
hosts: bulgaria_vm
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for chicago-vm
# Category: vms
# Services: 7
#
# Usage:
# ansible-playbook playbooks/deploy_chicago_vm.yml
# ansible-playbook playbooks/deploy_chicago_vm.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_chicago_vm.yml --check
- name: Deploy services to chicago-vm
hosts: chicago_vm
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for concord-nuc
# Category: physical
# Services: 15
#
# Usage:
# ansible-playbook playbooks/deploy_concord_nuc.yml
# ansible-playbook playbooks/deploy_concord_nuc.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_concord_nuc.yml --check
- name: Deploy services to concord-nuc
hosts: concord_nuc
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for contabo-vm
# Category: vms
# Services: 1
#
# Usage:
# ansible-playbook playbooks/deploy_contabo_vm.yml
# ansible-playbook playbooks/deploy_contabo_vm.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_contabo_vm.yml --check
- name: Deploy services to contabo-vm
hosts: contabo_vm
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for guava
# Category: truenas
# Services: 2
#
# Usage:
# ansible-playbook playbooks/deploy_guava.yml
# ansible-playbook playbooks/deploy_guava.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_guava.yml --check
- name: Deploy services to guava
hosts: guava
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for lxc
# Category: proxmox
# Services: 1
#
# Usage:
# ansible-playbook playbooks/deploy_lxc.yml
# ansible-playbook playbooks/deploy_lxc.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_lxc.yml --check
- name: Deploy services to lxc
hosts: lxc
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for matrix-ubuntu-vm
# Category: vms
# Services: 4
#
# Usage:
# ansible-playbook playbooks/deploy_matrix_ubuntu_vm.yml
# ansible-playbook playbooks/deploy_matrix_ubuntu_vm.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_matrix_ubuntu_vm.yml --check
- name: Deploy services to matrix-ubuntu-vm
hosts: matrix_ubuntu_vm
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

View File

@@ -0,0 +1,35 @@
---
# Deployment playbook for seattle
# Category: vms
# Services: 13
#
# Usage:
# ansible-playbook playbooks/deploy_seattle.yml
# ansible-playbook playbooks/deploy_seattle.yml -e "stack_deploy=false"
# ansible-playbook playbooks/deploy_seattle.yml --check
- name: Deploy services to seattle
hosts: seattle
gather_facts: true
vars:
services: '{{ host_services | default([]) }}'
tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: Deploying {{ services | length }} services to {{ inventory_hostname }}
- name: Ensure docker data directory exists
ansible.builtin.file:
path: '{{ docker_data_path }}'
state: directory
mode: '0755'
- name: Deploy each enabled service
ansible.builtin.include_role:
name: docker_stack
vars:
stack_name: '{{ item.stack_dir }}'
stack_compose_file: '{{ item.compose_file }}'
stack_env_file: '{{ item.env_file | default(omit) }}'
loop: '{{ services }}'
loop_control:
label: '{{ item.name }}'
when: item.enabled | default(true)

87
ansible/homelab/site.yml Normal file
View File

@@ -0,0 +1,87 @@
---
# Master Homelab Deployment Playbook
# Auto-generated from docker-compose files
#
# Usage:
# Deploy everything: ansible-playbook site.yml
# Deploy specific host: ansible-playbook site.yml --limit atlantis
# Deploy by category: ansible-playbook site.yml --tags synology
#
- name: Deploy all homelab services
hosts: localhost
gather_facts: false
tasks:
- name: Display deployment plan
ansible.builtin.debug:
msg: Deploying services to all hosts. Use --limit to target specific hosts.
- name: Deploy to anubis (8 services)
ansible.builtin.import_playbook: playbooks/deploy_anubis.yml
tags:
- physical
- anubis
- name: Deploy to atlantis (57 services)
ansible.builtin.import_playbook: playbooks/deploy_atlantis.yml
tags:
- synology
- atlantis
- name: Deploy to bulgaria-vm (12 services)
ansible.builtin.import_playbook: playbooks/deploy_bulgaria_vm.yml
tags:
- vms
- bulgaria_vm
- name: Deploy to calypso (34 services)
ansible.builtin.import_playbook: playbooks/deploy_calypso.yml
tags:
- synology
- calypso
- name: Deploy to chicago-vm (7 services)
ansible.builtin.import_playbook: playbooks/deploy_chicago_vm.yml
tags:
- vms
- chicago_vm
- name: Deploy to concord-nuc (15 services)
ansible.builtin.import_playbook: playbooks/deploy_concord_nuc.yml
tags:
- physical
- concord_nuc
- name: Deploy to contabo-vm (1 services)
ansible.builtin.import_playbook: playbooks/deploy_contabo_vm.yml
tags:
- vms
- contabo_vm
- name: Deploy to guava (2 services)
ansible.builtin.import_playbook: playbooks/deploy_guava.yml
tags:
- truenas
- guava
- name: Deploy to homelab-vm (39 services)
ansible.builtin.import_playbook: playbooks/deploy_homelab_vm.yml
tags:
- vms
- homelab_vm
- name: Deploy to lxc (1 services)
ansible.builtin.import_playbook: playbooks/deploy_lxc.yml
tags:
- proxmox
- lxc
- name: Deploy to matrix-ubuntu-vm (4 services)
ansible.builtin.import_playbook: playbooks/deploy_matrix_ubuntu_vm.yml
tags:
- vms
- matrix_ubuntu_vm
- name: Deploy to rpi5-vish (6 services)
ansible.builtin.import_playbook: playbooks/deploy_rpi5_vish.yml
tags:
- edge
- rpi5_vish
- name: Deploy to seattle (13 services)
ansible.builtin.import_playbook: playbooks/deploy_seattle.yml
tags:
- vms
- seattle
- name: Deploy to setillo (5 services)
ansible.builtin.import_playbook: playbooks/deploy_setillo.yml
tags:
- synology
- setillo