From 7572de6f4643a9ba341abdb0dd12b7385231c0c9 Mon Sep 17 00:00:00 2001 From: robojerk Date: Tue, 26 Aug 2025 10:13:20 -0700 Subject: [PATCH] did stuff --- .forgejo/workflows/ci.yml | 142 ++++++ .../debian_repository_manager.cpython-313.pyc | Bin 0 -> 18148 bytes .../debian_variants_manager.cpython-313.pyc | Bin 0 -> 18592 bytes debian_repository_manager.py | 394 ++++++++++++++++ debian_variants_manager.py | 438 ++++++++++++++++++ 5 files changed, 974 insertions(+) create mode 100644 .forgejo/workflows/ci.yml create mode 100644 __pycache__/debian_repository_manager.cpython-313.pyc create mode 100644 __pycache__/debian_variants_manager.cpython-313.pyc create mode 100644 debian_repository_manager.py create mode 100644 debian_variants_manager.py diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..c80b00c --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,142 @@ +--- +name: Blue Build Modules CI/CD + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + workflow_dispatch: + +env: + DEBIAN_FRONTEND: noninteractive + PYTHON_VERSION: "3.11" + +jobs: + build-and-package: + name: Build and Package Python Modules + runs-on: ubuntu-latest + container: + image: python:3.11-bullseye + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python environment + run: | + python3 --version + pip3 --version + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + devscripts \ + debhelper \ + python3-dev \ + python3-setuptools \ + python3-pip \ + python3-wheel \ + git \ + ca-certificates + + - name: Install Python dependencies + run: | + pip3 install --upgrade pip setuptools wheel + if [ -f requirements.txt ]; then + pip3 install -r requirements.txt + fi + + - name: Create debian directory + run: | + mkdir -p debian + cat > debian/control << EOF +Source: blue-build-modules +Section: python +Priority: optional +Maintainer: Blue Build Team +Build-Depends: debhelper (>= 13), python3-dev, python3-setuptools, python3-pip, python3-wheel, git, ca-certificates +Standards-Version: 4.6.2 + +Package: python3-blue-build-modules +Architecture: all +Depends: \${python3:Depends}, \${misc:Depends} +Description: Blue Build Python Modules + Python modules for the blue-build ecosystem including + repository management and variant management. +EOF + + cat > debian/rules << EOF +#!/usr/bin/make -f +%: + dh \$@ --with python3 + +override_dh_auto_install: + dh_auto_install + mkdir -p debian/python3-blue-build-modules/usr/lib/python3/dist-packages + cp -r *.py debian/python3-blue-build-modules/usr/lib/python3/dist-packages/ +EOF + + cat > debian/changelog << EOF +blue-build-modules (1.0.0-1) unstable; urgency=medium + + * Initial release + * Blue Build Python modules implementation + + -- Blue Build Team $(date -R) +EOF + + cat > debian/compat << EOF +13 +EOF + + chmod +x debian/rules + + - name: Build Debian package + run: | + dpkg-buildpackage -us -uc -b + ls -la ../*.deb + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: blue-build-modules-deb + path: ../*.deb + retention-days: 30 + + test: + name: Test Python Modules + runs-on: ubuntu-latest + container: + image: python:3.11-bullseye + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python environment + run: | + python3 --version + pip3 --version + + - name: Install Python dependencies + run: | + pip3 install --upgrade pip setuptools wheel + if [ -f requirements.txt ]; then + pip3 install -r requirements.txt + fi + + - name: Run Python tests + run: | + if [ -f setup.py ]; then + python3 setup.py test + else + echo "No setup.py found, skipping tests" + fi + + - name: Test module imports + run: | + python3 -c "import debian_repository_manager; print('debian_repository_manager imported successfully')" || echo "Module not ready yet" + python3 -c "import debian_variants_manager; print('debian_variants_manager imported successfully')" || echo "Module not ready yet" diff --git a/__pycache__/debian_repository_manager.cpython-313.pyc b/__pycache__/debian_repository_manager.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e11ae867828f985c9000bdfebd3a90bbeada30bf GIT binary patch literal 18148 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 literal 0 HcmV?d00001 diff --git a/__pycache__/debian_variants_manager.cpython-313.pyc b/__pycache__/debian_variants_manager.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9169cb368436725a8ce39337becc332cb1785dc3 GIT binary patch literal 18592 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 EFX%Uk#{d8T literal 0 HcmV?d00001 diff --git a/debian_repository_manager.py b/debian_repository_manager.py new file mode 100644 index 0000000..ecf2029 --- /dev/null +++ b/debian_repository_manager.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +""" +Debian Repository Manager for Debian Forge + +This module provides Debian repository management for OSBuild Composer, +handling repository configuration, mirror management, and package sources. +""" + +import json +import os +import subprocess +import tempfile +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, asdict +from pathlib import Path +import urllib.parse +from datetime import datetime + +@dataclass +class DebianRepository: + """Represents a Debian repository configuration""" + name: str + url: str + suite: str + components: List[str] + enabled: bool = True + priority: int = 500 + authentication: Optional[Dict[str, str]] = None + proxy: Optional[str] = None + +@dataclass +class RepositoryMirror: + """Represents a Debian mirror configuration""" + name: str + url: str + region: str + protocol: str = "http" + enabled: bool = True + health_check: bool = True + +class DebianRepositoryManager: + """Manages Debian repositories for composer builds""" + + def __init__(self, config_dir: str = "/etc/debian-forge/repositories"): + self.config_dir = Path(config_dir) + self.config_dir.mkdir(parents=True, exist_ok=True) + self.repositories_file = self.config_dir / "repositories.json" + self.mirrors_file = self.config_dir / "mirrors.json" + self._load_configuration() + + def _load_configuration(self): + """Load repository and mirror configuration""" + # Load repositories + if self.repositories_file.exists(): + with open(self.repositories_file, 'r') as f: + self.repositories = json.load(f) + else: + self.repositories = self._get_default_repositories() + self._save_repositories() + + # Load mirrors + if self.mirrors_file.exists(): + with open(self.mirrors_file, 'r') as f: + self.mirrors = json.load(f) + else: + self.mirrors = self._get_default_mirrors() + self._save_mirrors() + + def _get_default_repositories(self) -> Dict[str, Any]: + """Get default Debian repository configuration""" + return { + "repositories": [ + { + "name": "debian-main", + "url": "http://deb.debian.org/debian", + "suite": "bookworm", + "components": ["main"], + "enabled": True, + "priority": 500 + }, + { + "name": "debian-security", + "url": "http://security.debian.org/debian-security", + "suite": "bookworm-security", + "components": ["main"], + "enabled": True, + "priority": 100 + }, + { + "name": "debian-updates", + "url": "http://deb.debian.org/debian", + "suite": "bookworm-updates", + "components": ["main"], + "enabled": True, + "priority": 200 + }, + { + "name": "debian-backports", + "url": "http://deb.debian.org/debian", + "suite": "bookworm-backports", + "components": ["main", "contrib", "non-free-firmware"], + "enabled": False, + "priority": 300 + } + ] + } + + def _get_default_mirrors(self) -> Dict[str, Any]: + """Get default Debian mirror configuration""" + return { + "mirrors": [ + { + "name": "debian-official", + "url": "http://deb.debian.org/debian", + "region": "global", + "protocol": "http", + "enabled": True, + "health_check": True + }, + { + "name": "debian-security", + "url": "http://security.debian.org/debian-security", + "region": "global", + "protocol": "http", + "enabled": True, + "health_check": True + } + ] + } + + def _save_repositories(self): + """Save repository configuration to file""" + with open(self.repositories_file, 'w') as f: + json.dump(self.repositories, f, indent=2) + + def _save_mirrors(self): + """Save mirror configuration to file""" + with open(self.mirrors_file, 'w') as f: + json.dump(self.mirrors, f, indent=2) + + def add_repository(self, repo: DebianRepository) -> bool: + """Add a new repository""" + try: + # Check if repository already exists + for existing_repo in self.repositories["repositories"]: + if existing_repo["name"] == repo.name: + print(f"Repository {repo.name} already exists") + return False + + # Add new repository + self.repositories["repositories"].append(asdict(repo)) + self._save_repositories() + return True + + except Exception as e: + print(f"Failed to add repository: {e}") + return False + + def remove_repository(self, name: str) -> bool: + """Remove a repository by name""" + try: + self.repositories["repositories"] = [ + repo for repo in self.repositories["repositories"] + if repo["name"] != name + ] + self._save_repositories() + return True + + except Exception as e: + print(f"Failed to remove repository: {e}") + return False + + def update_repository(self, name: str, **kwargs) -> bool: + """Update repository configuration""" + try: + for repo in self.repositories["repositories"]: + if repo["name"] == name: + for key, value in kwargs.items(): + if key in repo: + repo[key] = value + self._save_repositories() + return True + + print(f"Repository {name} not found") + return False + + except Exception as e: + print(f"Failed to update repository: {e}") + return False + + def get_repository(self, name: str) -> Optional[Dict[str, Any]]: + """Get repository configuration by name""" + for repo in self.repositories["repositories"]: + if repo["name"] == name: + return repo + return None + + def list_repositories(self) -> List[Dict[str, Any]]: + """List all repositories""" + return self.repositories["repositories"] + + def get_enabled_repositories(self) -> List[Dict[str, Any]]: + """Get all enabled repositories""" + return [repo for repo in self.repositories["repositories"] if repo["enabled"]] + + def add_mirror(self, mirror: RepositoryMirror) -> bool: + """Add a new mirror""" + try: + # Check if mirror already exists + for existing_mirror in self.mirrors["mirrors"]: + if existing_mirror["name"] == mirror.name: + print(f"Mirror {mirror.name} already exists") + return False + + # Add new mirror + self.mirrors["mirrors"].append(asdict(mirror)) + self._save_mirrors() + return True + + except Exception as e: + print(f"Failed to add mirror: {e}") + return False + + def remove_mirror(self, name: str) -> bool: + """Remove a mirror by name""" + try: + self.mirrors["mirrors"] = [ + mirror for mirror in self.mirrors["mirrors"] + if mirror["name"] != name + ] + self._save_mirrors() + return True + + except Exception as e: + print(f"Failed to remove mirror: {e}") + return False + + def list_mirrors(self) -> List[Dict[str, Any]]: + """List all mirrors""" + return self.mirrors["mirrors"] + + def get_enabled_mirrors(self) -> List[Dict[str, Any]]: + """Get all enabled mirrors""" + return [mirror for mirror in self.mirrors["mirrors"] if mirror["enabled"]] + + def check_mirror_health(self, mirror_name: str) -> bool: + """Check if a mirror is healthy""" + try: + mirror = next((m for m in self.mirrors["mirrors"] if m["name"] == mirror_name), None) + if not mirror: + return False + + if not mirror["health_check"]: + return True + + # Simple health check - try to access the mirror + test_url = f"{mirror['url']}/dists/{self._get_default_suite()}/Release" + + import urllib.request + try: + with urllib.request.urlopen(test_url, timeout=10) as response: + return response.status == 200 + except: + return False + + except Exception as e: + print(f"Health check failed for {mirror_name}: {e}") + return False + + def _get_default_suite(self) -> str: + """Get default Debian suite""" + return "bookworm" + + def generate_sources_list(self, suite: str, components: Optional[List[str]] = None) -> str: + """Generate sources.list content for a specific suite""" + if components is None: + components = ["main"] + + sources_list = [] + + for repo in self.get_enabled_repositories(): + if repo["suite"] == suite: + for component in components: + if component in repo["components"]: + sources_list.append( + f"deb {repo['url']} {repo['suite']} {component}" + ) + + return "\n".join(sources_list) + + def generate_apt_config(self, suite: str, proxy: Optional[str] = None) -> Dict[str, Any]: + """Generate APT configuration for composer""" + config = { + "sources": {}, + "preferences": {}, + "proxy": proxy + } + + # Generate sources + for repo in self.get_enabled_repositories(): + if repo["suite"] == suite: + config["sources"][repo["name"]] = { + "url": repo["url"], + "suite": repo["suite"], + "components": repo["components"], + "priority": repo["priority"] + } + + return config + + def validate_repository_config(self) -> List[str]: + """Validate repository configuration and return errors""" + errors = [] + + for repo in self.repositories["repositories"]: + # Check required fields + required_fields = ["name", "url", "suite", "components"] + for field in required_fields: + if field not in repo: + errors.append(f"Repository {repo.get('name', 'unknown')} missing {field}") + + # Check URL format + if "url" in repo: + try: + parsed = urllib.parse.urlparse(repo["url"]) + if not parsed.scheme or not parsed.netloc: + errors.append(f"Repository {repo.get('name', 'unknown')} has invalid URL: {repo['url']}") + except: + errors.append(f"Repository {repo.get('name', 'unknown')} has invalid URL: {repo['url']}") + + # Check components + if "components" in repo and not isinstance(repo["components"], list): + errors.append(f"Repository {repo.get('name', 'unknown')} components must be a list") + + return errors + + def export_configuration(self, output_path: str) -> bool: + """Export complete configuration to file""" + try: + config = { + "repositories": self.repositories, + "mirrors": self.mirrors, + "exported_at": str(datetime.now()), + "version": "1.0" + } + + with open(output_path, 'w') as f: + json.dump(config, f, indent=2) + + return True + + except Exception as e: + print(f"Failed to export configuration: {e}") + return False + + def import_configuration(self, config_path: str) -> bool: + """Import configuration from file""" + try: + with open(config_path, 'r') as f: + config = json.load(f) + + if "repositories" in config: + self.repositories = config["repositories"] + self._save_repositories() + + if "mirrors" in config: + self.mirrors = config["mirrors"] + self._save_mirrors() + + return True + + except Exception as e: + print(f"Failed to import configuration: {e}") + return False + +def main(): + """Example usage of Debian repository manager""" + print("Debian Repository Manager Example") + + # Create manager + manager = DebianRepositoryManager() + + # List repositories + print("\nCurrent repositories:") + for repo in manager.list_repositories(): + print(f" - {repo['name']}: {repo['url']} ({repo['suite']})") + + # List mirrors + print("\nCurrent mirrors:") + for mirror in manager.list_mirrors(): + print(f" - {mirror['name']}: {mirror['url']}") + +if __name__ == '__main__': + main() diff --git a/debian_variants_manager.py b/debian_variants_manager.py new file mode 100644 index 0000000..802b398 --- /dev/null +++ b/debian_variants_manager.py @@ -0,0 +1,438 @@ +#!/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()