This is an update on this blog post and mainly on its hands-on part.

There have been a continuous effort in the area of linux distribution maintenance to make its tooling reusable and accessible for everyone. Finally, this note is presenting the results of this effort to you. It will give you an idea how big distros like Fedora or RHEL are maintained and it will also show you a way to start and maintain your own linux distribution.

Okay, show me what you’ve got

The following tutorial will be run with dist-git 1.8-1 and rpkg 2.6-1. You may find packages of those or higher versions already in official Fedora and EPEL repositories but we will be using Fedora coprs clime/rpkg-util and clime/dist-git to ensure we have the needed up-to-date versions.

Note: On EPEL, we will also use updated git from clime/rpkg-util.

If you use vagrant and you want to skip the setup part below, you can use this Vagrantfile for Fedora-28. You can then immediately jump here.

Otherwise, let’s begin!

Normally, you would use a server machine and a client machine but for simple playing around, we can just use localhost to substitute for both. We recommend to create an isolated container or a virtual machine to carry out the tutorial.

First, install the dist-git package:

# dnf install dnf-plugins-core
# dnf copr enable clime/dist-git
# dnf install dist-git

on CentOS7 or RHEL7 with EPEL7 (yum install epel-release) enabled:

# yum install yum-plugin-copr
# yum copr enable clime/dist-git
# yum install dist-git

Next step is to setup Apache for uploading source tarballs.

There is /etc/httpd/conf.d/dist-git/lookaside-upload.conf.example provided by the dist-git package itself for ssl uploading with authentication by client certificates but we will use something much more simple. Put the following lines into /etc/httpd/conf.d/dist-git/lookaside-upload.conf:

<VirtualHost _default_:80>
    # This alias must come before the /repo/ one to avoid being overridden.
    ScriptAlias /repo/pkgs/upload.cgi /var/lib/dist-git/web/upload.cgi

    Alias /repo/ /var/lib/dist-git/cache/lookaside/

    LogLevel trace8

    # provide username manually to upload.cgi
    SetEnv SSL_CLIENT_S_DN_CN joe

    <Location /repo/pkgs/upload.cgi>
        Options +ExecCGI
        Require all granted

Now you can start the httpd server:

# systemctl start httpd

If you hit problems with localhost ssl certs missing on httpd start, move /etc/httpd/conf.d/ssl.conf to /etc/httpd/conf.d/ and try again.

We will now create two users to carry out all the unprivileged tutorial actions.

User joe will be responsible for all client operations (i.e. cloning/pushing) and user admin will be responsible for all server operations (i.e. setting up a new package repo/chilling out). Both joe and admin need to belong to packager group that got created on installation of the dist-git package.

# useradd admin -G packager
# useradd joe -G packager

There is very few things missing to set up the server part at this point. First, start dist-git.socket service so that git:// protocol works for anonymous read-only access (we shall use it later):

# systemctl start dist-git.socket
# systemctl status dist-git.socket  # state should be "active (listening)"

Now we will make sure sshd is up and running which we will be used for authorized Git read/write access.

To install ssh server on Fedora, run:

# dnf install openssh-server

To install it on EPEL, run:

# yum install openssh-server

Then finally on both distros, you can run:

# systemctl start sshd
# systemctl status sshd  # state should be "active (running)"

Also, let’s configure public key access to localhost for user joe:

# su joe
joe@localhost / $ cd
joe@localhost ~ $ ssh-keygen  # press enter on everything
joe@localhost ~ $ cat .ssh/ >> .ssh/authorized_keys
joe@localhost ~ $ chmod 600 .ssh/authorized_keys
joe@localhost ~ $ ssh localhost  # on Fedora, you might need to do rm /run/nologin as root

Now for the client part, install the rpkg package. We will use the latest package version from project.

On Fedora, you can invoke:

# dnf copr enable clime/rpkg-util
# dnf install rpkg

On EPEL, you can do:

# yum copr enable clime/rpkg-util
# yum install rpkg
# yum install git  # to upgrade git from the enabled copr repo

