J'ai créé fedora-rpm-copr-playground pour apprendre à publier des packages RPM sur Fedora COPR

Journal du lundi 23 mars 2026 à 14:23

Introduction

Après trois ans à repousser ce projet, je me suis enfin lancé en janvier 2026 dans la création de paquets RPM pour Fedora COPR.

J'ai créé et publié les packages aichat-git (repository) et text-to-audio (repository). L'expérience a été beaucoup plus simple et rapide que je le pensais. Les agents IA simplifient certes ce genre de tâche, mais même sans eux, le code reste plutôt minimaliste.

Pourquoi est-ce que je me suis intéressé à ce sujet ? Au départ, c'était pour distribuer qemu-compose sous forme de package RPM (voir issue).

Pour bien maîtriser ces opérations, la semaine dernière, je suis reparti de zéro et j'ai implémenté et publié le playground : fedora-rpm-copr-playground. Voici les objectifs de ce playground :

  • Générer un package pour distribuer un simple script Bash qui affiche un "Hello world" (dans la branche bash).
  • Générer un package pour distribuer une application Golang qui affiche un "Hello world" (dans la branche golang)

Pour chacun de ces packages, j'ai testé trois méthodes de build :

  • build du package RPM 100% local
  • build du package SRPM en local, puis upload sur Fedora COPR qui génère les RPM pour plusieurs plateformes et architectures (x86_64, aarch64, etc.)
  • une méthode basée à 100% sur Fedora COPR à partir des sources d'un dépôt GitHub, déclenchée automatiquement par un script GitHub Actions

Cette note documente ce playground et rassemble les difficultés que j'ai rencontrées. Le README.md reste consultable si vous préférez suivre un exemple pas à pas.

Le fichier .spec

Le point central pour créer un package RPM est le fichier .spec /rpm/hello-bash.spec :

# 
Name:           hello-bash
Version:        1.0.7
Release:        1%{?dist}
Summary:        A simple Hello World bash script

License:        MIT
URL:            https://github.com/stephane-klein/fedora-rpm-copr-playground
Source0:        hello-bash

BuildArch:      noarch

%description
A simple "Hello World" Bash script packaged as an RPM for Fedora COPR.

%prep
# Nothing to prepare, source is ready

%build
# Nothing to build, it's a bash script

%install
mkdir -p %{buildroot}/%{_bindir}
cp %{SOURCE0} %{buildroot}/%{_bindir}/hello-bash
chmod 755 %{buildroot}/%{_bindir}/hello-bash

%files
%{_bindir}/hello-bash

%changelog
* Thu Mar 19 2026 Stéphane Klein <contact@stephane-klein.info> - 1.0.0-1
- Initial release

Les lignes importantes dans ce fichier :

  • BuildArch: noarch, étant donnée que c'est un simple script, ce package n'est pas dépendant de l'architecture (processeur).
  • La section %install
  • La section %files

La syntaxe du format .spec peut sembler étrange en 2026. Elle date de 1995 — avant même l'existence de YAML (2001) et JSON (1999). Cette ancienneté explique les %... et %{...} qui peuvent paraitre cryptiques aujourd'hui.

Historiquement, le champ Source0 pointe vers une archive (généralement un tar.gz), contenant les sources du projet. Pour des cas simples, comme ici avec le script Bash, Source0 peut directement référencer le fichier source.

J'ai aussi implémenté une variante bash-multifiles dans le playground, pour tester le packaging de plusieurs scripts accompagnés d'un fichier de documentation. J'y indique les fichiers via Source0:, Source1:, Source2:, puis je les copie dans %install avec %{SOURCE0}, %{SOURCE1}, %{SOURCE2}. Cela fonctionne correctement, bien qu'au-delà de trois ou quatre fichiers, je pense qu'il soit probablement plus pratique d'utiliser une archive.

Build local du package RPM

Le script /build.sh suivant permet de générer un package RPM :

#!/bin/bash
set -e

TOPDIR="$(pwd)/rpmbuild"

mkdir -p "$TOPDIR"/{BUILD,RPMS,SRPMS,SOURCES,SPECS}

echo "Copying source to SOURCES..."
cp hello-bash "$TOPDIR/SOURCES/"

echo "Building RPM..."
rpmbuild --define "_topdir $TOPDIR" -ba rpm/hello-bash.spec

echo ""
echo "Build complete!"
echo "RPM: $TOPDIR/RPMS/noarch/"

Il commence par préparer la structure de dossier suivante :

/rpmbuild/
├── BUILD
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS

Ensuite les fichiers à packager sont copiés dans rpmbuild/SOURCES

