Хочете мати власний приватний PPA для Ubuntu з автоматичною збіркою і підписом .deb? Зробимо це разом: ми зберемо пакети у GitHub Actions, підпишемо їх GPG-ключем і опублікуємо у приватній гілці репозиторію, з якої клієнти зможуть безпечно встановлювати оновлення. Це зручно для команд і ентузіастів, які люблять керувати релізами через термінал Linux і цінують автоматизацію задач. 🔒

Що таке приватний PPA і як це працює

Традиційно PPA означає «Personal Package Archive» на Launchpad. Але приватні PPA там — платна опція. Ми підемо іншим шляхом: створимо приватний APT-репозиторій у вашому GitHub-репозиторії. GitHub Actions буде:

  • збирати .deb пакети з вихідних;
  • підписувати метадані репозиторію вашим GPG-ключем;
  • публікувати структуру dists/ і pool/ у приватній гілці (наприклад, apt);
  • клієнти додадуть джерело через sources.list і отримуватимуть оновлення як зі звичайного PPA.

Переваги: контроль доступу через GitHub (PAT-токени), стандартна підтримка apt, прозора інтеграція в CI/CD. Це повністю сумісно з apt команди та підписом InRelease.

Підготовка: GPG-ключ і секрети GitHub

Спочатку створіть ключ для підпису релізів. У термінал Linux виконайте:

# 1) Створення GPG-ключа для підпису (1 рік дії)
gpg --quick-generate-key "Your Name <you@example.com>" rsa4096 sign 1y

# 2) Експортуємо публічний і приватний ключі (armor)
gpg --export -a "Your Name <you@example.com>" > public.asc
gpg --export-secret-keys -a "Your Name <you@example.com>" > private.asc

# 3) Перевіряємо ключ-id (fingerprint)
gpg --list-secret-keys --keyid-format=long

Збережіть private.asc як секрет GPG_PRIVATE_KEY у Settings → Secrets → Actions вашого репозиторію. Додайте також GPG_PASSPHRASE (якщо є) і PUBLISH_TOKEN — це ваш персональний PAT з правами repo для пушу у приватну гілку. Ми публікуватимемо архів у гілці apt вашого приватного репозиторію.

Структура APT-репозиторію (коротко)

