Skip to content

Commit 927c0e2

Browse files
committed
tools: Add scripts and supporting files to generate os packge repo
Signed-off-by: Abhijat Malviya <[email protected]>
1 parent fac42a0 commit 927c0e2

File tree

12 files changed

+379
-0
lines changed

12 files changed

+379
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: generate-site
2+
on:
3+
workflow_dispatch:
4+
5+
jobs:
6+
gen-site:
7+
runs-on: ubuntu-latest
8+
env:
9+
SiteRoot: _site
10+
11+
name: Generate index and site assets
12+
steps:
13+
- name: Checkout Repository
14+
uses: actions/checkout@v4
15+
16+
- name: Install packaging tools
17+
# RPM tools are available on ubuntu
18+
run: sudo apt install -y rpm gpg createrepo-c dpkg-dev reprepro
19+
20+
- name: Setup requirements
21+
working-directory: tools/packaging/osrepos
22+
run: pip install -r requirements.txt
23+
24+
- name: Download packages
25+
working-directory: tools/packaging/osrepos
26+
run: python scripts/fetch-releases.py $SiteRoot
27+
28+
- name: Import GPG key
29+
id: gpg-import
30+
uses: crazy-max/ghaction-import-gpg@v6
31+
with:
32+
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
33+
34+
- name: Sign RPMs
35+
shell: sh
36+
working-directory: tools/packaging/osrepos
37+
run: sh scripts/sign-rpms.sh ${{ steps.gpg-import.outputs.fingerprint }}
38+
39+
- name: Create YUM repository
40+
# Creates metadata for YUM/DNF repository, the files were copied in the download step
41+
shell: sh
42+
working-directory: tools/packaging/osrepos
43+
run: createrepo_c -v $SiteRoot/rpm
44+
45+
- name: Sign YUM repository
46+
shell: sh
47+
working-directory: tools/packaging/osrepos
48+
run: gpg --armor --detach-sign $SiteRoot/rpm/repodata/repomd.xml
49+
50+
- name: Create APT repository
51+
shell: sh
52+
working-directory: tools/packaging/osrepos
53+
run: sh -x scripts/generate-apt-repo.sh
54+
55+
- name: Prepare assets
56+
working-directory: tools/packaging/osrepos
57+
run: |
58+
cp -aRv dragonfly.repo pgp-key.public dragonfly.sources $SiteRoot/
59+
rm -rf $SiteRoot/deb/conf
60+
61+
- name: Generate Directory Listings
62+
working-directory: tools/packaging/osrepos
63+
run: python scripts/generate-index.py
64+
65+
- name: Authenticate
66+
uses: 'google-github-actions/auth@v3'
67+
with:
68+
project_id: 'dragonflydb'
69+
credentials_json: ${{ secrets.GCP_BUCKET_CREDENTIALS }}
70+
71+
- name: GCloud setup
72+
uses: 'google-github-actions/setup-gcloud@v3'
73+
74+
- name: Deploy site
75+
working-directory: tools/packaging/osrepos
76+
run: |
77+
gcloud storage rsync $SiteRoot gs://dragonfly-packages-bucket --recursive --delete-unmatched-destination-objects

