Skip to content

Commit 908c864

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

File tree

12 files changed

+346
-0
lines changed

12 files changed

+346
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: generate-site
2+
on: [ push ]
3+
4+
jobs:
5+
gen-site:
6+
runs-on: ubuntu-latest
7+
8+
name: Generate index and site assets
9+
steps:
10+
- name: Checkout Repository
11+
uses: actions/checkout@v4
12+
13+
- name: Install RPM tools
14+
run: sudo apt install -y rpm gpg createrepo-c dpkg-dev reprepro
15+
16+
- name: Setup requirements
17+
run: pip install -r requirements.txt
18+
19+
- name: Download packages
20+
run: python scripts/fetch-releases.py
21+
22+
- name: Import GPG key
23+
id: gpg-import
24+
uses: crazy-max/ghaction-import-gpg@v6
25+
with:
26+
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
27+
28+
- name: Sign RPMs
29+
shell: sh
30+
run: sh scripts/sign-rpms.sh ${{ steps.gpg-import.outputs.fingerprint }}
31+
32+
- name: Create YUM repository
33+
shell: sh
34+
run: createrepo_c -v _site/rpm
35+
36+
- name: Sign YUM repository
37+
shell: sh
38+
run: gpg --armor --detach-sign _site/rpm/repodata/repomd.xml
39+
40+
- name: Create APT repository
41+
shell: sh
42+
run: sh -x scripts/generate-apt-repo.sh
43+
44+
- name: Prepare assets
45+
run: |
46+
cp -aRv dragonfly.repo pgp-key.public dragonfly.sources _site/
47+
rm -rf _site/deb/conf
48+
49+
- name: Generate Directory Listings
50+
run: python scripts/generate-index.py
51+
52+
deploy:
53+
needs: gen-site
54+
permissions:
55+
pages: write
56+
id-token: write
57+
58+
environment:
59+
name: github-pages
60+
url: ${{ steps.deployment.outputs.page_url }}
61+
62+
runs-on: ubuntu-latest
63+
steps:
64+
- name: Deploy to GitHub Pages
65+
id: deployment
66+
uses: actions/[email protected]

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://{placeholder}/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://{placeholder}/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://{placeholder}/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://{placeholder}/rpm/
4+
enabled=1
5+
gpgcheck=1
6+
gpgkey=https://{placeholder}/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: http://{placeholder}/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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import dataclasses
2+
import enum
3+
import os.path
4+
import time
5+
6+
import requests
7+
8+
RELEASE_URL = "https://api.github.com/repos/dragonflydb/dragonfly/releases"
9+
10+
11+
class AssetKind(enum.Enum):
12+
RPM = 1
13+
DEB = 2
14+
15+
16+
@dataclasses.dataclass
17+
class Package:
18+
kind: AssetKind
19+
download_url: str
20+
version: str
21+
filename: str
22+
arch: str
23+
24+
@staticmethod
25+
def from_url(url: str) -> "Package":
26+
tokens = url.split("/")
27+
filename = tokens[-1]
28+
kind = AssetKind.RPM if filename.endswith(".rpm") else AssetKind.DEB
29+
if kind == AssetKind.DEB:
30+
arch = filename.split(".")[0].split("_")[1]
31+
else:
32+
arch = filename.split(".")[1]
33+
return Package(
34+
kind=kind, download_url=url, version=tokens[-2], filename=filename, arch=arch
35+
)
36+
37+
def storage_path(self, root: str) -> str:
38+
match self.kind:
39+
case AssetKind.RPM:
40+
return os.path.join(root, "rpm", self.version)
41+
case AssetKind.DEB:
42+
return os.path.join("deb_tmp", self.arch, self.version)
43+
44+
45+
def collect_download_urls() -> list[Package]:
46+
packages = []
47+
# TODO retry logic
48+
response = requests.get(RELEASE_URL)
49+
releases = response.json()
50+
for release in releases[:5]:
51+
for asset in release["assets"]:
52+
if asset["name"].endswith(".rpm") or asset["name"].endswith(".deb"):
53+
packages.append(Package.from_url(asset["browser_download_url"]))
54+
return packages
55+
56+
57+
def download_packages(root: str, packages: list[Package]):
58+
# The debian repository building tool, reprepo, only supports a single package per version by default.
59+
# The ability to support multiple versions has been added but is not present in ubuntu-latest on
60+
# github action runners yet. So we only download one package, the latest, for ubuntu.
61+
# The rest of the scripts work on a set of packages, so that when the Limit parameter is supported,
62+
# we can remove this flag and start hosting more than the latest versions.
63+
# Another alternative would be to use the components feature of reprepo, but it would involve updating
64+
# the repository definition itself for each release, which is a bad experience for end users.
65+
deb_done = False
66+
for package in packages:
67+
if package.kind == AssetKind.DEB and deb_done:
68+
continue
69+
70+
print(f"Downloading {package.download_url}")
71+
path = package.storage_path(root)
72+
if not os.path.exists(path):
73+
os.makedirs(path)
74+
75+
target = os.path.join(path, package.filename)
76+
# TODO retry logic
77+
response = requests.get(package.download_url)
78+
with open(target, "wb") as f:
79+
f.write(response.content)
80+
print(f"Downloaded {package.download_url}")
81+
time.sleep(0.5)
82+
if package.kind == AssetKind.DEB:
83+
deb_done = True
84+
85+
86+
def main():
87+
packages = collect_download_urls()
88+
download_packages("_site", packages)
89+
90+
91+
if __name__ == "__main__":
92+
main()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
rm -rf deb_tmp

0 commit comments

Comments
 (0)