aur-shield v0.1.0: AI-powered AUR firewall

- FastAPI server with scan/build/repo endpoints
- LLM scanner (Ollama) with regex pre-scan
- makepkg/devtools builder with chroot isolation
- Scan cache with TTL + PKGBUILD hash
- Client installer + safe-yay wrapper
- Docs + config example
This commit is contained in:
arch_agent
2026-08-04 09:35:35 +02:00
commit adee5dfc78
14 changed files with 1368 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
"""Package builder — runs makepkg in a clean environment."""
from __future__ import annotations
import os
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from .config import ServerConfig, BuildConfig
@dataclass
class BuildResult:
success: bool
pkg_files: list[str] # paths to .pkg.tar.zst
log: str
error: str = ""
def build_package(
pkgbuild: str,
package_name: str,
server_cfg: ServerConfig,
build_cfg: BuildConfig,
) -> BuildResult:
"""Build a package from a PKGBUILD string."""
work_dir = Path(server_cfg.work_dir) / package_name
work_dir.mkdir(parents=True, exist_ok=True)
# Write PKGBUILD
pkgbuild_path = work_dir / "PKGBUILD"
pkgbuild_path.write_text(pkgbuild)
# Clean previous build artifacts
for f in work_dir.glob("*.pkg.tar.*"):
f.unlink()
log_lines: list[str] = []
try:
if build_cfg.use_devtools and shutil.which("extra-x86_64-build"):
# Use devtools chroot for isolation
cmd = [
"extra-x86_64-build",
"--", "-cC",
]
cwd = work_dir
else:
# Fallback: makepkg directly
cmd = [
"makepkg", "-sf", "--noconfirm", "--noprogressbar",
]
cwd = work_dir
log_lines.append(f"Building {package_name} with: {' '.join(cmd)}")
result = subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
timeout=build_cfg.timeout,
env={**os.environ, "MAKEFLAGS": "-j$(nproc)"},
)
log_lines.append(result.stdout)
if result.stderr:
log_lines.append("--- stderr ---")
log_lines.append(result.stderr)
if result.returncode != 0:
return BuildResult(
success=False,
pkg_files=[],
log="\n".join(log_lines),
error=f"Build failed (exit {result.returncode})",
)
# Find built packages
pkg_files = [
str(f) for f in work_dir.glob("*.pkg.tar.zst")
]
if not pkg_files:
return BuildResult(
success=False,
pkg_files=[],
log="\n".join(log_lines),
error="Build succeeded but no .pkg.tar.zst found",
)
log_lines.append(f"Built: {pkg_files}")
return BuildResult(
success=True,
pkg_files=pkg_files,
log="\n".join(log_lines),
)
except subprocess.TimeoutExpired:
return BuildResult(
success=False,
pkg_files=[],
log="\n".join(log_lines),
error=f"Build timed out after {build_cfg.timeout}s",
)
except Exception as e:
return BuildResult(
success=False,
pkg_files=[],
log="\n".join(log_lines),
error=str(e),
)
def add_to_repo(
pkg_files: list[str],
repo_dir: str,
) -> str:
"""Add built packages to the local pacman repo using repo-add."""
repo_path = Path(repo_dir)
repo_path.mkdir(parents=True, exist_ok=True)
db_name = "aur-shield.db"
db_file = repo_path / f"{db_name}.tar.gz"
# Copy packages to repo dir
copied = []
for pkg in pkg_files:
src = Path(pkg)
dst = repo_path / src.name
shutil.copy2(src, dst)
copied.append(str(dst))
if not copied:
return str(db_file)
# Run repo-add
cmd = ["repo-add", str(db_file)] + copied
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60,
)
if result.returncode != 0:
raise RuntimeError(f"repo-add failed: {result.stderr}")
return str(db_file)