Inital ver (port)
This commit is contained in:
53
.gitea/workflows/nightly.yml
Normal file
53
.gitea/workflows/nightly.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
name: nightly-build
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 03:30 UTC every night
|
||||
- cron: "30 3 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
only:
|
||||
description: "Space-separated package dirs to build (blank = all)"
|
||||
required: false
|
||||
default: ""
|
||||
replace:
|
||||
description: "Replace existing registry files on version clash"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux:latest
|
||||
# makepkg refuses to run as root; we create a build user below.
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Install toolchain
|
||||
run: |
|
||||
pacman -Syu --noconfirm --needed base-devel git python sudo
|
||||
|
||||
- name: Create build user
|
||||
run: |
|
||||
useradd -m -s /bin/bash builder
|
||||
echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder
|
||||
chmod 0440 /etc/sudoers.d/builder
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fix permissions
|
||||
run: chown -R builder:builder "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Build and publish
|
||||
env:
|
||||
AUTOPKG_REGISTRY_URL: ${{ github.server_url }}
|
||||
AUTOPKG_OWNER: ${{ github.repository_owner }}
|
||||
AUTOPKG_REPO: arch
|
||||
AUTOPKG_USER: ${{ github.actor }}
|
||||
AUTOPKG_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
||||
AUTOPKG_REPLACE: ${{ github.event.inputs.replace }}
|
||||
ONLY: ${{ github.event.inputs.only }}
|
||||
run: |
|
||||
sudo -E -u builder \
|
||||
python3 autopackage.py --keep-going ${ONLY:+--only $ONLY}
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.build/
|
||||
__pycache__/
|
||||
*.pkg.tar.*
|
||||
*.log
|
||||
134
README.md
Normal file
134
README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# autopackage
|
||||
|
||||
Nightly, directory-driven Arch package builder for Gitea CI.
|
||||
|
||||
Every top-level folder in this repo is a package. The nightly job
|
||||
([`.gitea/workflows/nightly.yml`](.gitea/workflows/nightly.yml)) builds them all
|
||||
in an `archlinux:latest` container and publishes the resulting
|
||||
`*.pkg.tar.zst` files to a [Gitea Arch package
|
||||
registry](https://docs.gitea.com/usage/packages/arch). Gitea keeps the pacman
|
||||
database up to date automatically — there is no `repo-add` step.
|
||||
|
||||
## How a folder is classified
|
||||
|
||||
For a folder `foo/`:
|
||||
|
||||
| Contents of `foo/` | Treated as | What happens |
|
||||
| ----------------------------------------- | ---------- | ------------ |
|
||||
| Contains a `PKGBUILD` | **custom** | Built as-is from the folder. |
|
||||
| No `PKGBUILD` | **AUR** | `https://aur.archlinux.org/foo.git` is cloned, then overrides/patches from `foo/` are applied. |
|
||||
|
||||
### AUR packages
|
||||
|
||||
The folder name **is** the AUR package name. The simplest AUR package is a
|
||||
folder that just contains a `.gitkeep` (git won't track an empty directory):
|
||||
|
||||
```
|
||||
openrgb-git/
|
||||
.gitkeep
|
||||
```
|
||||
|
||||
To customize an AUR package without forking the whole `PKGBUILD`, drop files in
|
||||
the folder:
|
||||
|
||||
- **Patches** — any `*.patch` / `*.diff` files are applied (in sorted order) on
|
||||
top of the freshly cloned AUR repo. `git apply` is tried first, then
|
||||
`patch -Np1`.
|
||||
- **Overrides** — any other file (e.g. an extra source file, a `.install`
|
||||
script) is copied into the clone, replacing a file of the same name.
|
||||
|
||||
> Note: if you add a full `PKGBUILD` to the folder it becomes a *custom*
|
||||
> package and the AUR repo is no longer cloned. Use patches/overrides to tweak
|
||||
> an AUR package.
|
||||
|
||||
Example AUR package with a patch:
|
||||
|
||||
```
|
||||
openrgb-git/
|
||||
.gitkeep
|
||||
0001-fix-udev-rules.patch
|
||||
```
|
||||
|
||||
### Custom packages
|
||||
|
||||
A folder with its own `PKGBUILD` (plus any local sources) is built directly:
|
||||
|
||||
```
|
||||
my-thing/
|
||||
PKGBUILD
|
||||
my-thing.install
|
||||
some-source.tar.gz
|
||||
```
|
||||
|
||||
## Publishing / consuming
|
||||
|
||||
Packages are uploaded to `{GITEA_URL}/api/packages/{owner}/arch/{repo}` where
|
||||
`{repo}` defaults to `arch` (configurable via `AUTOPKG_REPO`).
|
||||
|
||||
Add this to `/etc/pacman.conf` on client machines (replace the host/owner):
|
||||
|
||||
```ini
|
||||
[arch]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://git.example.com/api/packages/<owner>/arch/arch/$arch
|
||||
```
|
||||
|
||||
> `SigLevel = Optional TrustAll` is used because packages are unsigned by
|
||||
> default. To sign them, set up a GPG key on the runner and pass `--sign`
|
||||
> (the `.sig` files are uploaded automatically); then you can switch to a
|
||||
> stricter `SigLevel`.
|
||||
|
||||
## CI configuration
|
||||
|
||||
The workflow needs one secret:
|
||||
|
||||
- **`PACKAGE_TOKEN`** — a Gitea access token with the `write:package` scope
|
||||
(and `read:package`). Create it under *Settings → Applications → Generate New
|
||||
Token*, then add it under the repo's *Settings → Actions → Secrets*.
|
||||
|
||||
Everything else is derived from built-in Gitea Actions variables
|
||||
(`GITHUB_SERVER_URL`, `GITHUB_REPOSITORY_OWNER`, `github.actor`).
|
||||
|
||||
The job runs nightly at 03:30 UTC and can also be triggered manually
|
||||
(*workflow_dispatch*) with optional `only` (subset of package dirs) and
|
||||
`replace` inputs.
|
||||
|
||||
## Running locally
|
||||
|
||||
```bash
|
||||
# Dry run: show what would be built/published
|
||||
./autopackage.py --dry-run
|
||||
|
||||
# Build a subset without publishing
|
||||
./autopackage.py --only openrgb-git paru --no-publish
|
||||
|
||||
# Build everything and publish to a registry
|
||||
AUTOPKG_REGISTRY_URL=https://git.example.com \
|
||||
AUTOPKG_OWNER=joe \
|
||||
AUTOPKG_TOKEN=xxxxxxxx \
|
||||
./autopackage.py --keep-going
|
||||
```
|
||||
|
||||
makepkg must run as a non-root user with `sudo` available for dependency
|
||||
installation. Builds happen in `./.build/` (gitignored).
|
||||
|
||||
### Useful flags
|
||||
|
||||
| Flag | Purpose |
|
||||
| ---- | ------- |
|
||||
| `--only NAME ...` | Build just these package dirs. |
|
||||
| `--no-publish` | Build only; skip uploads. |
|
||||
| `--dry-run` | Print actions without running them. |
|
||||
| `--cleanbuild` | Pass `--cleanbuild` to makepkg. |
|
||||
| `--sign` | Sign packages and upload `.sig` files. |
|
||||
| `--replace` | Delete + re-upload on a version clash (HTTP 409). |
|
||||
| `--keep-going` | Continue if a package fails. |
|
||||
|
||||
Config can also come from env vars: `AUTOPKG_REGISTRY_URL`, `AUTOPKG_OWNER`,
|
||||
`AUTOPKG_REPO`, `AUTOPKG_USER`, `AUTOPKG_TOKEN`, `AUTOPKG_REPLACE`.
|
||||
|
||||
## Ignoring folders
|
||||
|
||||
Top-level dirs `.git`, `.gitea`, `.github`, `.build`, `scripts`, `dist` and
|
||||
`__pycache__` are never treated as packages. Add more (one name per line) in a
|
||||
`.autopackageignore` file at the repo root.
|
||||
432
autopackage.py
Normal file
432
autopackage.py
Normal file
@@ -0,0 +1,432 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
autopackage.py
|
||||
|
||||
This is a rewrite/port of my original autopackage python script, that i used
|
||||
to just keep to myself but... now im just trusting enough of my personal supply chain
|
||||
that i think im comfy sharing package builds... at least for friends!
|
||||
|
||||
I mostly intend for CI to run this script but, if you want to make tweaks or experiment locally
|
||||
thats super fine too!
|
||||
|
||||
Examples:
|
||||
./autopackage.py --dry-run
|
||||
./autopackage.py --only openrgb-git paru
|
||||
./autopackage.py --no-publish # build everything, skip upload
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
AUR_BASE = "https://aur.archlinux.org"
|
||||
|
||||
# Top-level directories that are never packages
|
||||
DEFAULT_IGNORE = {
|
||||
".git",
|
||||
".gitea",
|
||||
".github",
|
||||
".build",
|
||||
"scripts",
|
||||
"dist",
|
||||
"__pycache__",
|
||||
}
|
||||
|
||||
PKG_GLOBS = ("*.pkg.tar.zst", "*.pkg.tar.xz", "*.pkg.tar.gz", "*.pkg.tar.bz2")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
repo_root: Path
|
||||
workdir: Path
|
||||
makepkg_cmd: List[str]
|
||||
sign: bool
|
||||
keep_going: bool
|
||||
dry_run: bool
|
||||
publish: bool
|
||||
replace: bool
|
||||
# Gitea Arch registry settings
|
||||
registry_url: str
|
||||
owner: str
|
||||
arch_repo: str
|
||||
auth_user: str
|
||||
token: str
|
||||
ignore: set = field(default_factory=set)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Shell helpers
|
||||
# --------------------------------------------------------------------------- #
|
||||
def run(cmd: List[str], cwd: Optional[Path] = None, dry_run: bool = False,
|
||||
check: bool = True) -> subprocess.CompletedProcess:
|
||||
pretty = " ".join(shlex.quote(c) for c in cmd)
|
||||
if cwd:
|
||||
pretty = f"(cd {cwd}) {pretty}"
|
||||
print(f"+ {pretty}", flush=True)
|
||||
if dry_run:
|
||||
return subprocess.CompletedProcess(cmd, 0)
|
||||
return subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=check)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Discovery / classification
|
||||
# --------------------------------------------------------------------------- #
|
||||
def load_ignore(repo_root: Path) -> set:
|
||||
ignore = set(DEFAULT_IGNORE)
|
||||
ignore_file = repo_root / ".autopackageignore"
|
||||
if ignore_file.is_file():
|
||||
for line in ignore_file.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#"):
|
||||
ignore.add(line)
|
||||
return ignore
|
||||
|
||||
|
||||
def discover_packages(cfg: Config, only: List[str]) -> List[Path]:
|
||||
pkgs: List[Path] = []
|
||||
for entry in sorted(cfg.repo_root.iterdir()):
|
||||
if not entry.is_dir():
|
||||
continue
|
||||
name = entry.name
|
||||
if name.startswith(".") or name in cfg.ignore:
|
||||
continue
|
||||
if only and name not in only:
|
||||
continue
|
||||
pkgs.append(entry)
|
||||
return pkgs
|
||||
|
||||
|
||||
def is_custom(pkg_src: Path) -> bool:
|
||||
return (pkg_src / "PKGBUILD").is_file()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Build preparation
|
||||
# --------------------------------------------------------------------------- #
|
||||
def reset_build_dir(build_dir: Path, dry_run: bool) -> None:
|
||||
if dry_run:
|
||||
print(f"+ rm -rf {build_dir}")
|
||||
return
|
||||
if build_dir.exists():
|
||||
shutil.rmtree(build_dir)
|
||||
build_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def prepare_custom(pkg_src: Path, build_dir: Path, dry_run: bool) -> None:
|
||||
print(f" custom package -> copying sources into {build_dir}")
|
||||
if dry_run:
|
||||
print(f"+ cp -r {pkg_src} {build_dir}")
|
||||
return
|
||||
shutil.copytree(pkg_src, build_dir, ignore=shutil.ignore_patterns(".git"))
|
||||
|
||||
|
||||
def prepare_aur(pkg_src: Path, build_dir: Path, cfg: Config) -> None:
|
||||
name = pkg_src.name
|
||||
aur_url = f"{AUR_BASE}/{name}.git"
|
||||
print(f" AUR package -> cloning {aur_url}")
|
||||
run(["git", "clone", "--depth", "1", aur_url, str(build_dir)], dry_run=cfg.dry_run)
|
||||
apply_overrides(pkg_src, build_dir, cfg)
|
||||
|
||||
|
||||
def apply_overrides(pkg_src: Path, build_dir: Path, cfg: Config) -> None:
|
||||
patches = sorted(
|
||||
p for p in pkg_src.iterdir()
|
||||
if p.is_file() and p.suffix in (".patch", ".diff")
|
||||
)
|
||||
overrides = sorted(
|
||||
p for p in pkg_src.iterdir()
|
||||
if p.is_file()
|
||||
and p.suffix not in (".patch", ".diff")
|
||||
and p.name != ".gitkeep"
|
||||
)
|
||||
|
||||
for ov in overrides:
|
||||
dest = build_dir / ov.name
|
||||
print(f" override: {ov.name}")
|
||||
if cfg.dry_run:
|
||||
print(f"+ cp {ov} {dest}")
|
||||
continue
|
||||
shutil.copy2(ov, dest)
|
||||
|
||||
for patch in patches:
|
||||
print(f" patch: {patch.name}")
|
||||
apply_patch(patch, build_dir, cfg.dry_run)
|
||||
|
||||
|
||||
def apply_patch(patch: Path, build_dir: Path, dry_run: bool) -> None:
|
||||
# Use `git apply` first if theres a patch that i have in there
|
||||
git_apply = run(
|
||||
["git", "apply", "--whitespace=nowarn", str(patch)],
|
||||
cwd=build_dir, dry_run=dry_run, check=False,
|
||||
)
|
||||
if git_apply.returncode == 0:
|
||||
return
|
||||
if dry_run:
|
||||
return
|
||||
print(f" (git apply failed, trying patch -p1) {patch.name}")
|
||||
with patch.open("rb") as fh:
|
||||
result = subprocess.run(
|
||||
["patch", "-Np1", "--forward"],
|
||||
cwd=str(build_dir), stdin=fh,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"failed to apply patch {patch.name}")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Build
|
||||
# --------------------------------------------------------------------------- #
|
||||
def find_artifacts(build_dir: Path, want_sig: bool) -> List[Path]:
|
||||
found: List[Path] = []
|
||||
for pat in PKG_GLOBS:
|
||||
found.extend(build_dir.glob(pat))
|
||||
pkgs = [p for p in found if p.is_file() and not p.name.endswith(".sig")]
|
||||
pkgs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
artifacts: List[Path] = []
|
||||
for p in pkgs:
|
||||
artifacts.append(p)
|
||||
if want_sig:
|
||||
sig = p.with_name(p.name + ".sig")
|
||||
if sig.is_file():
|
||||
artifacts.append(sig)
|
||||
return artifacts
|
||||
|
||||
|
||||
def build_one(pkg_src: Path, cfg: Config) -> List[Path]:
|
||||
name = pkg_src.name
|
||||
build_dir = cfg.workdir / name
|
||||
print(f"\n=== {name} ===")
|
||||
|
||||
reset_build_dir(build_dir, cfg.dry_run)
|
||||
if is_custom(pkg_src):
|
||||
prepare_custom(pkg_src, build_dir, cfg.dry_run)
|
||||
else:
|
||||
prepare_aur(pkg_src, build_dir, cfg)
|
||||
|
||||
run(cfg.makepkg_cmd, cwd=build_dir, dry_run=cfg.dry_run)
|
||||
|
||||
if cfg.dry_run:
|
||||
return []
|
||||
|
||||
artifacts = find_artifacts(build_dir, want_sig=cfg.sign)
|
||||
pkg_files = [a for a in artifacts if not a.name.endswith(".sig")]
|
||||
if not pkg_files:
|
||||
raise RuntimeError(f"{name}: build finished but no *.pkg.tar.* produced")
|
||||
print(f" built: {', '.join(a.name for a in pkg_files)}")
|
||||
return artifacts
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Publish to Gitea Arch registry
|
||||
# --------------------------------------------------------------------------- #
|
||||
def parse_pkg_filename(filename: str) -> Optional[tuple]:
|
||||
"""Return (name, version, arch) from <name>-<ver>-<rel>-<arch>.pkg.tar.zst."""
|
||||
base = filename
|
||||
for suffix in (".pkg.tar.zst", ".pkg.tar.xz", ".pkg.tar.gz", ".pkg.tar.bz2"):
|
||||
if base.endswith(suffix):
|
||||
base = base[: -len(suffix)]
|
||||
break
|
||||
else:
|
||||
return None
|
||||
parts = base.rsplit("-", 3)
|
||||
if len(parts) != 4:
|
||||
return None
|
||||
name, ver, rel, arch = parts
|
||||
return name, f"{ver}-{rel}", arch
|
||||
|
||||
|
||||
def registry_base(cfg: Config) -> str:
|
||||
return f"{cfg.registry_url.rstrip('/')}/api/packages/{cfg.owner}/arch/{cfg.arch_repo}"
|
||||
|
||||
|
||||
def auth_header(cfg: Config) -> str:
|
||||
raw = f"{cfg.auth_user}:{cfg.token}".encode("utf-8")
|
||||
return "Basic " + base64.b64encode(raw).decode("ascii")
|
||||
|
||||
|
||||
def http_request(url: str, method: str, cfg: Config,
|
||||
data: Optional[bytes] = None) -> tuple:
|
||||
req = urllib.request.Request(url, data=data, method=method)
|
||||
req.add_header("Authorization", auth_header(cfg))
|
||||
if data is not None:
|
||||
req.add_header("Content-Type", "application/octet-stream")
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
return resp.status, resp.read().decode("utf-8", "replace")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode("utf-8", "replace")
|
||||
|
||||
|
||||
def delete_version(filename: str, cfg: Config) -> None:
|
||||
meta = parse_pkg_filename(filename)
|
||||
if not meta:
|
||||
return
|
||||
name, version, arch = meta
|
||||
url = f"{registry_base(cfg)}/{name}/{version}/{arch}"
|
||||
status, body = http_request(url, "DELETE", cfg)
|
||||
print(f" delete {name} {version} {arch} -> {status}")
|
||||
|
||||
|
||||
def publish_file(path: Path, cfg: Config) -> None:
|
||||
url = registry_base(cfg)
|
||||
if cfg.dry_run:
|
||||
print(f"+ PUT {url} ({path.name})")
|
||||
return
|
||||
data = path.read_bytes()
|
||||
status, body = http_request(url, "PUT", cfg, data=data)
|
||||
if status in (200, 201):
|
||||
print(f" uploaded {path.name} -> {status}")
|
||||
return
|
||||
if status == 409 and cfg.replace:
|
||||
print(f" {path.name} exists; replacing")
|
||||
delete_version(path.name, cfg)
|
||||
status, body = http_request(url, "PUT", cfg, data=data)
|
||||
if status in (200, 201):
|
||||
print(f" uploaded {path.name} -> {status}")
|
||||
return
|
||||
if status == 409:
|
||||
print(f" {path.name} already published (409); skipping "
|
||||
f"(use --replace to overwrite)")
|
||||
return
|
||||
raise RuntimeError(
|
||||
f"upload of {path.name} failed: HTTP {status}: {body.strip()[:500]}"
|
||||
)
|
||||
|
||||
|
||||
def publish(artifacts: List[Path], cfg: Config) -> None:
|
||||
for art in artifacts:
|
||||
publish_file(art, cfg)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# CLI
|
||||
# --------------------------------------------------------------------------- #
|
||||
def env(name: str, default: str = "") -> str:
|
||||
return os.environ.get(name, default)
|
||||
|
||||
|
||||
def build_config(args: argparse.Namespace) -> Config:
|
||||
repo_root = args.repo_root.resolve()
|
||||
|
||||
makepkg_cmd = ["makepkg", "-s", "--noconfirm", "--needed"]
|
||||
if args.cleanbuild:
|
||||
makepkg_cmd.append("--cleanbuild")
|
||||
if args.sign:
|
||||
makepkg_cmd.append("--sign")
|
||||
|
||||
cfg = Config(
|
||||
repo_root=repo_root,
|
||||
workdir=args.workdir.resolve(),
|
||||
makepkg_cmd=makepkg_cmd,
|
||||
sign=args.sign,
|
||||
keep_going=args.keep_going,
|
||||
dry_run=args.dry_run,
|
||||
publish=not args.no_publish,
|
||||
replace=args.replace,
|
||||
registry_url=args.registry_url,
|
||||
owner=args.owner,
|
||||
arch_repo=args.arch_repo,
|
||||
auth_user=args.auth_user or args.owner,
|
||||
token=args.token,
|
||||
ignore=load_ignore(repo_root),
|
||||
)
|
||||
return cfg
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Nightly directory-driven Arch package builder for Gitea CI",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
ap.add_argument("--repo-root", type=Path, default=Path.cwd(),
|
||||
help="Repo root to scan for package directories")
|
||||
ap.add_argument("--workdir", type=Path, default=Path.cwd() / ".build",
|
||||
help="Scratch directory for clones/builds")
|
||||
ap.add_argument("--only", nargs="*", default=[],
|
||||
help="Only build these package directory names")
|
||||
ap.add_argument("--cleanbuild", action="store_true",
|
||||
help="Pass --cleanbuild to makepkg")
|
||||
ap.add_argument("--sign", action="store_true",
|
||||
help="Sign packages (makepkg --sign) and upload .sig files")
|
||||
ap.add_argument("--keep-going", action="store_true",
|
||||
help="Continue with remaining packages if one fails")
|
||||
ap.add_argument("--dry-run", action="store_true",
|
||||
help="Print actions without executing them")
|
||||
ap.add_argument("--no-publish", action="store_true",
|
||||
help="Build only; do not upload to the registry")
|
||||
ap.add_argument("--replace", action="store_true",
|
||||
default=env("AUTOPKG_REPLACE", "") not in ("", "0", "false"),
|
||||
help="Delete + re-upload when a file already exists (409)")
|
||||
|
||||
# Gitea Arch registry (CLI overrides env; env defaults suit Gitea Actions)
|
||||
ap.add_argument("--registry-url", default=env("AUTOPKG_REGISTRY_URL",
|
||||
env("GITHUB_SERVER_URL")),
|
||||
help="Gitea base URL, e.g. https://git.example.com")
|
||||
ap.add_argument("--owner", default=env("AUTOPKG_OWNER",
|
||||
env("GITHUB_REPOSITORY_OWNER")),
|
||||
help="Package registry owner (user or org)")
|
||||
ap.add_argument("--arch-repo", default=env("AUTOPKG_REPO", "arch"),
|
||||
help="Registry repository/group name (the [repo] in pacman.conf)")
|
||||
ap.add_argument("--auth-user", default=env("AUTOPKG_USER",
|
||||
env("GITHUB_ACTOR")),
|
||||
help="Username for HTTP basic auth (defaults to owner)")
|
||||
ap.add_argument("--token", default=env("AUTOPKG_TOKEN",
|
||||
env("PACKAGE_TOKEN", env("GITHUB_TOKEN"))),
|
||||
help="Gitea token with write:package scope")
|
||||
|
||||
args = ap.parse_args(argv)
|
||||
cfg = build_config(args)
|
||||
|
||||
pkgs = discover_packages(cfg, args.only)
|
||||
if not pkgs:
|
||||
print("No package directories found.", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print(f"Discovered {len(pkgs)} package(s): "
|
||||
f"{', '.join(p.name for p in pkgs)}")
|
||||
|
||||
if cfg.publish and not cfg.dry_run:
|
||||
missing = [n for n, v in (("registry-url", cfg.registry_url),
|
||||
("owner", cfg.owner),
|
||||
("token", cfg.token)) if not v]
|
||||
if missing:
|
||||
print(f"Cannot publish: missing {', '.join(missing)}. "
|
||||
f"Set the matching env/secret or pass --no-publish.",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
failures: List[str] = []
|
||||
for pkg_src in pkgs:
|
||||
try:
|
||||
artifacts = build_one(pkg_src, cfg)
|
||||
if cfg.publish and not cfg.dry_run:
|
||||
publish(artifacts, cfg)
|
||||
elif cfg.publish and cfg.dry_run:
|
||||
print(f" (dry-run) would publish artifacts for {pkg_src.name}")
|
||||
except (subprocess.CalledProcessError, RuntimeError, OSError) as e:
|
||||
print(f"✗ {pkg_src.name}: {e}", file=sys.stderr)
|
||||
failures.append(pkg_src.name)
|
||||
if not cfg.keep_going:
|
||||
break
|
||||
|
||||
if failures:
|
||||
print(f"\nFailed: {', '.join(failures)}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("\nAll done.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
0
bacula/.gitkeep
Normal file
0
bacula/.gitkeep
Normal file
0
fchat-horizon-appimage/.gitkeep
Normal file
0
fchat-horizon-appimage/.gitkeep
Normal file
0
ggml-git-latest-commit/.gitkeep
Normal file
0
ggml-git-latest-commit/.gitkeep
Normal file
0
openrgb-git/.gitkeep
Normal file
0
openrgb-git/.gitkeep
Normal file
0
paru/.gitkeep
Normal file
0
paru/.gitkeep
Normal file
19
plezy-bin/0001-sentry-native-inproc-backend.patch
Normal file
19
plezy-bin/0001-sentry-native-inproc-backend.patch
Normal file
@@ -0,0 +1,19 @@
|
||||
diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt
|
||||
index 29b0af464..721fa7864 100644
|
||||
--- a/linux/CMakeLists.txt
|
||||
+++ b/linux/CMakeLists.txt
|
||||
@@ -81,6 +81,14 @@ set_target_properties(${BINARY_NAME}
|
||||
)
|
||||
|
||||
|
||||
+# Build sentry-native with the in-process backend instead of crashpad. The
|
||||
+# crashpad backend forks a handler and calls setsid(), which fails (EPERM) when
|
||||
+# plezy is launched as a systemd app scope / session leader, tripping a fatal
|
||||
+# CHECK in crashpad that SIGTRAPs on startup. inproc installs in-process signal
|
||||
+# handlers and avoids the fork/setsid path. Overrides the plugin's forced
|
||||
+# SENTRY_BACKEND=crashpad via the env-var escape hatch in sentry-native.cmake.
|
||||
+set(ENV{SENTRY_NATIVE_BACKEND} "inproc")
|
||||
+
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
0
qownnotes/.gitkeep
Normal file
0
qownnotes/.gitkeep
Normal file
0
ventoy-bin/.gitkeep
Normal file
0
ventoy-bin/.gitkeep
Normal file
0
visual-studio-code-bin/.gitkeep
Normal file
0
visual-studio-code-bin/.gitkeep
Normal file
Reference in New Issue
Block a user