Власний RPM-репозиторій — це зручно, коли ви підтримуєте внутрішні пакети або хочете ділитися збірками із командою. Сьогодні покажу, як налаштувати повний цикл: побудова RPM, підпис пакунків та метаданих, формування репозиторію і автоматична публікація через GitLab CI. Без страху і паніки — лише дружній підхід і трохи любові до терміналу Linux 😊

Що і навіщо ми будуємо

Мета: автоматично збирати RPM-пакети в CI, підписувати їх GPG-ключем, генерувати метадані через createrepo_c, підписувати repomd.xml і публікувати статично (GitLab Pages або свій сервер Linux). Це полегшує встановлення та оновлення пакетів командою dnf/yum, а також вписується у ваші процеси автоматизація задач і DevOps.

Передумови

  • GitLab репозиторій з доступним Shared Runner або власним Runner (Docker executor бажано; це про Docker на Linux).
  • GPG 2.x локально для створення ключів.
  • Пакети для базового хостингу: nginx або GitLab Pages.

How-to: повний пайплайн

1) Структура проєкту

Пропонована структура для однієї архітектури та дистрибутива (приклад EL9/x86_64):

repo/
└── el9/
    └── x86_64/
        ├── RPMS/           # сюди складаємо підписані RPM
        └── repodata/       # createrepo_c згенерує автоматично

2) Створення та експорт GPG-ключа

Створіть окремий ключ для підпису пакетів і метаданих. Можна з коротким строком дії.

# Інтерактивно (на локальній машині)
gpg --full-generate-key

# Подивитись ключі
gpg --list-secret-keys --keyid-format LONG

# Експорт приватного ключа (з паролем!)
KEYID=<ВАШ_KEYID_LONG>
gpg --armor --export-secret-keys "$KEYID" > private.key.asc

# Експорт публічного ключа для клієнтів
gpg --armor --export "$KEYID" > RPM-GPG-KEY-custom.pub

Файл private.key.asc збережіть у секретах GitLab (CI/CD Variables), наприклад як GPG_PRIVATE_KEY. Пароль від ключа — як GPG_PASSPHRASE (masked, protected).

3) Налаштування rpmsign і підпис пакунків

Щоб rpmsign працював у CI без TTY, вмикаємо loopback-pinentry. Також пропишемо RPM-макроси:

cat > ~/.rpmmacros <<'EOF'
%_signature gpg
%_gpg_digest_algo sha256
%_gpg_name <Your Name <you@example.com>>
EOF

mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
printf 'pinentry-mode loopback\n' >> ~/.gnupg/gpg.conf
printf 'allow-loopback-pinentry\n' >> ~/.gnupg/gpg-agent.conf

gpgconf --kill gpg-agent || true

Підпис пакунків:

# Імпорт ключа в CI
echo "$GPG_PRIVATE_KEY" | gpg --batch --import

