About

For whatever reasons, you may want to create your own package repository, like this: repo.sainnhe.dev

In this article, I’m going to record my steps to create my own arch linux package repository, via github pages, no need to maintain your server.

Build signed packages

Create your gpg key

When you installed a package from AUR, you generally cloned a repo that contains a PKGBUILD file, then you executed makepkg to build this package. This is the minimalist steps to build a package, but by this way your package is not signed.

The goal of signing package is to guarantee that a package was created by the person that claims to have made it. Arch linux uses GnuPG to implement this feature, so let’s get start from GnuPG.

First of all, let’s create a gpg key:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
~
❯ gpg --full-generate-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory '/home/sainnhe/.gnupg' created
gpg: keybox '/home/sainnhe/.gnupg/pubring.kbx' created
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
  (14) Existing key from card
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)
Requested keysize is 3072 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 5y
Key expires at Fri 20 Feb 2026 02:46:59 PM CST
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Sainnhe Park
Email address: sainnhe@gmail.com
Comment:
You selected this USER-ID:
    "Sainnhe Park <sainnhe@gmail.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /home/sainnhe/.gnupg/trustdb.gpg: trustdb created
gpg: key 2C316AF89E7074F3 marked as ultimately trusted
gpg: directory '/home/sainnhe/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/sainnhe/.gnupg/openpgp-revocs.d/410B4A906DF63761AA2B26DC2C316AF89E7074F3.rev'
public and secret key created and signed.

pub   rsa3072 2021-02-21 [SC] [expires: 2026-02-20]
      410B4A906DF63761AA2B26DC2C316AF89E7074F3
uid                      Sainnhe Park <sainnhe@gmail.com>
sub   rsa3072 2021-02-21 [E] [expires: 2026-02-20]

Now you can use this command to check the key you just created:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
~
❯ gpg --list-keys
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2026-02-20
/home/sainnhe/.gnupg/pubring.kbx
--------------------------------
pub   rsa3072 2021-02-21 [SC] [expires: 2026-02-20]
      410B4A906DF63761AA2B26DC2C316AF89E7074F3
uid           [ultimate] Sainnhe Park <sainnhe@gmail.com>
sub   rsa3072 2021-02-21 [E] [expires: 2026-02-20]

Here, you need to pay attention to two key points.

In the output of this command, 410B4A906DF63761AA2B26DC2C316AF89E7074F3 is your key id, and sainnhe@gmail.com is your email.

410B4A906DF63761AA2B26DC2C316AF89E7074F3 is the full key id which contains 40 characters, but sometimes we can use the last 16 characters to represent this key, i.e. 2C316AF89E7074F3, this is the “short key”.

We will use them later.

So we successfully created a gpg key. Next, let’s share it with others.

Generally, there are two methods to share your keys. One of them is to send your key to a key server, then people can download it from the Internet.

Let’s try to send your key to a key server first. It’s pretty easy, simplely execute this command:

1
$ gpg --send-keys 410B4A906DF63761AA2B26DC2C316AF89E7074F3

If this command is executed successfully, other people can receive your key by running this command:

1
$ gpg --recv-keys 410B4A906DF63761AA2B26DC2C316AF89E7074F3

Or use the short key here:

1
$ gpg --recv-keys 2C316AF89E7074F3

Another method is to generate a key file and share it with others, then people can directly import your gpg key from this file.

To generate the key file, execute the following command:

1
$ gpg --output sainnhe.gpg --armor --export 410B4A906DF63761AA2B26DC2C316AF89E7074F3

Then we’ll get a key file named sainnhe.gpg.

To import this key, execute the following command:

1
$ gpg --import /path/to/sainnhe.gpg

Configure pacman

Now let’s configure pacman to use this gpg key.

Like gpg, there are also two methods for pacman to import a gpg key.

Mehod 1 is to receive it from key server:

1
$ sudo pacman-key --recv-keys 2C316AF89E7074F3

Another method is to directly import this key from a key file:

1
$ sudo pacman-key --add /path/to/sainnhe.gpg