Put the following configuration into /etc/rpkg.conf (by replacing the default content):

preprocess_spec = True

# auto-packing is deprecated:
auto_pack = False

base_output_path = /tmp/rpkg

lookaside = http://localhost/repo/pkgs/%(ns1)s/%(name)s/%(filename)s/%(hashtype)s/%(hash)s/%(filename)s
lookaside_cgi = http://localhost/repo/pkgs/upload.cgi
gitbaseurl = ssh://%(user)s@localhost/var/lib/dist-git/git/%(module)s
anongiturl = git://localhost/%(module)s

That’s it! Let’s create our first DistGit repository.

# su admin
admin@localhost / $ /usr/share/dist-git/setup_git_package package  # creates Git repo on the server
Generating initial grok manifest...
admin@localhost / $ ls /var/lib/dist-git/git/rpms

Now you can already interact with the created DistGit repository by using rpkg:

admin@localhost / $ exit
# su joe
joe@localhost / $ cd
joe@localhost ~ $ rpkg clone package  # clones the package.git repo
joe@localhost ~ $ cd package
joe@localhost package $ ls

Now we are in our local cloned Git repository. Let’s initialize it with some public source rpm:

joe@localhost package $ curl -o /tmp/prunerepo-1.13-1.fc28.src.rpm
joe@localhost package $ rpkg import /tmp/prunerepo-1.13-1.fc28.src.rpm  # unpack src.rpm, upload tarball into dist-git's lookaside and modify local repo accordingly
joe@localhost package $ git status  # display what has been changed in the local git repo
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitignore
        new file:   prunerepo.spec
        modified:   sources

joe@localhost package $ cat sources  # let's display the pointer to the lookaside cache for the uploaded tarball
SHA512 (prunerepo-1.13.tar.gz) = 25c3f6e42f390e4e2215f0f24fea4a0482ee910ce7fa129c8d91c33bf350d31c564796721437a053ad34bdddb67c36cbb8130b5e54c5bf6af9d68bed0e983244

joe@localhost package $ git config --global "joe@localhost"  # set user git commit info
joe@localhost package $ git config --global "joe"

joe@localhost package $ git commit -m "DistGit test update" -a  # commit changes to the local Git repo
joe@localhost package $ git rev-parse master  # show commit hash, output will differ for you
joe@localhost package $ rpkg srpm  # build srpm just to test things out
joe@localhost package $ rpkg push  # push local git changes to DistGit
Wrote: /tmp/rpkg/prunerepo-1-qzygm0hc/prunerepo.spec
Wrote: /tmp/rpkg/prunerepo-1-qzygm0hc/prunerepo-1.13-1.fc28.src.rpm
joe@localhost package $ rpkg build  # build package in Copr BuildSystem, this needs copr-cli tool to be installed

You can also verify that the changes got into the DistGit server:

joe@localhost package $ cat /var/lib/dist-git/git/rpms/package.git/refs/heads/master
joe@localhost package $ ls /var/lib/dist-git/cache/lookaside/pkgs/rpms/package/prunerepo-1.13.tar.gz/sha512/25c3f6e42f390e4e2215f0f24fea4a0482ee910ce7fa129c8d91c33bf350d31c564796721437a053ad34bdddb67c36cbb8130b5e54c5bf6af9d68bed0e983244/prunerepo-1.13.tar.gz

And you can, of course, now clone the repo and start doing something from scratch. Let’s do it with -a switch, which uses the git:// scheme and the read-only git-smart-http Git backend.

joe@localhost package $ cd
joe@localhost ~ $ rpkg clone -a package package-copy
joe@localhost package-copy $ cd package-copy
joe@localhost package-copy $ ls
prunerepo.spec  sources
joe@localhost package-copy $ rpkg sources  # fetch the tarball
Downloading prunerepo-1.13.tar.gz from lookaside cache at localhost
######################################################################## 100.0%
joe@localhost package-copy $ ls
prunerepo-1.13.tar.gz  prunerepo.spec  sources
joe@localhost package-copy $ rpkg srpm  # build srpm again just to see that it works
Wrote: /tmp/rpkg/prunerepo-2-_w0wu16l/prunerepo.spec
Wrote: /tmp/rpkg/prunerepo-2-_w0wu16l/prunerepo-1.13-1.fc27.src.rpm

