dotfiles/bin/actions/update.py
2025-03-10 19:59:34 +01:00

198 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import sys
import subprocess
import argparse
# Import helper functions
sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin"))
from helpers.functions import printfe, run_command
def help_message():
"""Print help message and exit"""
printfe("green", "Usage: upgrade.py [options]")
printfe("green", "Options:")
printfe("green", " --ha, -H Upgrade Home Manager packages.")
printfe("green", " --ansible, -A Upgrade Ansible packages.")
printfe("green", " --ansible-verbose Upgrade Ansible packages with verbose output. (-vvv)")
printfe("green", " --full-speed, -F Upgrade packages and use all available cores for compilation. (Default: 8 cores)")
printfe("green", " --help, -h Display this help message.")
return 0
def ensure_ansible_collections():
"""Ensure required Ansible collections are installed"""
# List of required collections that can be expanded in the future
required_collections = [
"community.general",
]
printfe("cyan", "Checking for required Ansible collections...")
# Get default collections paths from Ansible config
collections_paths = []
try:
status, output = run_command(["ansible-config", "dump", "COLLECTIONS_PATHS"], shell=False)
if status and output.strip():
# Extract paths from output which is in format "COLLECTIONS_PATHS(default) = ['/path1', '/path2']"
import re
paths_match = re.search(r'\[(.*)\]', output)
if paths_match:
paths_str = paths_match.group(1)
# Split by comma, strip quotes and whitespace
collections_paths = [p.strip().strip("'\"") for p in paths_str.split(',')]
except Exception as e:
printfe("yellow", f"Failed to get collections paths: {e}")
# Add default paths if we couldn't get them from config
if not collections_paths:
collections_paths = [
os.path.expanduser("~/.ansible/collections"),
"/usr/share/ansible/collections"
]
# Check if each required collection is installed
missing_collections = []
for collection in required_collections:
collection_found = False
namespace, name = collection.split('.')
for path in collections_paths:
collection_path = os.path.join(path, "ansible_collections", namespace, name)
if os.path.exists(collection_path):
collection_found = True
break
if not collection_found:
missing_collections.append(collection)
# Install missing collections
if missing_collections:
for collection in missing_collections:
printfe("yellow", f"Installing {collection} collection...")
status, install_output = run_command(["ansible-galaxy", "collection", "install", collection], shell=False)
if not status:
printfe("yellow", f"Warning: Failed to install {collection} collection: {install_output}")
printfe("yellow", f"Continuing anyway, but playbook might fail if it requires {collection}")
else:
printfe("green", f"Successfully installed {collection} collection")
else:
printfe("green", "All required collections are already installed.")
return True
def main():
# Parse arguments
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--ha", "-H", action="store_true", help="Upgrade Home Manager packages")
parser.add_argument("--ansible", "-A", action="store_true", help="Upgrade Ansible packages")
parser.add_argument("--ansible-verbose", action="store_true", help="Upgrade Ansible packages with verbose output")
parser.add_argument("--full-speed", "-F", action="store_true", help="Use all available cores")
parser.add_argument("--help", "-h", action="store_true", help="Display help message")
args = parser.parse_args()
if args.help:
return help_message()
# If no specific option provided, run both
if not args.ha and not args.ansible and not args.ansible_verbose:
args.ha = True
args.ansible = True
# If ansible_verbose is set, also set ansible
if args.ansible_verbose:
args.ansible = True
# Set cores and jobs based on full-speed flag
if args.full_speed:
import multiprocessing
cores = jobs = multiprocessing.cpu_count()
else:
cores = 8
jobs = 1
printfe("cyan", f"Limiting to {cores} cores with {jobs} jobs.")
# Home Manager update
if args.ha:
dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles"))
hostname = os.uname().nodename
printfe("cyan", "Updating Home Manager flake...")
os.chdir(f"{dotfiles_path}/config/home-manager")
status, output = run_command(["nix", "--extra-experimental-features", "nix-command",
"--extra-experimental-features", "flakes", "flake", "update"],
shell=False)
if not status:
printfe("red", f"Failed to update Home Manager flake: {output}")
return 1
# Check if home-manager is installed
status, _ = run_command(["which", "home-manager"], shell=False)
if status:
printfe("cyan", "Cleaning old backup files...")
backup_file = os.path.expanduser("~/.config/mimeapps.list.backup")
if os.path.exists(backup_file):
os.remove(backup_file)
printfe("cyan", "Upgrading Home Manager packages...")
env = os.environ.copy()
env["NIXPKGS_ALLOW_UNFREE"] = "1"
cmd = ["home-manager", "--extra-experimental-features", "nix-command",
"--extra-experimental-features", "flakes", "switch", "-b", "backup",
f"--flake", f".#{hostname}", "--impure", "--cores", str(cores), "-j", str(jobs)]
result = subprocess.run(cmd, env=env)
if result.returncode != 0:
printfe("red", "Failed to upgrade Home Manager packages.")
return 1
else:
printfe("red", "Home Manager is not installed.")
return 1
# Ansible update
if args.ansible:
dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles"))
hostname = os.uname().nodename
username = os.environ.get("USER", os.environ.get("USERNAME", "user"))
# Check if ansible is installed
status, _ = run_command(["which", "ansible-playbook"], shell=False)
if not status:
printfe("yellow", "Ansible is not installed, installing it with pipx...")
status, output = run_command(["pipx", "install", "--include-deps", "ansible", "ansible-lint"], shell=False)
if not status:
printfe("red", f"Failed to install Ansible: {output}")
return 1
# Ensure required collections are installed
if not ensure_ansible_collections():
printfe("red", "Failed to ensure required Ansible collections are installed")
return 1
printfe("cyan", "Running Ansible playbook...")
ansible_cmd = [
"ansible-playbook",
"-i", f"{dotfiles_path}/config/ansible/inventory.ini",
f"{dotfiles_path}/config/ansible/main.yml",
"--extra-vars", f"hostname={hostname}",
"--extra-vars", f"ansible_user={username}",
"--limit", hostname,
"--ask-become-pass"
]
if args.ansible_verbose:
ansible_cmd.append("-vvv")
result = subprocess.run(ansible_cmd)
if result.returncode != 0:
printfe("red", "Failed to upgrade Ansible packages.")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())