diff --git a/.gitea/workflows/app_builder.yaml b/.gitea/workflows/app_builder.yaml index 265f541..2108a51 100644 --- a/.gitea/workflows/app_builder.yaml +++ b/.gitea/workflows/app_builder.yaml @@ -155,3 +155,71 @@ jobs: run: | uv sync uv run main.py + + publish-arch: + name: Build and publish Arch package + runs-on: archlinux + environment: production + env: + MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }} + S3_BUCKET: ${{ vars.S3_BUCKET }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + + defaults: + run: + working-directory: GitLite + + steps: + - uses: actions/checkout@v5 + with: + path: GitLite + + - name: Set release version + env: + RELEASE_VERSION: ${{ github.ref_name }} + run: | + npm version "$RELEASE_VERSION" --no-git-tag-version --allow-same-version + node -e "const fs=require('fs'); const version=require('./package.json').version; const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=version; fs.writeFileSync(path,JSON.stringify(config,null,2)+'\n');" + PACKAGE_VERSION="$(node -p "require('./package.json').version")" + sed -i "s/^pkgver=.*/pkgver=$PACKAGE_VERSION/; s/^pkgrel=.*/pkgrel=1/" PKGBUILD + + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b + + - name: Restore pacman repository database + working-directory: GitLite/cicd_tool + run: | + uv sync + uv run arch_repo.py download-db --output ../arch-repo/gitty.db.tar.gz + + - name: Build pkg.tar.zst with PKGBUILD + run: | + mkdir -p arch-output + docker run --rm \ + -v "$PWD:/source:ro" \ + -v "$PWD/arch-output:/output" \ + archlinux:base-devel bash -lc ' + pacman -Syu --noconfirm git nodejs npm rust webkit2gtk-4.1 gtk3 hicolor-icon-theme libappindicator-gtk3 librsvg xdotool && + useradd --create-home builder && + cp -a /source /build && + chown -R builder:builder /build && + runuser -u builder -- bash -lc "cd /build && makepkg --cleanbuild --noconfirm" && + cp /build/*.pkg.tar.zst /output/ && + chmod 0644 /output/*.pkg.tar.zst + ' + + - name: Update pacman repository database + run: | + mkdir -p arch-repo + docker run --rm \ + -v "$PWD/arch-output:/packages:ro" \ + -v "$PWD/arch-repo:/repo" \ + archlinux:base-devel bash -lc ' + cd /repo && + repo-add gitty.db.tar.gz /packages/*.pkg.tar.zst + ' + + - name: Upload package and repository database to CDN + working-directory: GitLite/cicd_tool + run: uv run arch_repo.py publish --packages-dir ../arch-output --repo-dir ../arch-repo diff --git a/PKGBUILD b/PKGBUILD index 33121dc..40f2798 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -11,7 +11,7 @@ pkgdesc="A lightweight, modern Git client built with Tauri" arch=('x86_64') url="https://git.cbsk-tech.de/Christoph/GitLite" license=('MIT') -depends=('webkit2gtk-4.1' 'gtk3' 'git' 'hicolor-icon-theme') +depends=('webkit2gtk-4.1' 'gtk3' 'git' 'hicolor-icon-theme' 'libappindicator-gtk3' 'librsvg' 'xdotool') makedepends=('rust' 'nodejs' 'npm') options=('!lto') diff --git a/README.md b/README.md index 273b981..3f3e356 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,28 @@ MIT License --- +## Arch Linux + +Release builds are published as an `x86_64` pacman repository. Add this block to +`/etc/pacman.conf` once: + +```ini +[gitty] +SigLevel = Optional TrustAll +Server = https://cdn.cbsk-tech.de/gitty/arch/$arch +``` + +Then install Gitty and receive future releases through the normal system update: + +```bash +sudo pacman -Syu gitty +``` + +The repository is currently distributed over HTTPS but is not GPG-signed. A +repository signing key should be added before recommending it to third parties. + +--- + ## SigNoz telemetry After the user opts in, Gitty sends privacy-filtered product events and technical errors as OTLP/HTTP JSON logs. Every Git/Tauri command is also captured as a trace span with its command name, duration and success state, but without command arguments or results. CPU utilization and resident memory usage of the Gitty process are sampled every 30 seconds and exported as OpenTelemetry gauges. The default endpoints are `https://telemetry.cbsk-tech.de/v1/logs`, `https://telemetry.cbsk-tech.de/v1/traces`, and `https://telemetry.cbsk-tech.de/v1/metrics`. diff --git a/cicd_tool/arch_repo.py b/cicd_tool/arch_repo.py new file mode 100644 index 0000000..efb710a --- /dev/null +++ b/cicd_tool/arch_repo.py @@ -0,0 +1,86 @@ +import argparse +import os +from pathlib import Path + +from minio import Minio +from minio.error import S3Error + +from main import ( + ConfigurationError, + _create_minio_client, + _ensure_bucket, + _parse_publish_targets, +) + + +REPO_PREFIX = "arch/x86_64" +DATABASE_NAME = "gitty.db.tar.gz" + + +def _targets(): + bucket_name = (os.environ.get("S3_BUCKET") or "gitty").lower() + app_dir = (os.environ.get("APP_DIR") or "gitty").strip("/") + return _parse_publish_targets(bucket_name, app_dir) + + +def download_database(client: Minio, output: Path) -> None: + target = _targets()[0] + object_name = f"{REPO_PREFIX}/{DATABASE_NAME}" + output.parent.mkdir(parents=True, exist_ok=True) + + try: + client.fget_object(target.bucket_name, object_name, str(output)) + print(f"Downloaded s3://{target.bucket_name}/{object_name} -> {output}") + except S3Error as exc: + if exc.code in {"NoSuchKey", "NoSuchObject"}: + print("No existing Arch repository database found; creating a new one.") + return + raise + + +def publish_repository(client: Minio, packages_dir: Path, repo_dir: Path) -> None: + packages = sorted(packages_dir.glob("*.pkg.tar.zst")) + if not packages: + raise FileNotFoundError(f"No .pkg.tar.zst package found in {packages_dir}") + + repository_files = [ + path + for path in repo_dir.iterdir() + if path.is_file() and path.name.startswith("gitty.") + ] + if not any(path.name == DATABASE_NAME for path in repository_files): + raise FileNotFoundError(f"Missing repository database: {repo_dir / DATABASE_NAME}") + + for target in _targets(): + _ensure_bucket(client, target.bucket_name) + for path in [*packages, *repository_files]: + object_name = f"{REPO_PREFIX}/{path.name}" + client.fput_object(target.bucket_name, object_name, str(path)) + print(f"Uploaded {path.name} -> s3://{target.bucket_name}/{object_name}") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Manage the Gitty pacman repository on MinIO.") + subparsers = parser.add_subparsers(dest="command", required=True) + + download_parser = subparsers.add_parser("download-db") + download_parser.add_argument("--output", type=Path, required=True) + + publish_parser = subparsers.add_parser("publish") + publish_parser.add_argument("--packages-dir", type=Path, required=True) + publish_parser.add_argument("--repo-dir", type=Path, required=True) + + args = parser.parse_args() + client = _create_minio_client() + + if args.command == "download-db": + download_database(client, args.output) + else: + publish_repository(client, args.packages_dir, args.repo_dir) + + +if __name__ == "__main__": + try: + main() + except (ConfigurationError, FileNotFoundError, S3Error) as exc: + raise SystemExit(f"Arch repository operation failed: {exc}") from exc