From 9c479e5615f082d70d59401939841ef9d767ca32 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 9 Mar 2026 22:43:40 +0000 Subject: [PATCH] feat: add Debian package building to CI pipeline and local build Support DEB packaging alongside RPM for Debian trixie (13/stable), forky (14/testing), Ubuntu noble (24.04 LTS), and plucky (25.04). Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++++++ package.json | 2 ++ scripts/build-deb.sh | 53 +++++++++++++++++++++++++++++++ scripts/build-rpm.sh | 9 ++++++ scripts/publish-deb.sh | 70 +++++++++++++++++++++++++++++++++++++++++ scripts/release.sh | 8 ++++- 6 files changed, 203 insertions(+), 1 deletion(-) create mode 100755 scripts/build-deb.sh create mode 100755 scripts/publish-deb.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f060e40..77d858a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -300,6 +300,9 @@ jobs: - name: Package RPM run: nfpm pkg --packager rpm --target dist/ + - name: Package DEB + run: nfpm pkg --packager deb --target dist/ + - name: Upload RPM artifact uses: actions/upload-artifact@v3 with: @@ -307,6 +310,13 @@ jobs: path: dist/mcpctl-*.rpm retention-days: 7 + - name: Upload DEB artifact + uses: actions/upload-artifact@v3 + with: + name: deb-package + path: dist/mcpctl*.deb + retention-days: 7 + # ── Release pipeline (main branch push only) ────────────── # NOTE: Docker image builds + deploy happen via `bash fulldeploy.sh` # (not CI) because the runner containers lack the privileged access @@ -363,3 +373,55 @@ jobs: # Link package to repo source scripts/link-package.sh link_package "rpm" "mcpctl" + + publish-deb: + runs-on: ubuntu-latest + needs: [build] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + steps: + - uses: actions/checkout@v4 + + - name: Download DEB artifact + uses: actions/download-artifact@v3 + with: + name: deb-package + path: dist/ + + - name: Publish DEB to Gitea + env: + GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }} + GITEA_URL: http://${{ env.GITEA_REGISTRY }} + GITEA_OWNER: ${{ env.GITEA_OWNER }} + GITEA_REPO: mcpctl + run: | + DEB_FILE=$(ls dist/mcpctl*.deb | head -1) + DEB_VERSION=$(dpkg-deb --field "$DEB_FILE" Version) + echo "Publishing $DEB_FILE (version $DEB_VERSION)..." + + # Publish to each supported distribution + # Debian: trixie (13/stable), forky (14/testing) + # Ubuntu: noble (24.04 LTS), plucky (25.04) + DISTRIBUTIONS="trixie forky noble plucky" + + for DIST in $DISTRIBUTIONS; do + echo " -> $DIST..." + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X PUT \ + -H "Authorization: token ${GITEA_TOKEN}" \ + --upload-file "$DEB_FILE" \ + "${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DIST}/main/upload") + + if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then + echo " Published to $DIST" + elif [ "$HTTP_CODE" = "409" ]; then + echo " Already exists in $DIST (skipping)" + else + echo " WARNING: Upload to $DIST returned HTTP $HTTP_CODE" + fi + done + + echo "Published successfully!" + + # Link package to repo + source scripts/link-package.sh + link_package "debian" "mcpctl" diff --git a/package.json b/package.json index a474590..47e84ab 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "completions:check": "tsx scripts/generate-completions.ts --check", "rpm:build": "bash scripts/build-rpm.sh", "rpm:publish": "bash scripts/publish-rpm.sh", + "deb:build": "bash scripts/build-deb.sh", + "deb:publish": "bash scripts/publish-deb.sh", "release": "bash scripts/release.sh", "mcpd:build": "bash scripts/build-mcpd.sh", "mcpd:deploy": "bash deploy.sh", diff --git a/scripts/build-deb.sh b/scripts/build-deb.sh new file mode 100755 index 0000000..d045312 --- /dev/null +++ b/scripts/build-deb.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$PROJECT_ROOT" + +# Load .env if present +if [ -f .env ]; then + set -a; source .env; set +a +fi + +# Ensure tools are on PATH +export PATH="$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH" + +# Check if binaries already exist (build-rpm.sh may have been run first) +if [ ! -f dist/mcpctl ] || [ ! -f dist/mcpctl-local ]; then + echo "==> Binaries not found, building from scratch..." + echo "" + + echo "==> Running unit tests..." + pnpm test:run + echo "" + + echo "==> Building TypeScript..." + pnpm build + + echo "==> Generating shell completions..." + pnpm completions:generate + + echo "==> Bundling standalone binaries..." + mkdir -p dist + + # Ink optionally imports react-devtools-core which isn't installed. + # Provide a no-op stub so bun can bundle it (it's only invoked when DEV=true). + if [ ! -e node_modules/react-devtools-core ]; then + ln -s ../src/cli/stubs/react-devtools-core node_modules/react-devtools-core + fi + + bun build src/cli/src/index.ts --compile --outfile dist/mcpctl + bun build src/mcplocal/src/main.ts --compile --outfile dist/mcpctl-local +else + echo "==> Using existing binaries in dist/" +fi + +echo "==> Packaging DEB..." +rm -f dist/mcpctl-*.deb dist/mcpctl_*.deb +nfpm pkg --packager deb --target dist/ + +DEB_FILE=$(ls dist/mcpctl*.deb 2>/dev/null | head -1) +echo "==> Built: $DEB_FILE" +echo " Size: $(du -h "$DEB_FILE" | cut -f1)" +dpkg-deb --info "$DEB_FILE" 2>/dev/null || true diff --git a/scripts/build-rpm.sh b/scripts/build-rpm.sh index 84e5395..0bfc3c0 100755 --- a/scripts/build-rpm.sh +++ b/scripts/build-rpm.sh @@ -43,3 +43,12 @@ RPM_FILE=$(ls dist/mcpctl-*.rpm 2>/dev/null | head -1) echo "==> Built: $RPM_FILE" echo " Size: $(du -h "$RPM_FILE" | cut -f1)" rpm -qpi "$RPM_FILE" + +echo "" +echo "==> Packaging DEB..." +rm -f dist/mcpctl*.deb +nfpm pkg --packager deb --target dist/ + +DEB_FILE=$(ls dist/mcpctl*.deb 2>/dev/null | head -1) +echo "==> Built: $DEB_FILE" +echo " Size: $(du -h "$DEB_FILE" | cut -f1)" diff --git a/scripts/publish-deb.sh b/scripts/publish-deb.sh new file mode 100755 index 0000000..75aa30b --- /dev/null +++ b/scripts/publish-deb.sh @@ -0,0 +1,70 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$PROJECT_ROOT" + +# Load .env if present +if [ -f .env ]; then + set -a; source .env; set +a +fi + +GITEA_URL="${GITEA_URL:-http://10.0.0.194:3012}" +GITEA_OWNER="${GITEA_OWNER:-michal}" +GITEA_REPO="${GITEA_REPO:-mcpctl}" + +if [ -z "$GITEA_TOKEN" ]; then + echo "Error: GITEA_TOKEN not set. Add it to .env or export it." + exit 1 +fi + +DEB_FILE=$(ls dist/mcpctl*.deb 2>/dev/null | head -1) +if [ -z "$DEB_FILE" ]; then + echo "Error: No DEB found in dist/. Run scripts/build-deb.sh first." + exit 1 +fi + +# Extract version from the deb filename (e.g. mcpctl_0.0.1_amd64.deb) +DEB_VERSION=$(dpkg-deb --field "$DEB_FILE" Version 2>/dev/null || echo "unknown") + +echo "==> Publishing $DEB_FILE (version $DEB_VERSION) to ${GITEA_URL}..." + +# Gitea Debian registry: PUT /api/packages/{owner}/debian/pool/{distribution}/{component}/upload +# We publish to each supported distribution. +# Debian: trixie (13/stable), forky (14/testing) +# Ubuntu: noble (24.04 LTS), plucky (25.04) +DISTRIBUTIONS="trixie forky noble plucky" + +for DIST in $DISTRIBUTIONS; do + echo " -> $DIST..." + HTTP_CODE=$(curl -s -o /tmp/deb-upload-$DIST.out -w "%{http_code}" \ + -X PUT \ + -H "Authorization: token ${GITEA_TOKEN}" \ + --upload-file "$DEB_FILE" \ + "${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DIST}/main/upload") + + if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then + echo " Published to $DIST" + elif [ "$HTTP_CODE" = "409" ]; then + echo " Already exists in $DIST (skipping)" + else + echo " WARNING: Upload to $DIST returned HTTP $HTTP_CODE" + cat /tmp/deb-upload-$DIST.out 2>/dev/null || true + echo "" + fi + rm -f /tmp/deb-upload-$DIST.out +done + +echo "" +echo "==> Published successfully!" + +# Ensure package is linked to the repository +source "$SCRIPT_DIR/link-package.sh" +link_package "debian" "mcpctl" + +echo "" +echo "Install with:" +echo " echo \"deb ${GITEA_URL}/api/packages/${GITEA_OWNER}/debian trixie main\" | sudo tee /etc/apt/sources.list.d/mcpctl.list" +echo " curl -fsSL ${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/repository.key | sudo gpg --dearmor -o /etc/apt/keyrings/mcpctl.gpg" +echo " sudo apt update && sudo apt install mcpctl" diff --git a/scripts/release.sh b/scripts/release.sh index dfac30c..33effda 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -20,6 +20,7 @@ echo "" # Publish bash scripts/publish-rpm.sh +bash scripts/publish-deb.sh echo "" @@ -52,6 +53,11 @@ echo "" GITEA_URL="${GITEA_URL:-http://10.0.0.194:3012}" GITEA_OWNER="${GITEA_OWNER:-michal}" echo "=== Done! ===" -echo "Others can install with:" +echo "RPM install:" echo " sudo dnf config-manager --add-repo ${GITEA_URL}/api/packages/${GITEA_OWNER}/rpm.repo" echo " sudo dnf install mcpctl" +echo "" +echo "DEB install (Debian/Ubuntu):" +echo " echo \"deb ${GITEA_URL}/api/packages/${GITEA_OWNER}/debian trixie main\" | sudo tee /etc/apt/sources.list.d/mcpctl.list" +echo " curl -fsSL ${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/repository.key | sudo gpg --dearmor -o /etc/apt/keyrings/mcpctl.gpg" +echo " sudo apt update && sudo apt install mcpctl"