After importing this key, it’s recommended to verify the fingerprint:

1
$ sudo pacman-key --finger 2C316AF89E7074F3

Finally, we need to locally sign the key:

1
$ sudo pacman-key --lsign-key 2C316AF89E7074F3

Configure makepkg

If you want to build packages signed with this key, you’ll also need to configure makepkg.

In your /etc/makepkg.conf, uncomment and modify the following lines:

1
2
3
4
#-- Packager: name/email of the person or organization building packages
PACKAGER="Sainnhe Park <sainnhe@gmail.com>"
#-- Specify a key to use for package signing
GPGKEY="2C316AF89E7074F3"

Remember to change the PACKAGER field, too. This will determine the Packager section in your package.

Build signed packages

Now let’s try to build a signed package.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
~
❯ cd repo
────────────────────────────────────────────────────────────────────
~/repo
❯ git clone https://aur.archlinux.org/pikaur.git
Cloning into 'pikaur'...
remote: Enumerating objects: 547, done.
remote: Counting objects: 100% (547/547), done.
remote: Compressing objects: 100% (313/313), done.
remote: Total 547 (delta 234), reused 547 (delta 234), pack-reused 0
Receiving objects: 100% (547/547), 142.15 KiB | 260.00 KiB/s, done.
Resolving deltas: 100% (234/234), done.
────────────────────────────────────────────────────────────────────
~/repo
❯ cd pikaur
────────────────────────────────────────────────────────────────────
~/repo/pikaur   master
❯ makepkg -sr --sign
==> Making package: pikaur 1.6.16.2-1 (Sun 21 Feb 2021 03:34:59 PM CST)
==> Checking runtime dependencies...
==> Checking buildtime dependencies...
==> Installing missing dependencies...
[sudo] password for sainnhe:
resolving dependencies...
looking for conflicting packages...

Packages (1) python-commonmark-0.9.1-3

Total Download Size:   0.10 MiB
Total Installed Size:  0.45 MiB