/rpmbuild/
├── BUILD
├── RPMS
├── SOURCES
│   ├── hello-bash
├── SPECS
└── SRPMS

Pour finir, la commande rpmbuild --define "_topdir $TOPDIR" -ba rpm/hello-bash.spec génère à la fois le package SRPM (source RPM) et le RPM binaire. L'option -ba signifie "build all". Pour générer uniquement le SRPM, il faudrait utiliser -bs (build source). Ici, comme le package contient un script Bash, il est de type noarch :

/rpmbuild/
├── BUILD
├── RPMS
│   └── noarch
│       └── hello-bash-1.0.7-1.fc42.noarch.rpm
├── SOURCES
│   ├── hello-bash
├── SPECS
└── SRPMS
    └── hello-bash-1.0.7-1.fc42.src.rpm

Publication sur Fedora COPR

Le playground contient un second script qui permet de publier le package sur Fedora COPR, ce qui permet de rendre accessible publiquement son package.

Voici comment cette méthode fonctionne. Tout d'abord, il faut créer un compte et un projet sur Fedora COPR. Dans le playground, j'ai implémenté le script init-copr-project.sh basé sur copr-cli, qui me permet d'automatiser la création du projet (paradigme GitOps).

$ copr-cli create "hello-bash" \
    --description "A simple Hello World Bash script packaged as an RPM (auto-build on tags)" \
    --chroot fedora-42-x86_64 \
    --chroot fedora-43-x86_64 \
    --chroot fedora-44-x86_64

Dans cet exemple, je demande à COPR de builder les packages du projet pour les distributions fedora-42-x86_64, fedora-43-x86_64, fedora-44-x86_64.

Après avoir configuré le projet COPR, je lance le script /build-copr.sh qui exécute :

copr-cli build "hello-bash" /rpmbuild/SRPMS/hello-bash-1.0.6-1.fc42.src.rpm

Le premier paramètre "hello-bash" est le nom du projet et le second est le package source SRPM préalablement construit localement par le script /build.sh.

Voici ce que donne l'exécution de ./build-copr.sh côté cli :

$ ./build-copr.sh

...

Build complete!
RPM: /home/stephane/git/github.com/stephane-klein/fedora-rpm-copr-playground/.worktree/bash/rpmbuild/RPMS/noarch/
Uploading package ./rpmbuild/SRPMS/hello-bash-1.0.6-1.fc42.src.rpm
 |################################| 8.5 kB 47.1 kB/s eta 0:00:00
Build was added to hello-bash:
  https://copr.fedorainfracloud.org/coprs/build/10252699
Created builds: 10252699
Watching build(s): (this may be safely interrupted)
  08:59:15 Build 10252699: pending
  08:59:45 Build 10252699: running
  09:00:15 Build 10252699: starting
  09:00:46 Build 10252699: running

Voici ce qui est visible sur l'interface web de COPR, https://copr.fedorainfracloud.org/coprs/stephaneklein/hello-bash/builds/ :

Une fois le build des packages terminé, il est facile d'installer le package avec les commandes suivantes :

$ sudo dnf copr enable -y stephaneklein/hello-bash
$ sudo dnf install -y hello-bash
$ hello-bash
Hello World

Automatisation GitOps avec COPR

Et pour finir, j'ai implémenté dans le playground l'automatisation complète de la compilation et publication des packages sur l'infrastructure COPR.

Pour cela, dans le script init-copr-project.sh j'ai déclaré l'URL du repository qui contient le code source :


...

copr-cli add-package-scm "$COPR_PROJECT" \
    --name hello-bash \
    --clone-url https://github.com/stephane-klein/fedora-rpm-copr-playground.git \
    --commit bash \
    --subdir . \
    --spec rpm/hello-bash.spec \
    --type git \
    --method make_srpm \
    --webhook-rebuild on

Le paramètre --commit bash permet de définir la branche Git à utiliser comme source.

Le paramètre --method make_srpm, qui permet à l'utilisateur d'utiliser un script personnalisé de génération du SRPM, à placer dans /.copr/Makefile à la racine du dépôt avec une cible srpm, exemple :

specfile = rpm/hello-bash.spec