APT очікує стандартну структуру:

  • pool/main/<pkg>/*.deb — самі пакети;
  • dists/stable/main/binary-amd64/Packages (+ .gz, .xz) — індекси;
  • dists/stable/Release, InRelease, Release.gpg — метадані з підписом GPG.

Ми згенеруємо все це у GitHub Actions за допомогою dpkg-scanpackages і apt-ftparchive.

How-to: GitHub Actions для збірки, підпису та публікації

Створіть workflow. Найпростіше — додати файл з локального репо через bash скрипти:

# Створюємо файл GitHub Actions (у вашому локальному репо)
mkdir -p .github/workflows
cat > .github/workflows/ppa.yml <<'YAML'
name: Build and publish APT repo
on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install build deps
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            devscripts debhelper dpkg-dev fakeroot gnupg apt-utils ca-certificates

      - name: Build .deb
        run: |
          # Припускаємо, що у репозиторії є папка debian/ для dpkg-buildpackage
          dpkg-buildpackage -us -uc -b
          mkdir -p build
          mv ../*.deb build/

      - name: Import GPG
        env:
          GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
          GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
        run: |
          echo "$GPG_PRIVATE_KEY" | gpg --batch --yes --import

      - name: Create APT repo structure
        env:
          DIST: stable
          COMPONENT: main
          ARCH: amd64
          GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
        run: |
          set -eux
          mkdir -p repo/dists/$DIST/$COMPONENT/binary-$ARCH repo/pool/$COMPONENT
          cp build/*.deb repo/pool/$COMPONENT/
          pushd repo
          dpkg-scanpackages -m pool/$COMPONENT /dev/null > dists/$DIST/$COMPONENT/binary-$ARCH/Packages
          gzip -fk dists/$DIST/$COMPONENT/binary-$ARCH/Packages
          xz -fke dists/$DIST/$COMPONENT/binary-$ARCH/Packages
          cat > apt.conf <<EOF
          APT::FTPArchive::Release {
            Origin "MyPrivate";
            Label "MyPrivate";
            Suite "stable";
            Codename "stable";
            Architectures "amd64";
            Components "main";
            Description "Private APT repo";
          };
          EOF
          apt-ftparchive -c=apt.conf release dists/$DIST > dists/$DIST/Release
          gpg --batch --yes --passphrase "$GPG_PASSPHRASE" --pinentry-mode loopback -abs -o dists/$DIST/Release.gpg dists/$DIST/Release
          gpg --batch --yes --passphrase "$GPG_PASSPHRASE" --pinentry-mode loopback --clearsign -o dists/$DIST/InRelease dists/$DIST/Release
          popd

      - name: Publish to apt branch
        env:
          PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
          APT_BRANCH: apt
        run: |
          set -eux
          git config user.name "github-actions"
          git config user.email "actions@users.noreply.github.com"
          git fetch origin $APT_BRANCH || true
          if git ls-remote --exit-code --heads origin $APT_BRANCH; then
            git checkout $APT_BRANCH
          else
            git checkout --orphan $APT_BRANCH
            git rm -rf . || true
          fi
          rsync -a --delete repo/ .
          mkdir -p keys
          gpg --export -a > keys/public.asc
          git add .
          git commit -m "Publish APT repo for $GITHUB_REF_NAME" || true
          git push https://x-access-token:$PUBLISH_TOKEN@github.com/${GITHUB_REPOSITORY}.git $APT_BRANCH
YAML

Після пушу з тегом v* (наприклад, v1.0.0) workflow збере і опублікує репозиторій у гілці apt. Клієнти зможуть читати його за посиланням https://raw.githubusercontent.com/<OWNER>/<REPO>/apt/ з авторизацією.

Підключення клієнтів Ubuntu до приватного репозиторію

1) Додайте автентифікацію для raw.githubusercontent.com

# Створіть файл з правами 600 (APT читає auth.conf)
echo "machine raw.githubusercontent.com login GITHUB_USER password <PAT_TOKEN>" | \
  sudo tee /etc/apt/auth.conf.d/github.conf >/dev/null
sudo chmod 600 /etc/apt/auth.conf.d/github.conf

2) Встановіть публічний ключ репозиторію

sudo install -m 0755 -d /usr/share/keyrings
curl -fsSL https://raw.githubusercontent.com/<OWNER>/<REPO>/apt/keys/public.asc \
  | sudo tee /usr/share/keyrings/mypriv-archive-keyring.asc >/dev/null

3) Додайте джерело у sources.list.d

echo "deb [signed-by=/usr/share/keyrings/mypriv-archive-keyring.asc] \
https://raw.githubusercontent.com/<OWNER>/<REPO>/apt/ stable main" | \
  sudo tee /etc/apt/sources.list.d/mypriv.list >/dev/null

sudo apt update
# Тепер можна встановлювати свій пакет (приклад)
sudo apt install mypackage

Готово! Ви підключили приватний репозиторій. Далі оновлення виконуються як завжди через apt команди.

Альтернативні способи

  • aptly + S3/CloudFront: надійно, масштабовано; потребує AWS і додаткової конфігурації.
  • Launchpad (публічний): простіший публічний PPA для open-source.
  • Packagecloud/Cloudsmith: готові керовані приватні APT-сховища (платні, зручні GUI).

GUI-спосіб (через інтерфейс GitHub)

  1. Створіть репозиторій (private) у GitHub.
  2. Settings → Secrets and variables → Actions: додайте GPG_PRIVATE_KEY, GPG_PASSPHRASE (за потреби) і PUBLISH_TOKEN.
  3. Створіть файл .github/workflows/ppa.yml через кнопку «Add file» → «Create new file», вставте вміст із розділу How-to і збережіть.
  4. Створіть релізний тег v1.0.0 у розділі Releases → Draft a new release → Publish.
  5. Перевірте гілку apt: там мають з’явитися dists/, pool/ і keys/public.asc.

FAQ

Чому apt каже «NO_PUBKEY» або «The following signatures couldn't be verified»?

Переконайтеся, що signed-by вказує на той самий ключ, яким підписано InRelease. Оновіть ключ: заново експортуйте public.asc і замініть файл у /usr/share/keyrings/.

Отримую 401/403 з raw.githubusercontent.com

APT не бачить токен. Перевірте /etc/apt/auth.conf.d/github.conf (права 600), правильний користувач/токен, і що репозиторій приватний та існує гілка apt.

InRelease/Release.gpg не збігаються

Ймовірно, ви не оновили підпис після змін у dists/. У CI переконайтеся, що крок підпису виконується після генерації Release і що кеш не підмінює файли.

Як додати інші архітектури або дистрибутиви (suite)?

Додайте ще каталоги binary-arm64, змініть Architectures в apt.conf, продублюйте генерацію індексів для кожної архітектури та додайте нові stable/testing гілки за потреби.

Чи безпечно зберігати приватний ключ у GitHub Actions?

Тримайте його у Secrets, використовуйте мінімально необхідні права для PAT, регулярно перевипускайте ключ (ротація), а для додаткової безпеки — апаратний ключ і короткі TTL.

Чи можна замість raw.githubusercontent.com використати GitHub Pages?

Так, але для приватності потрібен платний план/організація. Для більшості команд приватна гілка + auth через raw — простіше.

Rate limit GitHub і повільне оновлення?

Використовуйте персональні токени на клієнтах, кешуйте репозиторій-проксі (apt-cacher-ng) усередині мережі, зменшуйте частоту оновлень.

Порада від Kernelka

Додавайте перевірки підпису і smoke-тести встановлення пакета прямо в CI, щоб не штовхати «биті» індекси. Маленький тестовий контейнер з apt update && apt install з вашого репо — і ви спокійні. 🚀

Підсумок

  • Згенерували GPG-ключ і додали його у Secrets GitHub.
  • Налаштували workflow для збірки .deb, генерації індексів і підпису InRelease.
  • Опублікували репозиторій у приватній гілці apt.
  • Підключили клієнтів через auth.conf, keyring і sources.list.d.
  • Керуємо оновленнями стандартними apt команди; все автоматизовано через GitHub Actions і ваші bash скрипти.