:: Proceed with installation? [Y/n]
:: Retrieving packages...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  100k  100  100k    0     0  35734      0  0:00:02  0:00:02 --:--:-- 35747
(1/1) checking keys in keyring                                                                      [###########################################################] 100%
(1/1) checking package integrity                                                                    [###########################################################] 100%
(1/1) loading package files                                                                         [###########################################################] 100%
(1/1) checking for file conflicts                                                                   [###########################################################] 100%
(1/1) checking available disk space                                                                 [###########################################################] 100%
:: Processing package changes...
(1/1) installing python-commonmark                                                                  [###########################################################] 100%
:: Running post-transaction hooks...
(1/3) Arming ConditionNeedsUpdate...
(2/3) Sync file system on /
(3/3) Updating pkgfile database
==> Retrieving sources...
  -> Downloading pikaur-1.6.16.2.tar.gz...
==> Validating source files with md5sums...
    pikaur-1.6.16.2.tar.gz ... Passed
==> Extracting sources...
  -> Extracting pikaur-1.6.16.2.tar.gz with bsdtar
==> Starting build()...
==> Entering fakeroot environment...
==> Starting package()...
==> Tidying install...
  -> Removing libtool files...
  -> Purging unwanted files...
  -> Removing static library files...
  -> Stripping unneeded symbols from binaries and libraries...
  -> Compressing man and info pages...
==> Checking for packaging issues...
==> Creating package "pikaur"...
  -> Generating .PKGINFO file...
  -> Generating .BUILDINFO file...
  -> Generating .MTREE file...
  -> Compressing package...
==> Leaving fakeroot environment.
==> Signing package(s)...
  -> Created signature file pikaur-1.6.16.2-1-any.pkg.tar.zst.sig.
==> Finished making: pikaur 1.6.16.2-1 (Sun 21 Feb 2021 03:35:37 PM CST)
==> Removing installed dependencies...
checking dependencies...

Packages (1) python-commonmark-0.9.1-3

Total Removed Size:  0.45 MiB

:: Do you want to remove these packages? [Y/n]
:: Processing package changes...
(1/1) removing python-commonmark                                                                    [###########################################################] 100%
:: Running post-transaction hooks...
(1/3) Arming ConditionNeedsUpdate...
(2/3) Sync file system on /
(3/3) Updating pkgfile database
────────────────────────────────────────────────────────────────────
~/repo/pikaur   master ✩
❯ ls
 CHANGELOG   pikaur-1.6.16.2-1-any.pkg.tar.zst   pikaur-1.6.16.2-1-any.pkg.tar.zst.sig   pikaur-1.6.16.2.tar.gz   pkg   PKGBUILD   src

The main difference between building signed packages and building unsigned packages is that you need to pass --sign argument to makepkg if you want to build signed packages.

And after building a signed package, we will get not only a *.pkg.tar.zst, but also a *.pkg.tar.zst.sig file. This *.pkg.tar.zst.sig file is the binary signature of this package, we’ll need to add both of these two files to our package repository.

Create arch linux package repository

Create a local package repository

So let’s create a repository first, then we will push it to github and publish this repository via github pages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
~
❯ cd repo
────────────────────────────────────────────────────────────────────
~/repo
❯ mkdir my_repo
────────────────────────────────────────────────────────────────────
~/repo
❯ cd my_repo
────────────────────────────────────────────────────────────────────
~/repo/my_repo
❯ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in /home/sainnhe/repo/my_repo/.git/
────────────────────────────────────────────────────────────────────
~/repo/my_repo   master
❯ mkdir x86_64
────────────────────────────────────────────────────────────────────
~/repo/my_repo   master
❯ cd x86_64
────────────────────────────────────────────────────────────────────
~/repo/my_repo/x86_64   master
❯ mv ~/repo/pikaur/pikaur-1.6.16.2-1-any.pkg.tar.zst ./
────────────────────────────────────────────────────────────────────
~/repo/my_repo/x86_64   master ✩
❯ mv ~/repo/pikaur/pikaur-1.6.16.2-1-any.pkg.tar.zst.sig ./
────────────────────────────────────────────────────────────────────
~/repo/my_repo/x86_64   master ✩
❯ repo-add --verify --sign sainnhe.db.tar.gz *.pkg.tar.zst
==> Adding package 'pikaur-1.6.16.2-1-any.pkg.tar.zst'
  -> Adding package signature...
  -> Computing checksums...
  -> Creating 'desc' db entry...
  -> Creating 'files' db entry...
==> Creating updated database file 'sainnhe.db.tar.gz'
==> Signing database 'sainnhe.db.tar.gz'...
  -> Created signature file 'sainnhe.db.tar.gz.sig'
==> Signing database 'sainnhe.files.tar.gz'...
  -> Created signature file 'sainnhe.files.tar.gz.sig'
────────────────────────────────────────────────────────────────────
~/repo/my_repo/x86_64   master ✩
❯ ls
 pikaur-1.6.16.2-1-any.pkg.tar.zst       sainnhe.db       sainnhe.db.tar.gz       sainnhe.files       sainnhe.files.tar.gz
 pikaur-1.6.16.2-1-any.pkg.tar.zst.sig   sainnhe.db.sig   sainnhe.db.tar.gz.sig   sainnhe.files.sig   sainnhe.files.tar.gz.sig

Note that we need to pass --verify --sign to repo-add so a detached signature of the repository database will be generated.

Now we successfully get a package repository named sainnhe.

Publish via github pages

Create a new public github repository, and push my_repo to this public repository.

Visit the settings page of this repository: https://github.com/sainnhe/my_repo/settings

Navigate to “GitHub Pages” section and change the “Source” field from “None” to “Branch: master”.

Wait for several minutes, and your github pages will be published at https://sainnhe.github.io/my_repo.

So the url of package repository should be https://sainnhe.github.io/my_repo/x86_64.

To add this repository, add something like this at the end of /etc/pacman.conf:

1
2
[sainnhe]
Server = https://sainnhe.github.io/my_repo/x86_64

Now refresh the package databases and your package repository will be successfully added.

1
$ sudo pacman -Sy