.PHONY: srpm
srpm: $(specfile)
	mkdir -p /tmp/copr-srpm-build
	cp rpm/hello-bash.spec /tmp/copr-srpm-build/hello-bash.spec
	cp -r . /tmp/copr-srpm-build/source/
	cd /tmp/copr-srpm-build && \
		rpmbuild -bs hello-bash.spec \
			--define "_topdir /tmp/copr-srpm-build/rpmbuild" \
			--define "dist .fc42" \
			--define "_sourcedir /tmp/copr-srpm-build/source"
	cp /tmp/copr-srpm-build/rpmbuild/SRPMS/*.src.rpm $(outdir)

Je ne souhaite pas détailler ici d'autres méthodes comme tito ou Packit, mais la méthode make_srpm est la plus flexible, elle permet de contrôler entièrement comment le SRPM est construit.

Une fois tout ceci configuré, il est possible de rebuild le package directement en cliquant sur le bouton "Rebuild" sur l'interface web de COPR :

Dernière étape : j'ai implémenté un build automatique qui est déclenchée par un appel curl dans le job GitHub Actions /.github/workflows/trigger-copr-build.yml, dont voici le contenu :

name: Trigger Copr Build

on:
  push:
    tags:
      - '*'

jobs:
  trigger-copr-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Verify tag is on bash branch
        run: |
          if ! git branch -r --contains ${{ github.ref_name }} | grep -q "origin/bash"; then
            echo "Tag ${{ github.ref_name }} is not on branch bash"
            exit 1
          fi

      - name: Trigger Copr webhook
        run: |
          curl -X POST https://copr.fedorainfracloud.org/webhooks/custom/226325/3cf20247-820b-4050-bfb1-593b01a6996f/hello-bash/

Ce job est exécuté à chaque publication d'un nouveau Git tag, suivi d'une vérification que le tag provient bien de la branche bash.

Claude Sonnet 4.6 m'a suggéré l'existence d'une méthode de polling de dépôt Git intégrée à COPR, mais je n'ai trouvé aucune trace de celle-ci dans la documentation.

J'ai aussi essayé d'utiliser la méthode basée sur les webhooks GitHub de COPR, mais je n'ai pas réussi à la faire fonctionner. L'interface de GitHub m'indiquait à chaque fois une erreur dans la réponse des calls HTTP. C'est pour cela que j'ai fini par déclencher le webhook custom via un job GitHub Actions.

Package d'un projet en Golang

Le playground contient aussi le packaging d'une application en Golang, consultable dans la branche golang.

Voici le contenu du fichier /golang/rpm/hello-golang.spec :

Name:           hello-golang
Version:        1.0.10
Release:        1%{?dist}
Summary:        A simple Hello World Go application

License:        MIT
URL:            https://github.com/stephane-klein/fedora-rpm-copr-playground
Source0:        %{name}-%{version}.tar.gz

BuildRequires:  golang >= 1.21

%description
A simple "Hello World" Go application packaged as an RPM for Fedora COPR.

%prep
%autosetup

%build
go build -ldflags "-X main.version=%{version}" -o %{name}

%install
mkdir -p %{buildroot}%{_bindir}
cp %{name} %{buildroot}%{_bindir}/

%files
%{_bindir}/%{name}

%changelog
* Fri Mar 20 2026 Stéphane Klein <contact@stephane-klein.info> - 1.0.0-1
- Initial release

Les principales différences avec la version pour Bash :

  • Absence de BuildArch: noarch
  • Présence de BuildRequires: golang >= 1.21
  • Et l'ajout des instructions suivantes :
%prep
%autosetup

%build
go build -ldflags "-X main.version=%{version}" -o %{name}

Peu de changement au niveau du script /build-rpm-locally.sh, qui génère ces fichiers :

rpmbuild
├── BUILD
├── RPMS
│   └── x86_64
│       ├── hello-golang-1.0.10-1.fc42.x86_64.rpm
│       ├── hello-golang-debuginfo-1.0.10-1.fc42.x86_64.rpm
│       └── hello-golang-debugsource-1.0.10-1.fc42.x86_64.rpm
├── SOURCES
│   ├── hello-golang-1.0.10
│   │   ├── go.mod
│   │   └── main.go
│   └── hello-golang-1.0.10.tar.gz
├── SPECS
└── SRPMS
    └── hello-golang-1.0.10-1.fc42.src.rpm

Cette fois, plus rien dans le dossier RPMS/noarch/, la commande rpmbuild --define "_topdir $TOPDIR" -ba rpm/hello-golang.spec build le package pour la distribution de la workstation du développeur.

Pour le reste, je n'ai pas identifié de différence majeure entre la version Bash et la version Golang

La suite… méthode Tito et Packit

Pour être tout à fait transparent, en rédigeant cette note, j'ai découvert les méthodes tito et Packit.

Je compte mettre à jour stephane-klein/fedora-rpm-copr-playground pour les tester et ensuite publier une nouvelle note de compte rendu.