mirror of https://github.com/Chizi123/Arch-autobuild-repo.git

44906d2119208335402cb3f4fd62a4bb38258231..cf7fcc2bd3c99b5eb4bdfac2752bee2b82a593f7
2 hours ago Joel Grunbaum
email everytime logic
cf7fcc diff | tree
20 hours ago Joel Grunbaum
Add an option to email on every build or only on failures
700cfa diff | tree
20 hours ago Joel Grunbaum
Support starttls
b09614 diff | tree
20 hours ago Joel Grunbaum
Copy only latest package version
e072db diff | tree
4 files modified
112 ■■■■ changed files
config/config.example.yaml 2 ●●●●● patch | view | raw | blame | history
src/archrepobuild/config.py 1 ●●●● patch | view | raw | blame | history
src/archrepobuild/notifications.py 33 ●●●● patch | view | raw | blame | history
src/archrepobuild/repo.py 76 ●●●● patch | view | raw | blame | history
config/config.example.yaml
@@ -41,6 +41,8 @@
  email:
    # Enable email notifications on build failures
    enabled: false
    # Send email every time the script is run or only on failures
    email_everytime: true
    # Recipient email address
    to: ""
    # Sender email address
src/archrepobuild/config.py
@@ -38,6 +38,7 @@
    """Email notification settings."""
    enabled: bool = Field(default=False, description="Enable email notifications")
    email_everytime: bool = Field(default=False, description="Send email every time the script is run")
    to: str = Field(default="", description="Recipient email address")
    from_addr: str = Field(default="", alias="from", description="Sender email address")
    smtp_host: str = Field(default="localhost", description="SMTP server host")
src/archrepobuild/notifications.py
@@ -106,7 +106,7 @@
    async def send(self, summary: BuildSummary, config: Config) -> bool:
        """Send email notification."""
        if not self.config.enabled:
        if not self.config.enabled and ((not self.config.email_everytime) and summary.failed == 0):
            return True
        if not self.config.to:
@@ -140,21 +140,24 @@
    def _send_email(self, msg: MIMEMultipart) -> None:
        """Send email synchronously (called from executor)."""
        smtp_class = smtplib.SMTP
        use_starttls = False
        if self.config.use_tls:
            context = ssl.create_default_context()
            with smtplib.SMTP_SSL(
                self.config.smtp_host,
                self.config.smtp_port,
                context=context,
            ) as server:
                if self.config.username and self.config.password:
                    server.login(self.config.username, self.config.password)
                server.send_message(msg)
        else:
            with smtplib.SMTP(self.config.smtp_host, self.config.smtp_port) as server:
                if self.config.username and self.config.password:
                    server.login(self.config.username, self.config.password)
                server.send_message(msg)
            if self.config.smtp_port == 465:
                smtp_class = smtplib.SMTP_SSL
            else:
                use_starttls = True
        with smtp_class(self.config.smtp_host, self.config.smtp_port) as server:
            if use_starttls:
                context = ssl.create_default_context()
                server.starttls(context=context)
            if self.config.username and self.config.password:
                server.login(self.config.username, self.config.password)
            server.send_message(msg)
class WebhookBackend(NotificationBackend):
src/archrepobuild/repo.py
@@ -67,6 +67,48 @@
            cwd=self.config.repository.path,
        )
    def _vercmp(self, v1: str, v2: str) -> int:
        """Compare two version strings using vercmp.
        Returns:
            >0 if v1 > v2, <0 if v1 < v2, 0 if equal
        """
        try:
            result = subprocess.run(
                ["vercmp", v1, v2],
                capture_output=True,
                text=True,
                check=True,
            )
            return int(result.stdout.strip())
        except (subprocess.CalledProcessError, ValueError):
            return 0
    def _parse_pkg_filename(self, filename: str) -> tuple[str, str]:
        """Parse package name and version from a filename.
        Args:
            filename: Package filename (e.g. name-version-rel-arch.pkg.tar.zst)
        Returns:
            Tuple of (package_name, version-release)
        """
        # Remove suffixes
        stem = filename
        for suffix in [".pkg.tar.zst", ".pkg.tar.xz", ".pkg.tar.gz", ".pkg.tar.bz2", ".pkg.tar"]:
            if stem.endswith(suffix):
                stem = stem[:-len(suffix)]
                break
        # Format: name-version-rel-arch
        parts = stem.rsplit("-", 3)
        if len(parts) == 4:
            name = parts[0]
            version = f"{parts[1]}-{parts[2]}"
            return name, version
        return stem, "unknown"
    def ensure_repo_exists(self) -> None:
        """Ensure repository directory and database exist."""
        self.config.repository.path.mkdir(parents=True, exist_ok=True)
@@ -101,12 +143,22 @@
        with self._get_repo_lock():
            self.ensure_repo_exists()
            # Remove old versions of this package
            self._remove_old_packages(build_result.package)
            # Group artifacts by package name and only keep the latest version
            latest_artifacts: dict[str, Path] = {}
            for artifact in build_result.artifacts:
                name, version = self._parse_pkg_filename(artifact.name)
                if name not in latest_artifacts:
                    latest_artifacts[name] = artifact
                else:
                    _, current_best_ver = self._parse_pkg_filename(latest_artifacts[name].name)
                    if self._vercmp(version, current_best_ver) > 0:
                        latest_artifacts[name] = artifact
            artifacts_to_copy = list(latest_artifacts.values())
            # Copy artifacts to repo directory
            copied_files: list[Path] = []
            for artifact in build_result.artifacts:
            for artifact in artifacts_to_copy:
                dest = self.config.repository.path / artifact.name
                shutil.copy2(artifact, dest)
                copied_files.append(dest)
@@ -126,6 +178,10 @@
                logger.error(f"Failed to add packages to database: {result.stderr}")
                return False
            # Clean up old versions in repo for each package name added
            for name in latest_artifacts.keys():
                self._remove_old_packages(name)
            logger.info(f"Added {len(copied_files)} package(s) to repository")
            return True
@@ -185,11 +241,11 @@
        removed = 0
        for f in to_remove:
            f.remove()
            f.unlink()
            # Also remove signature
            sig = f.with_suffix(f.suffix + ".sig")
            if sig.exists():
                sig.remove()
                sig.unlink()
            removed += 1
        if removed:
@@ -209,15 +265,7 @@
            if f.name.endswith(".sig"):
                continue
            # Parse package name and version from filename
            # Format: name-version-rel-arch.pkg.tar.zst
            parts = f.stem.replace(".pkg.tar", "").rsplit("-", 3)
            if len(parts) >= 3:
                name = "-".join(parts[:-2])
                version = f"{parts[-2]}-{parts[-1]}" if len(parts) > 2 else parts[-1]
            else:
                name = f.stem
                version = "unknown"
            name, version = self._parse_pkg_filename(f.name)
            stat = f.stat()
            packages.append(PackageInfo(