Sanitized mirror from private repository - 2026-04-18 11:19:59 UTC
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m14s
Documentation / Deploy to GitHub Pages (push) Has been skipped

This commit is contained in:
Gitea Mirror Bot
2026-04-18 11:19:59 +00:00
commit fb00a325d1
1418 changed files with 359990 additions and 0 deletions

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()