echo "$GPG_PASSPHRASE" | rpmsign \
  --addsign repo/el9/x86_64/RPMS/*.rpm \
  --define "_gpg_name <Your Name <you@example.com>>" \
  --passphrase-fd 0

Якщо ваша версія rpmsign не підтримує passphrase-fd, використайте expect або підписуйте через gpg-agent з попередньою конфігурацією loopback.

4) Створення та підпис репозиторію

Генеруємо метадані й підписуємо repomd.xml:

dnf -y install createrepo_c rpm-sign gnupg2

createrepo_c --update --workers 4 repo/el9/x86_64

gpg --batch --yes --pinentry-mode loopback \
  --passphrase "$GPG_PASSPHRASE" \
  --detach-sign --armor repo/el9/x86_64/repodata/repomd.xml

5) GitLab CI: .gitlab-ci.yml

Мінімальний приклад, що будує RPM (умовно вже зібрані артефакти у dist/*.rpm), підписує їх, формує репозиторій та публікує через GitLab Pages:

image: registry.fedoraproject.org/fedora:40

stages:
  - build
  - repo
  - deploy

variables:
  GIT_SUBMODULE_STRATEGY: recursive

before_script:
  - dnf -y install rpm-build rpm-sign createrepo_c gnupg2 findutils

build:rpm:
  stage: build
  script:
    - mkdir -p dist
    - rpmbuild -ba SPECS/your.spec --define '_topdir /builddir' # приклад
    - cp /builddir/RPMS/x86_64/*.rpm dist/
  artifacts:
    paths:
      - dist/

repo:sign-and-create:
  stage: repo
  dependencies:
    - build:rpm
  script:
    - mkdir -p repo/el9/x86_64/RPMS
    - mv dist/*.rpm repo/el9/x86_64/RPMS/
    - mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
    - echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
    - echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf
    - echo "$GPG_PRIVATE_KEY" | gpg --batch --import
    - |
      cat > ~/.rpmmacros <<EOF
      %_signature gpg
      %_gpg_digest_algo sha256
      %_gpg_name <Your Name <you@example.com>>
      EOF
    - echo "$GPG_PASSPHRASE" | rpmsign --addsign repo/el9/x86_64/RPMS/*.rpm --passphrase-fd 0
    - createrepo_c --update --workers 4 repo/el9/x86_64
    - gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" --detach-sign --armor repo/el9/x86_64/repodata/repomd.xml
    - mkdir -p public && cp -r repo/* public/
    - cp RPM-GPG-KEY-custom.pub public/
  artifacts:
    paths:
      - public/

pages:
  stage: deploy
  dependencies:
    - repo:sign-and-create
  script:
    - echo Deploying to Pages
  artifacts:
    paths:
      - public
  only:
    - main

Після деплою ваш репозиторій буде доступний як https://<user|group>.gitlab.io/<project>/el9/x86_64/

6) Підключення репозиторію на клієнті

sudo rpm --import https://<user>.gitlab.io/<project>/RPM-GPG-KEY-custom.pub

cat | sudo tee /etc/yum.repos.d/custom.repo >/dev/null <<EOF
[custom-el9]
name=Custom EL9 Repo
baseurl=https://<user>.gitlab.io/<project>/el9/x86_64/
enabled=1
gpgcheck=1
gpgkey=https://<user>.gitlab.io/<project>/RPM-GPG-KEY-custom.pub
repo_gpgcheck=1
EOF

sudo dnf clean all && sudo dnf makecache

Тепер ви можете встановлювати свої пакети через dnf, а оновлення прилітатимуть автоматично. Красота!

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

  • Хостинг на власному nginx: синхронізуйте repo/ на ваш сервер Linux і віддавайте як статичні файли. Не забудьте SELinux контекст та правильні MIME-ти.
  • S3-сумісне сховище (MinIO, AWS S3): публікуйте в бакет, вмикайте статичний веб-хостинг.
  • GitLab Package Registry (Generic): зручно для контролю доступу, але клієнтський dnf потребує прямого HTTP-доступу до дерев репозиторію.
  • Pulp/Nexus/Artifactory: важка артилерія з ролями, кешами та GUI.
  • Інші CI: GitHub Actions, Jenkins — логіка аналогічна, просто перенесіть bash скрипти.

GUI-спосіб: Nexus Repository OSS

  1. Встановіть Nexus Repository OSS на Docker або як службу (це теж про Docker на Linux).
  2. Створіть Hosted репозиторій типу yum (RPM). Вкажіть ім'я, політики кешування.
  3. Завантажте підписані RPM у веб-інтерфейсі або через REST API.
  4. Імпортуйте ваш публічний GPG-ключ клієнтам і додайте baseurl на Nexus. Готово!

FAQ

GPG: signing failed: Inappropriate ioctl for device

Додайте pinentry-mode loopback (gpg.conf) і allow-loopback-pinentry (gpg-agent.conf), використовуйте --pinentry-mode loopback та передавайте пароль через змінну. Без TTY інакше не працює.

rpmsign не приймає пароль

Перевірте версію rpm-sign. Якщо немає passphrase-fd, використайте expect або підписуйте ключ без пароля виключно в CI-оточенні з обмеженим доступом.

Клієнт каже, що підпис невалідний

Переконайтеся, що імпортовано той самий публічний ключ, що відповідає KEYID, та ввімкнено gpgcheck=1 і repo_gpgcheck=1 (для repomd.xml). Також стежте, щоб час на сервері та клієнті був синхронізований.

createrepo_c не знайдений

Встановіть пакет createrepo_c (dnf -y install createrepo_c). На деяких базових образах він відсутній.

Метадані не оновлюються

Використовуйте createrepo_c --update, а на клієнті виконайте dnf clean all і dnf makecache. Перевірте кешування на проксі/CDN.

403/404 при доступі до репозиторію

Звірте baseurl, права доступу до файлів, налаштування nginx (autoindex on;), а також гілку, з якої деплоїться GitLab Pages. Для приватних репозиторіїв оцініть альтернативу з власним сервером.

Помилки із SELinux на власному хостингу

Встановіть правильні контексти: chcon -R -t httpd_sys_content_t /var/www/repo або налаштуйте policy. Перевіряйте через audit2why/audit2allow.

Порада від Kernelka

Тримайте GPG-ключ окремим для підпису RPM і ротуйте його періодично. У CI зберігайте секрети як protected variables, а гілки для релізів обмежуйте. Маленька дисципліна — велика безпека ✨

Підсумок

  • Створили GPG-ключ і додали його до CI як секрет.
  • Підписали RPM-пакети і repomd.xml через rpmsign та gpg.
  • Згенерували метадані createrepo_c і опублікували статично (GitLab Pages або nginx).
  • Підключили репозиторій на клієнті через dnf/yum з gpgcheck.
  • За потреби використали GUI-варіант через Nexus.

Ось так, крок за кроком, і у вас є власний безпечний RPM-репозиторій з повною автоматизацією у GitLab CI. Ви круті!