tools/packaging/osrepos/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Package repositories for rpm and debian packages
2+
3+
This directory contains scripts and definitions for setting up YUM and apt repositories for Linux users to install
4+
dragonfly packages.
5+
6+
The repositories are served as static websites. The generate-site workflow is used to set up and deploy the sites using
7+
scripts and definitions included here.
8+
9+
The workflow does the following tasks:
10+
11+
* Download the latest 5 releases from dragonfly releases page, specifically deb and rpm assets
12+
* for deb files, only the latest package is downloaded and present (see note below)
13+
* Set up a directory structure separating deb and rpm files into version specific paths
14+
* Sign the packages (see note on GPG)
15+
* Deploy the assets prepared, along with the public GPG key and repo definitions for apt and rpm tooling
16+
17+
## Using the YUM repository
18+
19+
Add the repository using:
20+
21+
```shell
22+
sudo dnf config-manager addrepo --from-repofile=https://packages.dragonflydb.io/dragonfly.repo
23+
```
24+
25+
Then install dragonfly as usual, or a specific version:
26+
27+
```shell
28+
sudo dnf -y install dragonfly-0:v1.33.1-1.fc30.x86_64
29+
```
30+
31+
## Using the APT repository
32+
33+
First download the public GPG key to an appropriate location:
34+
35+
```shell
36+
sudo curl -Lo /usr/share/keyrings/dragonfly-keyring.public https://packages.dragonflydb.io/pgp-key.public
37+
```
38+
39+
Then add the sources file:
40+
41+
```shell
42+
sudo curl -Lo /etc/apt/sources.list.d/dragonfly.sources https://packages.dragonflydb.io/dragonfly.sources
43+
```
44+
45+
Finally install dragonfly using apt
46+
47+
```shell
48+
sudo apt update && sudo apt install dragonfly
49+
```
50+
51+
#### Versions in APT repository
52+
53+
Unlike the yum repo, the apt repo only has the latest version. The reason for this is the tool, `reprepro` supplied by
54+
debian to build repositories only supports multiple
55+
versions in version 5.4 onwards, and the github runner using ubuntu-latest does not have this version.
56+
57+
Another option would be to use the components feature of apt repositories in the sources file we ask users to install,
58+
but then the versions would need
59+
to be hardcoded in the sources file and the user would have
60+
to update the file with each new release whcih makes for a bad user experience. As of now users wanting older packages
61+
should download them directly.
62+
63+
### Signing packages
64+
65+
The packages are signed using the GPG key imported from the secret GPG_PRIVATE_KEY in this repository.
66+
67+
The corresponding public key is served with site assets, so the apt/yum/dnf based tooling can consume the public key to
68+
verify package integrity.
69+
70+
### TODO
71+
72+
- [X] debian packages signing (not required? release file is signed)
73+
- [X] debian repo metadata setup
74+
- [ ] tests asserting that packages are installable?
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[dragonfly]
2+
name=Dragonfly Packages
3+
baseurl=https://packages.dragonflydb.io/rpm/
4+
enabled=1
5+
gpgcheck=1
6+
gpgkey=https://packages.dragonflydb.io/pgp-key.public
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Types: deb
2+
URIs: https://packages.dragonflydb.io/deb
3+
Suites: noble
4+
Components: main
5+
Signed-By: /usr/share/keyrings/dragonfly-keyring.public
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-----BEGIN PGP PUBLIC KEY BLOCK-----
2+
3+
mQINBGjkpygBEADuvzXdOXChr/e4Uh2UBne60NPjmuhpjmArfMfqySeRezJ1Nuvd
4+
AvKNuYRyCw+zsh0Zc/sSANpIdAeKPqrfZJgfEIJI0f8WVjfqsCKi+yWB7Bx0GjQ9
5+
y/xoFLKkT7p0P/F4yRlb8kQq2KVP9UvcZBETJY96TpQIJM4N3XoG+8DsELW5HYF2
6+
6sbhgmaNUsxm9oH5UqHcBc7TTgUp10GmZFR4dTeB1IffD/eLMVDMQ8ygzmVxkJPQ
7+
zEKfpFFzseTVyreQlZ5U4GDR8FiB0mY4gZxbCywNqZRycyMM7v4EHuUO0fOgRHdl
8+
5dseF+H1aEG/00JRo6zjiIbgMga0x9wYmVWvTU4wLnGoomukEMCkEQxlil1QjUlK
9+
XI0EltU03DuGki5uhYc9dSS1h74ku2xWePaMsvmxrTphRo1WQBDutzVXSIZ6NBc3
10+
BN+VBHcumVvif9aRrsfsj2CXhnOB61AW+VWk3fk0evW9cceXZDA0NgGdyeTfS7EI
11+
pioaWtmE3Uv3AfHTlNbMytxG7d7k7oAT2xV6z2IygyQZ5LI1tvSJJ+I5kZHKeruj
12+
k2bFp6H9FGi+g4kA+z9QWgkt+0UXYbjKZAs5Es1uGrRk6o1rAyVTKBKz62F0YQbK
13+
j8Q49Z6iSobaKeQG8naCVkALSM49i4Zpw3x1jUpd7k8/KhpJObq3rewqIQARAQAB
14+
tCREcmFnb25mbHkgPHBhY2thZ2luZ0BkcmFnb25mbHlkYi5pbz6JAlIEEwEKADwW
15+
IQRgvYPC7oTdikxvMGcSMEAYvD0qugUCaOSnKAMbLwQFCwkIBwICIgIGFQoJCAsC
16+
BBYCAwECHgcCF4AACgkQEjBAGLw9KrpGbw//VH2zUjaoSh7SnKGdDOA7A95o2EET
17+
ZvChxImyb6xNKfUoMajPnKcJFg514aPFKLuJl4qJmikxdqBF/bYkznCQSJcLQhsT
18+
pvkqanUh/XwBqbJye1QjBq1o0qXLgeY/Ciz2nqupwLQdzvGHO6+2Yk04T89pnZEo
19+
CDSoZKkacu8TpalStqzqDlumryXZzdZ35hAu9OT0fVc2wtcMiY3pznLG1iawNk8I
20+
bzme0ezGA/fk7xEptEbGlb1OtUV5+iG/SFEVvic8GTNf1yLQNCVK3QzD1ciL3MzR
21+
OTH8a04ov2bMxjl8bIefKE/dFBeCSKbvkfTSMAEgqUAuRp7gvoO7uHO05A5AHU2i
22+
y4agskGkgQR9u1yqUXyYIM9kkpuUqqAkwRqg1pw55LG686Xe35QYH4zbpgvr45/Q
23+
JRPFjCbLzR1ZcNyrecHgrq2M9WNlk6dtdWBSJuc7L0M8KJqfrPxQmMpMm/KR43Ey
24+
um0FCgb2J+ceO2W4GrE/DHHoNTt2iio2gMcmRXM7XTmVupsigbYk7AqGncLIQ60B
25+
94jtv16ggXIeA5sPqmyssARXtweTM+EzLLs4K79be4K5j/yyg3CxxvZcq5CZNwoi
26+
fbQgGVNb4SS+nv2r1mVe9XNSonmVVrAqSIFpptH5ahqgaRDUnmy0Lzk7qiHv02OW
27+
PjbSiwQGHDHwq98=
28+
=SOT5
29+
-----END PGP PUBLIC KEY BLOCK-----
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Codename: noble
2+
Suite: stable
3+
Architectures: amd64 arm64
4+
Components: main
5+
Origin: Dragonfly
6+
Label: Dragonfly
7+
Description: Dragonfly APT repository
8+
SignWith: 60BD83C2EE84DD8A4C6F306712304018BC3D2ABA
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
verbose
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
certifi>=2025.10.5
2+
charset-normalizer>=3.4.3
3+
idna>=3.10
4+
requests>=2.32.5
5+
urllib3>=2.5.0
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import dataclasses
2+
import enum
3+
import os.path
4+
import time
5+
6+
import requests
7+
8+
"""
9+
Fetches the latest five releases for RPM and the single latest release for DEB.
10+
RPM files are placed in the destination folder where the DNF repo will expect them.
11+
DEB files are placed in a temporary location from where they will be copied by the
12+
reprepro tool.
13+
"""
14+
15+
RELEASE_URL = "https://api.github.com/repos/dragonflydb/dragonfly/releases"
16+
17+
18+
class AssetKind(enum.Enum):
19+
RPM = 1
20+
DEB = 2
21+
22+
23+
@dataclasses.dataclass
24+
class Package:
25+
kind: AssetKind
26+
download_url: str
27+
version: str
28+
filename: str
29+
arch: str
30+
31+
@staticmethod
32+
def from_url(url: str) -> "Package":
33+
tokens = url.split("/")
34+
filename = tokens[-1]
35+
kind = AssetKind.RPM if filename.endswith(".rpm") else AssetKind.DEB
36+
if kind == AssetKind.DEB:
37+
arch = filename.split(".")[0].split("_")[1]
38+
else:
39+
arch = filename.split(".")[1]
40+
return Package(
41+
kind=kind, download_url=url, version=tokens[-2], filename=filename, arch=arch
42+
)
43+
44+
def storage_path(self, root: str) -> str:
45+
match self.kind:
46+
case AssetKind.RPM:
47+
return os.path.join(root, "rpm", self.version)
48+
case AssetKind.DEB:
49+
# Debian packages are stored in a temporary path.
50+
# The reprepro tool will copy them later to the final path.
51+
return os.path.join("deb_tmp", self.arch, self.version)
52+
53+
54+
def collect_download_urls() -> list[Package]:
55+
packages = []
56+
# TODO retry logic
57+
response = requests.get(RELEASE_URL)
58+
releases = response.json()
59+
for release in releases[:5]:
60+
for asset in release["assets"]:
61+
if asset["name"].endswith(".rpm") or asset["name"].endswith(".deb"):
62+
packages.append(Package.from_url(asset["browser_download_url"]))
63+
return packages
64+
65+
66+
def download_packages(root: str, packages: list[Package]):
67+
# The debian repository building tool, reprepo, only supports a single package per version by default.
68+
# The ability to support multiple versions has been added but is not present in ubuntu-latest on
69+
# github action runners yet. So we only download one package, the latest, for ubuntu.
70+
# The rest of the scripts work on a set of packages, so that when the Limit parameter is supported,
71+
# we can remove this flag and start hosting more than the latest versions.
72+
# Another alternative would be to use the components feature of reprepo, but it would involve updating
73+
# the repository definition itself for each release, which is a bad experience for end users.
74+
deb_done = False
75+
for package in packages:
76+
if package.kind == AssetKind.DEB and deb_done:
77+
continue
78+
79+
print(f"Downloading {package.download_url}")
80+
path = package.storage_path(root)
81+
if not os.path.exists(path):
82+
os.makedirs(path)
83+
84+
target = os.path.join(path, package.filename)
85+
# TODO retry logic
86+
response = requests.get(package.download_url)
87+
with open(target, "wb") as f:
88+
f.write(response.content)
89+
print(f"Downloaded {package.download_url}")
90+
time.sleep(0.5)
91+
if package.kind == AssetKind.DEB:
92+
deb_done = True
93+
94+
95+
def main(root: str):
96+
packages = collect_download_urls()
97+
download_packages(root, packages)
98+
99+
100+
if __name__ == "__main__":
101+
import sys
102+
103+
if len(sys.argv) == 1:
104+
print(f"Usage: {sys.argv[0]} <site folder>")
105+
sys.exit(1)
106+
main(sys.argv[1])
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
set -e
2+
3+
METADATA_ROOT=_site/deb
4+
mkdir -pv ${METADATA_ROOT}/conf
5+
6+
cp -av reprepro-config/* ${METADATA_ROOT}/conf
7+
8+
reprepro -b ${METADATA_ROOT} createsymlinks
9+
reprepro -b ${METADATA_ROOT} export
10+
11+
for file in $(find deb_tmp -type f -name "*.deb"); do
12+
reprepro -b ${METADATA_ROOT} includedeb noble "${file}"
13+
done
14+
15+
# reprepro copied files to the destination, the temporary files can be removed now
16+
rm -rf deb_tmp

0 commit comments

Comments
 (0)