Pretty cool, right? You can basically start your own linux distribution from this very basic initial setup.

Needless to say, so far we have show-cased work with traditional packed sources only (spec + patches + tarballs) and this is how Fedora, CentOS, RHEL, Mageia and other distros work.

What is quite interesting about this setup (DistGit+rpkg) is that you can have unpacked sources repos (spec + raw source files) in DistGit as well and that’s thanks to support for this in the rpkg utility.

Let’s take project, which is raw sources with spec, and import it to our setup here.

# Here we are no longer mentioning commands for switching between users and dirs.

admin@localhost / $ /usr/share/dist-git/setup_git_package hello_rpkg

joe@localhost ~ $ rpkg clone hello_rpkg
joe@localhost ~ $ cd hello_rpkg
joe@localhost hello_rpkg $ git pull --allow-unrelated-histories  # on EPEL, omit --allow-unrelated-histories switch
joe@localhost hello_rpkg $ ls
Makefile  hello_rpkg.spec.rpkg  main.c  sources

So we have imported code and history from project. There is sources file in addition to the content at, which was created by /usr/share/dist-git/setup_git_package script. We may remove it as we won’t probably be using lookaside cache for this particular project.

joe@localhost hello_rpkg $ rm sources
joe@localhost hello_rpkg $ git commit -a -m 'remove unneeded sources file'

Note: In the current upstream version of dist-git at, the empty ‘sources’ file is no longer being pregenerated.

And let’s push:

joe@localhost hello_rpkg $ rpkg push

to get the code and history import finished.

Now it is time a play around with the code a little bit. So let’s again try to generate an srpm, this time from unpacked sources:

joe@localhost hello_rpkg $ rpkg srpm
git_dir_pack: packing path /home/joe/hello_rpkg
git_dir_pack: Wrote: /tmp/rpkg/hello_rpkg-1-2oz9vvwk/hello_rpkg-0.0.git.8.1a2615b.tar.gz
Wrote: /tmp/rpkg/hello_rpkg-1-2oz9vvwk/hello_rpkg.spec
Wrote: /tmp/rpkg/hello_rpkg-1-2oz9vvwk/hello_rpkg-0.0.git.8.1a2615b-1.fc28.src.rpm

You can see that it works as well as it worked for the packed case (spec+patches+tarballs). How is that even possible? You will find out when you closer examine the hello_rpkg.spec.rpkg file, which is an rpkg spec file template. Particularly, let’s examine the line defining an rpm source ('Source:'), which is usually a name of tarball stored in the lookaside cache:

joe@localhost hello_rpkg $ grep 'Source:' hello_rpkg.spec.rpkg
Source:     {{{ git_dir_pack }}}

{{{ git_dir_pack }}} is a special rpkg macro, which tells rpkg that the tarball should be dynamically generated from the Git checked-out content. That generated tarball will be then used to build the final srpm. This is different from the standard procedure where the tarball is statically present next to the spec file (even though just as a link to the lookaside until you download it) and can be just used to build an srpm.

This feature of rpkg utility enables you to work with the sources in their unpacked form and only pack them when you need to build them. With this feature, instead of adding patch files and Patch: directives into the spec file, you could just commit the changes without needing to generate patch files at all.

That’s it. This tutorial should give you the basic gist of how it works under the hood in Fedora and similarly, in other rpm distros. All those distros still use just the traditional spec+patches+tarballs approach. So if you use the setup presented here, you are going to be ahead of them as far as Git package maintenance goes.

Anything else?

The DistGit upstream is hosted at

The rpkg-util upstream is hosted at

Please, send us patches and requests there.