#!/usr/bin/env python3 """ Debian Variants and Flavors Manager This module manages Debian variants (stable, testing, unstable, backports) and desktop environment flavors (GNOME, KDE, XFCE, MATE). """ import json import os import subprocess import tempfile from typing import Dict, List, Optional, Any, Set from dataclasses import dataclass, asdict from pathlib import Path import urllib.request import urllib.parse from datetime import datetime, timedelta @dataclass class DebianVariant: """Represents a Debian variant""" name: str codename: str version: str status: str # stable, testing, unstable release_date: Optional[datetime] end_of_life: Optional[datetime] architectures: List[str] mirrors: List[str] security_support: bool updates_support: bool backports_support: bool @dataclass class DesktopFlavor: """Represents a desktop environment flavor""" name: str display_name: str description: str packages: List[str] dependencies: List[str] variants: List[str] # Which Debian variants support this flavor enabled: bool = True priority: int = 500 class DebianVariantsManager: """Manages Debian variants and desktop flavors""" def __init__(self, config_dir: str = "./config/variants"): self.config_dir = Path(config_dir) self.config_dir.mkdir(parents=True, exist_ok=True) self.variants_file = self.config_dir / "variants.json" self.flavors_file = self.config_dir / "flavors.json" self._load_configuration() def _load_configuration(self): """Load variants and flavors configuration""" # Load variants if self.variants_file.exists(): with open(self.variants_file, 'r') as f: self.variants = json.load(f) else: self.variants = self._get_default_variants() self._save_variants() # Load flavors if self.flavors_file.exists(): with open(self.flavors_file, 'r') as f: self.flavors = json.load(f) else: self.flavors = self._get_default_flavors() self._save_flavors() def _get_default_variants(self) -> Dict[str, Any]: """Get default Debian variants configuration""" return { "variants": [ { "name": "bookworm", "codename": "Bookworm", "version": "12", "status": "stable", "release_date": "2023-06-10T00:00:00", "end_of_life": "2026-06-10T00:00:00", "architectures": ["amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"], "mirrors": [ "http://deb.debian.org/debian", "http://security.debian.org/debian-security" ], "security_support": True, "updates_support": True, "backports_support": True }, { "name": "sid", "codename": "Sid", "version": "unstable", "status": "unstable", "release_date": None, "end_of_life": None, "architectures": ["amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"], "mirrors": [ "http://deb.debian.org/debian" ], "security_support": False, "updates_support": False, "backports_support": False }, { "name": "testing", "codename": "Trixie", "version": "13", "status": "testing", "release_date": None, "end_of_life": None, "architectures": ["amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"], "mirrors": [ "http://deb.debian.org/debian" ], "security_support": False, "updates_support": False, "backports_support": False } ] } def _get_default_flavors(self) -> Dict[str, Any]: """Get default desktop flavors configuration""" return { "flavors": [ { "name": "gnome", "display_name": "GNOME", "description": "Modern, intuitive desktop environment", "packages": ["task-gnome-desktop", "gnome-core"], "dependencies": ["gnome-session", "gnome-shell", "gdm3"], "variants": ["bookworm", "sid", "testing"], "enabled": True, "priority": 100 }, { "name": "kde", "display_name": "KDE Plasma", "description": "Feature-rich, customizable desktop", "packages": ["task-kde-desktop", "plasma-desktop"], "dependencies": ["kde-plasma-desktop", "sddm"], "variants": ["bookworm", "sid", "testing"], "enabled": True, "priority": 200 }, { "name": "xfce", "display_name": "Xfce", "description": "Lightweight, fast desktop environment", "packages": ["task-xfce-desktop", "xfce4"], "dependencies": ["xfce4-session", "lightdm"], "variants": ["bookworm", "sid", "testing"], "enabled": True, "priority": 300 }, { "name": "mate", "display_name": "MATE", "description": "Traditional GNOME 2 desktop", "packages": ["task-mate-desktop", "mate-desktop"], "dependencies": ["mate-session-manager", "lightdm"], "variants": ["bookworm", "sid", "testing"], "enabled": True, "priority": 400 }, { "name": "minimal", "display_name": "Minimal", "description": "Minimal system without desktop", "packages": [], "dependencies": [], "variants": ["bookworm", "sid", "testing"], "enabled": True, "priority": 500 } ] } def _save_variants(self): """Save variants configuration""" with open(self.variants_file, 'w') as f: json.dump(self.variants, f, indent=2) def _save_flavors(self): """Save flavors configuration""" with open(self.flavors_file, 'w') as f: json.dump(self.flavors, f, indent=2) def get_variant(self, name: str) -> Optional[Dict[str, Any]]: """Get variant configuration by name""" for variant in self.variants["variants"]: if variant["name"] == name: return variant return None def get_flavor(self, name: str) -> Optional[Dict[str, Any]]: """Get flavor configuration by name""" for flavor in self.flavors["flavors"]: if flavor["name"] == name: return flavor return None def list_variants(self, status: str = None) -> List[Dict[str, Any]]: """List variants, optionally filtered by status""" if status: return [v for v in self.variants["variants"] if v["status"] == status] return self.variants["variants"] def list_flavors(self, variant: str = None) -> List[Dict[str, Any]]: """List flavors, optionally filtered by variant support""" if variant: return [f for f in self.flavors["flavors"] if variant in f["variants"]] return self.flavors["flavors"] def add_variant(self, variant: DebianVariant) -> bool: """Add a new Debian variant""" try: # Check if variant already exists for existing_variant in self.variants["variants"]: if existing_variant["name"] == variant.name: print(f"Variant {variant.name} already exists") return False # Add new variant self.variants["variants"].append(asdict(variant)) self._save_variants() return True except Exception as e: print(f"Failed to add variant: {e}") return False def add_flavor(self, flavor: DesktopFlavor) -> bool: """Add a new desktop flavor""" try: # Check if flavor already exists for existing_flavor in self.flavors["flavors"]: if existing_flavor["name"] == flavor.name: print(f"Flavor {flavor.name} already exists") return False # Add new flavor self.flavors["flavors"].append(asdict(flavor)) self._save_flavors() return True except Exception as e: print(f"Failed to add flavor: {e}") return False def update_variant(self, name: str, **kwargs) -> bool: """Update variant configuration""" try: for variant in self.variants["variants"]: if variant["name"] == name: for key, value in kwargs.items(): if key in variant: variant[key] = value self._save_variants() return True print(f"Variant {name} not found") return False except Exception as e: print(f"Failed to update variant: {e}") return False def update_flavor(self, name: str, **kwargs) -> bool: """Update flavor configuration""" try: for flavor in self.flavors["flavors"]: if flavor["name"] == name: for key, value in kwargs.items(): if key in flavor: flavor[key] = value self._save_flavors() return True print(f"Flavor {name} not found") return False except Exception as e: print(f"Failed to update flavor: {e}") return False def get_variant_mirrors(self, variant_name: str) -> List[str]: """Get mirrors for a specific variant""" variant = self.get_variant(variant_name) if variant: return variant.get("mirrors", []) return [] def get_variant_architectures(self, variant_name: str) -> List[str]: """Get supported architectures for a variant""" variant = self.get_variant(variant_name) if variant: return variant.get("architectures", []) return [] def get_flavor_packages(self, flavor_name: str, variant_name: str = None) -> List[str]: """Get packages for a flavor, optionally filtered by variant""" flavor = self.get_flavor(flavor_name) if not flavor: return [] packages = flavor.get("packages", []) dependencies = flavor.get("dependencies", []) # Filter by variant if specified if variant_name and variant_name not in flavor.get("variants", []): return [] return packages + dependencies def create_variant_sources_list(self, variant_name: str, architecture: str = "amd64") -> str: """Create sources.list content for a variant""" variant = self.get_variant(variant_name) if not variant: return "" sources_list = [] # Main repository for mirror in variant.get("mirrors", []): if "security.debian.org" in mirror: continue # Security handled separately sources_list.append(f"deb {mirror} {variant_name} main contrib non-free-firmware") # Updates repository if variant.get("updates_support", False): sources_list.append(f"deb {mirror} {variant_name}-updates main contrib non-free-firmware") # Backports repository if variant.get("backports_support", False): sources_list.append(f"deb {mirror} {variant_name}-backports main contrib non-free-firmware") # Security repository if variant.get("security_support", False): security_mirrors = [m for m in variant.get("mirrors", []) if "security.debian.org" in m] for mirror in security_mirrors: sources_list.append(f"deb {mirror} {variant_name}-security main contrib non-free-firmware") return "\n".join(sources_list) def get_variant_status_summary(self) -> Dict[str, Any]: """Get summary of variant statuses""" summary = { "stable": [], "testing": [], "unstable": [], "total": len(self.variants["variants"]) } for variant in self.variants["variants"]: status = variant.get("status", "unknown") if status in summary: summary[status].append(variant["name"]) return summary def get_flavor_compatibility_matrix(self) -> Dict[str, List[str]]: """Get compatibility matrix between variants and flavors""" matrix = {} for variant in self.variants["variants"]: variant_name = variant["name"] matrix[variant_name] = [] for flavor in self.flavors["flavors"]: if variant_name in flavor.get("variants", []): matrix[variant_name].append(flavor["name"]) return matrix def validate_variant_flavor_combination(self, variant_name: str, flavor_name: str) -> bool: """Validate if a variant and flavor combination is supported""" variant = self.get_variant(variant_name) flavor = self.get_flavor(flavor_name) if not variant or not flavor: return False return variant_name in flavor.get("variants", []) def get_recommended_flavor_for_variant(self, variant_name: str) -> Optional[str]: """Get recommended flavor for a variant based on priority""" compatible_flavors = [] for flavor in self.flavors["flavors"]: if variant_name in flavor.get("variants", []) and flavor.get("enabled", True): compatible_flavors.append((flavor["name"], flavor.get("priority", 500))) if compatible_flavors: # Sort by priority (lower is higher priority) compatible_flavors.sort(key=lambda x: x[1]) return compatible_flavors[0][0] return None def main(): """Test variants and flavors management""" manager = DebianVariantsManager() # List variants print("Debian Variants:") variants = manager.list_variants() for variant in variants: print(f" - {variant['name']} ({variant['codename']}): {variant['status']}") # List flavors print("\nDesktop Flavors:") flavors = manager.list_flavors() for flavor in flavors: print(f" - {flavor['display_name']}: {flavor['description']}") # Show compatibility matrix print("\nVariant-Flavor Compatibility:") matrix = manager.get_flavor_compatibility_matrix() for variant, supported_flavors in matrix.items(): print(f" {variant}: {', '.join(supported_flavors)}") # Show variant status summary print("\nVariant Status Summary:") summary = manager.get_variant_status_summary() for status, variant_list in summary.items(): if status != "total": print(f" {status}: {', '.join(variant_list)}") if __name__ == "__main__": main()