01 Jan 2026
24 min read
As part of developing Git-Mastery, I had to publish the app across as many operating systems as possible for students to download it.
Debian was a target operating system as students may be using Debian/Ubuntu/some Debian-based Linux distro as their daily driver or they may be using Ubuntu for Windows Subsystem for Linux (WSL) on Windows.
Surprisingly, the process of packaging the app for Debian was way more involved than I had originally anticipated, taking over a day of experimentation to integrate with GitHub Actions.
While there are several nice guides covering individual steps of this process, I found myself having to piece them together as I was working on this that, and I had eventually compiled a set of pretty comprehensive notes on my findings.
I wanted to use this opportunity to consolidate my process and hopefully remove the awkwardness on figuring out how to package and publish applications on Debian!
I am not claiming to be subject matter expert, and I may have some inefficient steps below!
If you spot any, feel free to drop me an email and I will improve it!
This guide hopes to cover the following core topics:
.deb file)reprepo and GitHub PagesBefore packaging your application, you first need to create an ELF binary of your program (which I will refer to as a “binary” or “executable” from here on out). I will use the Git-Mastery app binary for reference.
If you wish to follow along, you can get v7.1.2 of the binary on ARM64 here.
The Git-Mastery app uses PyInstaller to bundle the CLI into a single executable, and it uses the Ubuntu GitHub Actions image to generate this executable across architectures.
If you are not using GitHub Actions, the next easiest option is to use a Virtual Machine (VM) to build it. You can also use the VM to run through the individual steps of packaging, so I highly recommend setting one up now. I have included my set-up for a Debian ARM64 VM on MacOS Apple Silicon in the appendix below.
Finally, you will need to ensure that you have the devscripts and debhelper-compat packages installed in your environment.
In Debian, you can install it using apt:
sudo apt-get install devscripts build-essential debhelper-compat
The official introduction to packaging on Debian provides some high-level details about the packaging process that I will (shamelessly) copy over to explain some fundamental concepts.
.tar.gz or .tgz) that contains the software that has been written (i.e. it contains the executable)In our context, the upstream tarball simply contains the executable that you intend to publish and the binary package is the resulting .deb file that is consumed by users, so these should be relatively straightforward to understand.
Instead, the focus of this guide is the construction of the source package, allowing tools like dpkg-buildpackage (man) to build and create the binary package.
The simplest source package consists of:
debian/ directory containing the changes made to the upstream source and all files required for the creation of the binary package.dsc) which contains metadata about the above filesThis guide will focus on these three as the primary packaging workflow.
Given that you are packaging your own application, you will first create the upstream tarball.
Start by creating a folder for your executable. I chose to name mine using the following convention <name>-<version>-<architecture>.
mkdir gitmastery-7.1.2-arm64/
Then, copy the executable into this folder.
cp gitmastery-7.1.2-linux-arm64 gitmastery-7.1.2-arm64/
Finally, generate the tarball following naming convention: <name>_<version>.orig.tar.gz.
tar -czf gitmastery_7.1.2.orig.tar.gz gitmastery-7.1.2-arm64/gitmastery-7.1.2-linux-arm64
debian/ directoryNavigate into the folder containing your executable.
cd gitmastery-7.1.2-arm64/
Then, create a sub-folder named debian.
mkdir debian
This is where all the configuration about packaging your application will be held.
There are several files you will need within debian/ to tell the packaging tool how to create your package:
debian/changelog
The Debian Changelog is where you (as the maintainer) log the changes to a package. If you are packaging the application using GitHub Actions, you could lift it from the latest commit message on the repository.
git show v7.1.2 --no-patch --pretty=format:%s
Otherwise, you can specify it directly when generating the file using dch (man).
dch --create -v 7.1.2-1 -u low --package gitmastery "Changed things"
--create : generates a new debian/changelog file (note that it expects the debian/ sub-folder so run this command outside of the debian/ sub-folder)-v 7.1.2-1: specifying the version of the application listed in the changelog; -1 indicates the release number of the package-u low: indicates the urgency of the changelog entry; tied to the Debian “testing” distribution; low implies that the package may transition to “testing” in 10 days--package: name of the packagedebian/control
The control file describes the source and binary package, providing information about the name, maintainers, and build dependencies.
You can generate this file using the following template:
Source: gitmastery
Maintainer: Jiahao, Woo <woojiahao1234@gmail.com>
Section: misc
Priority: optional
Standards-Version: 4.7.0
Build-Depends: debhelper-compat (= 13)
Package: gitmastery
Architecture: arm64
Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 (>= 2.35), python3
Description: execute Git-Mastery
gitmastery is a Git learning tool built by the National University of Singapore School of Computing
It is broken up into two parts: source and binary.
In the source section:
Source: source package nameMaintainer: name and email of the person responsible for the package; follows the $NAME <$EMAIL> formatPriority: priority of the package to indicate whether the package is important to the standard functioning system; one of required, important, standard, optionalBuild-Depends: list of packages that need to be installed to build the package (note this does not imply that the package can run with these dependencies, just that it can be built)In the binary section:
Package: name of the binary package which might differ from the source package name (in our case it doesn’t)Architecture: specifies the computer architecture where the binary package is expected to work, including arm64 and amd64 (the two primary architectures I chose to focus on)Depends: list of packages that must be installed for the program in the binary package to work; ${shlibs:Depends} are the packages that contain built shared libraries and executables and ${misc:Depends} contain the packages like debhelper (here we also list libc and python3 as extra dependencies)Description: full description of the binary package; first line is a summary and the rest of the lines are a longer descriptiondebian/copyright
The name is pretty self-explanatory - this contains the copyright information about the package.
You are able to leave this blank, but when packaging the Git-Mastery app, I had opted to instead copy over the LICENSE of the application, which happens to be the MIT license.
debian/rules
These provide the rules for installation, including how/where the application should be installed.
The file format is that of a Makefile.
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_install:
install -D -m 0755 gitmastery-7.1.2-linux-arm64 debian/gitmastery/usr/bin/gitmastery
While the official installation guide starts with a rather sparse file (that they build upon):
#!/usr/bin/make -f
%:
dh $@
I have found - through experimentation - that the binary package will not work unless you provide the override_dh_auto_install step (more information) where you need to specify how and where the application is getting installed using the install (man) command.
-D: create all leading components of the destination except the last (i.e. create all sub-folders) and then copy the source to the destination-m 0755: set the permission modedebian/gitmastery/usr/bin/gitmastery: the leading debian/gitmastery is necessary because it indicates the fakeroot before packaging; the ultimate path upon installation is /usr/bin/gitmastery (this can be verified upon installation).debian/$NAME.dirs
This serves as a directory declaration file used by the dh_installdirs (man).
For the Git-Mastery, I have left it blank, but if you create your file as debian/test.dirs, and specify a path usr/bin, it would then create debian/test/usr/bin.
debian/source/format
This specifies the version number for the format of the source package.
3.0 (quilt)
quilt (docs) is used to manage patches to Debian packages.
debian/source/include-binaries
Given that we’re directly installing the binary, we need to provide its path in this file.
gitmastery-7.1.2-linux-arm64
.deb fileThat is all the files we really need to package the application.
With this, we can finally package the application:
dpkg-buildpackage -us -uc -a arm64
You want to run this command in the root of your directory, not within debian/!
-us: do not sign the source package-uc: do not sign the .buildinfo and .changes files-a arm74: specify the architectureThis produces the .deb file with the name <NAME>_<VERSION>-1_<ARCHITECTURE>.deb.
You can verify the package using sudo dpkg -i <package>. Once installed, you can also check to confirm where the binary was installed using which gitmastery.
Now, if all you wanted to do was to create a one-off .deb and share it with someone directly, you’re done! Congratulations 🎉!
But if you want to publish this .deb to a Debian repository for the apt package manager or automate the entire process, read on!
repreproreprepro (repository) is used to create a Debian repository, but it also works well to host your package so that it is discoverable by through the apt package manager via third-party repositories.
As a Debian repository is just a set of files organized in a special directory tree with different infrastructure files, it can be hosted through a static site platform like GitHub Pages. But before we get ahead of ourselves with hosting, let’s figure out how to first generate the necessary files.
I had initially followed this wonderful guide on the DigitalOcean community tutorial blog on “How to Use Reprepro for a Secure Package Repository on Ubuntu 14.04” but I have adapted it for my (and hopefully your) use case!
Install reprepro on your machine.
apt-get update
apt-get install reprepro
Then, create a folder to store the files:
mkdir repo && cd repo
With this, we can start the process of setting up the Debian repository.
Create a GPG key. If you already have a GPG key, you might find this discussion useful in deciding if you should generate another one just for publishing your package, or you can consider using a subkey instead.
gpg --gen-key
List the key.
gpg --list-secret-key
The returned value will include your key which we will refer to from here on out as <key> .
[keyboxd]
---------
sec ed25519 2025-07-04 [SC] [expires: 2028-07-03]
<key>
uid [ultimate] Jiahao, Woo <woojiahao1234@gmail.com>
ssb cv25519 2025-07-04 [E] [expires: 2028-07-03]
Then, within repo/, create a conf/ folder.
mkdir conf
Create a repo/conf/distributions file:
Origin: GitHub
Label: GitHub Git-Mastery
Suite: stable
Codename: any
Components: main
Architectures: arm64
SignWith: <key>
Origin / Label / Description: free-form text displayed to the user or used for pinningSuite: stable or testingArchitectures: space-separated list of Debian architecture names; for now, we’re only publishing to the arm64 architecture (we will explore how to support multi-architecture packages later)SignWith: signing fingerprint from the PGP certificate above using <key>Then, you can start to add packages to your repository. For the Git-Mastery app, because we store the generated .deb packages as release artifacts, we need an additional step to download the files onto the local machine, but if you have the .deb package on hand, you can just use it directly.
curl -L https://github.com/git-mastery/app/releases/download/v7.1.2/gitmastery_7.1.2-1_arm64.deb -o gitmastery_7.1.2-1_arm64.deb
With this package, use debsigs (man) to crytographically sign it using the GPG key created earlier:
debsigs -v \
--gpgopts="--batch --no-tty --pinentry-mode=loopback" \
--sign=origin \
--default-key="<key>" \
gitmastery_7.1.2-1_arm64.deb
--gpgopts
--batch: non-interactive mode--no-tty: don’t use the terminal--pinentry-mode=loopback: allow passphrase input programmatically--sign=origin: creates an origin signature that indicates that it is the official signature of the organization that distributes the package (although you might use maint instead)--default-key="<key>": uses the GPG key createdThen, add the signed .deb to the Debian repository using reprepro:
reprepro -Vb repo includedeb any \
"gitmastery_7.1.2-1_arm64.deb"
-Vb repo: verbose and use the repo folder as the base directoryincludedeb: command to insert the .deb into the repository and update the metadataany: target distributionThis creates the necessary files for the Debian repository to host and publish the .deb package you had just included.
Then, copy your GPG public key over into repo/ along with creating an empty index.html file.
You can copy your GPG public key to a file named pubkey.gpg via
gpg --output pubkey.gpg --armor --export <email>As mentioned before, a Debian repository is just a set of files with some set of special infrastructure files. So you can host it on a static site hosting platform like GitHub Pages.
To do so, initialize repo as a Git repository.
git init
Then, create a GitHub repository and add it as a remote to the local Git repository.
git remote add origin https://github.com/woojiahao/demo-apt-repo.git
Add all of the generated files in a commit.
git add . && git commit -m "Setup Debian repository"
Push the changes to the repository.
git push -u origin main
Finally, enable GitHub Pages on the repository and GitHub should handle the rest.
You should then be able to install the package from the repository.
# Set-up to recognize the Debian repository as a trusted source
echo "deb [trusted=yes] https://git-mastery.github.io/gitmastery-apt-repo any main" | \
sudo tee /etc/apt/sources.list.d/gitmastery.list > /dev/null
sudo apt install software-properties-common
sudo add-apt-repository "deb https://git-mastery.github.io/gitmastery-apt-repo any main"
# Installing the package
sudo apt update
sudo apt-get install gitmastery
Remember to substitute your username and repository name accordingly!
Amazing, we are able to now install the package from the new Debian repository, so users can avoid directly downloading your .deb package just to try your package.
This just leaves two big questions:
Supporting multiple architectures is actually a lot simpler once you understand the packaging and publishing steps.
To create package your application for multiple architectures, you will need to:
Architecture field in the debian/control filedebian/rules filedebian/source/include-binaries filedpkg-buildpackage command, pass the architecture to the -a flagThese changes should generate a new .deb file for the new architecture.
With this new .deb file and you can sign it and add it to the Debian repository.
Architectures field in repo/conf/distributions field.deb file with debsigsreprepro command again (with no changes!)This should generate the necessary files for your Debian repository. Remember to commit these changes and push them, and let GitHub Pages do the rest.
Now, depending on your user’s architecture, the Debian repository should be able to intelligently download the appropriate signed .deb package for that architecture.
Let’s now wrap everything up and see how we can automate this entire process using GitHub Actions so that it triggers when a new tag is pushed.
For this, you will need two GitHub repositories:
You should already be working with the first, but you may need to create the second (note that if you were following along, you might need a new repository for this portion).
If you are new to GitHub Actions and need an introduction, I have given a talk on CI/CD with GitHub Actions along with a guide on the contents covered!
Create a new workflow file in your source repository under .github/workflows and you can name it whatever you want. I chose to name the one for Git-Mastery publish.yml (workflow file).
To automatically package your application on tag, you can use the workflow trigger action of push:
name: Build and release Git-Mastery CLI
on:
workflow_dispatch:
push:
tags:
- "v*.*.*"
Then, you will need three jobs to run in sequence:
.deb packages per architectureBuilding the Linux executable for both AMD64 and ARM64
To build the Linux executables for both AMD64 and ARM64, we can use a matrix in GitHub Actions to run the same job on both an amd64 and arm64 runner.
Then, the steps are relatively straightforward:
requirements.txt)ARCHITECTURE and FILENAME from the matrix informationpyinstaller for Git-Mastery)We use a workflow artifact because it can be shared across jobs within the same workflow, reducing the need for us to directly access the release files.
jobs:
linux-build:
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Get binary name
id: binary-name
env:
OS_VERSION: ${{ matrix.os }}
run: |
if [ $OS_VERSION = "ubuntu-latest" ]; then
ARCHITECTURE=amd64
else
ARCHITECTURE=arm64
fi
FILENAME=gitmastery-${GITHUB_REF_NAME#v}-linux-$ARCHITECTURE
echo "binary=$FILENAME" >> $GITHUB_OUTPUT
- name: Build binary
env:
BINARY_NAME: ${{ steps.binary-name.outputs.binary }}
run: |
echo "__version__ = \"${GITHUB_REF_NAME}\"" > app/version.py
pyinstaller --onefile main.py --name $BINARY_NAME
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: dist/${{ steps.binary-name.outputs.binary }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish package as artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.binary-name.outputs.binary }}
path: dist/${{ steps.binary-name.outputs.binary }}
Building the .deb packages per architecture
Then, we declare the next job that requires the linux-build job from above to start. We use a matrix again to build the .deb on each architecture.
These steps follow how we regularly package the .deb , except this time, we care a little more about where we store the files for publishing:
ARCHITECTURE and FILENAME from the matrix informationdebian/.deb using dpkg-buildpackage.deb to the GitHub releaseNote that when we generate the debian/rules file this way, we need to preserve all the tabs and newlines accordingly, so we use echo -e.
We also do not need to publish any workflow artifacts as there are no more downstream jobs that will use these files.
jobs:
linux-build:
# ...
debian-build:
needs: linux-build
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source
uses: actions/checkout@v3
with:
path: "app"
- name: Extract variables
env:
ARCHITECTURE: ${{ matrix.architecture }}
OS_VERSION: ${{ matrix.os }}
run: |
if [ $OS_VERSION = "ubuntu-latest" ]; then
ARCHITECTURE=amd64
else
ARCHITECTURE=arm64
fi
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
echo "ARCHITECTURE=${ARCHITECTURE}" >> $GITHUB_ENV
# Get the tag's commit message
cd app/
CHANGELOG_MESSAGE=$(git show ${GITHUB_REF_NAME} --no-patch --pretty=format:%s)
echo "CHANGELOG_MESSAGE=${CHANGELOG_MESSAGE}" >> $GITHUB_ENV
- name: Install Debian packaging tools
run: |
sudo apt-get install devscripts build-essential debhelper-compat
- name: Create folder structure for ${{ env.ARCHITECTURE }} distribution
run: |
mkdir gitmastery-${VERSION}-${ARCHITECTURE}
- name: Download ${{ env.ARCHITECTURE }} binaries from artifacts
uses: actions/download-artifact@v4
with:
name: gitmastery-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }}
path: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/
- name: Create upstream tarball .orig.tar.gz
run: |
# Create .orig.tar.gz file
tar -czf gitmastery_${VERSION}.orig.tar.gz gitmastery-${VERSION}-${ARCHITECTURE}/gitmastery-${VERSION}-linux-${ARCHITECTURE}
- name: Generate Debian packaging files
working-directory: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }}
env:
EMAIL: woojiahao1234@gmail.com
NAME: Jiahao, Woo
run: |
file gitmastery-${VERSION}-linux-${ARCHITECTURE}
# Create the debian folder
mkdir debian
# Generate the changelog
dch --create -v ${VERSION}-1 -u low --package gitmastery "$CHANGELOG_MESSAGE"
# Create the control file
echo """Source: gitmastery
Maintainer: $NAME <$EMAIL>
Section: misc
Priority: optional
Standards-Version: 4.7.0
Build-Depends: debhelper-compat (= 13)
Package: gitmastery
Architecture: ${ARCHITECTURE}
Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 (>= 2.35), python3
Description: execute Git-Mastery
gitmastery is a Git learning tool built by the National University of Singapore School of Computing
""" > debian/control
# Copy over the MIT license from the main app to this release
cat ../app/LICENSE > debian/copyright
mkdir debian/source
echo "3.0 (quilt)" > debian/source/format
# Provide the rules for installation, using -e to preserve the tab character as per:
# https://wiki.debian.org/Packaging/Intro
echo -e $"""#!/usr/bin/make -f
%:
\tdh \$@
\n
override_dh_auto_install:
\tinstall -D -m 0755 gitmastery-${VERSION}-linux-${ARCHITECTURE} debian/gitmastery/usr/bin/gitmastery
""" > debian/rules
echo """usr/bin
""" > debian/gitmastery.dirs
mkdir -p debian/source
echo """gitmastery-${VERSION}-linux-${ARCHITECTURE}
""" > debian/source/include-binaries
# Build the package
dpkg-buildpackage -us -uc -a ${ARCHITECTURE}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: gitmastery_${{ env.VERSION }}-1_${{ env.ARCHITECTURE }}.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Triggering the publish job
The final step is to trigger the publish job, taking the generated .deb packages (stored in the source repository’s GitHub releases) and uploading them to GitHub Pages on the Debian repository.
For this step, we will simply call the workflow file from the other GitHub repository. We pass the github.ref_name through as a variable since we will need it for versioning the published files.
jobs:
linux-build:
# ...
debian-build:
# ...
debian-publish-apt:
needs: debian-build
permissions: write-all
uses: git-mastery/gitmastery-apt-repo/.github/workflows/debian-apt-repo.yml@main
with:
version: ${{ github.ref_name }}
secrets: inherit
For the entire workflow file, refer to the appendix below.
Now, in the Debian repository, create a new workflow file under .github/workflows. For Git-Mastery, I opted to call it debian-apt-repo.yml (workflow file).
There are some additional set-up steps required to work with your generated GPG key:
Add the GPG private key to the GitHub repository’s secrets, you can retrieve your private key using gpg --armor --export-secret-key
For simplicity, the GPG should not require a passphrase, otherwise it will fail when this workflow runs.
Add the GPG public key (<key>) to the GitHub repository’s variables
Add a blank index.html to the repository
Add the GPG public key file to the repository
Set your GitHub Pages to publish from the gh-pages branch instead
Then, the rest of the steps are similar to what we have described before.
The main difference here is that we iterate over both architecture types to support multi-architecture publishing.
We also use a GitHub workflow to automatically publish the new files under a separate branch gh-pages instead, treating the main branch as just a store for the necessary files.
name: Build & Publish Debian Package
on:
workflow_call:
inputs:
version:
required: true
type: string
jobs:
build-and-publish:
permissions:
pages: write
contents: write
environment: Main
runs-on: ubuntu-latest
env:
DISTRIBUTION: any
COMPONENT: main
RELEASE: 1
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
KEY_ID: ${{ vars.KEY_ID }}
VERSION: ${{ inputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
repository: git-mastery/gitmastery-apt-repo
token: ${{ secrets.GITHUB_TOKEN }}
path: aptrepo
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y reprepro dpkg-dev curl jq gnupg debsigs
- name: Import GPG key
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
echo "$KEY_ID:6:" | gpg --batch --import-ownertrust --pinentry-mode=loopback
- name: Setup repository structure
working-directory: aptrepo
run: |
mkdir -p repo/conf
echo "Origin: GitHub" >repo/conf/distributions
echo "Label: GitHub Git-Mastery" >>repo/conf/distributions
echo "Suite: stable" >>repo/conf/distributions
echo "Codename: ${DISTRIBUTION}" >>repo/conf/distributions
echo "Components: ${COMPONENT}" >>repo/conf/distributions
echo "Architectures: arm64 amd64" >>repo/conf/distributions
echo "SignWith: $KEY_ID" >>repo/conf/distributions
cp pubkey.gpg repo/pubkey.gpg
cp index.html repo/
- name: Add .deb
working-directory: aptrepo
run: |
for ARCHITECTURE in "arm64" "amd64"; do
echo "Publishing for $ARCHITECTURE"
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/git-mastery/app/releases/tags/${VERSION} | jq -r --arg suffix "${ARCHITECTURE}.deb" '.assets[] | select(.name | endswith($suffix)) | .browser_download_url')
TRIMMED_VERSION=${VERSION#v}
FILENAME="gitmastery_${TRIMMED_VERSION}-1_${ARCHITECTURE}.deb"
TRIMMED_VERSION=${VERSION#v}
curl -L "$DOWNLOAD_URL" -o $FILENAME
debsigs -v --gpgopts="--batch --no-tty --pinentry-mode=loopback" --sign=origin --default-key="$KEY_ID" $FILENAME
reprepro -Vb repo includedeb ${DISTRIBUTION} "gitmastery_${TRIMMED_VERSION}-${RELEASE}_${ARCHITECTURE}.deb"
done
- name: Deploy APT repository to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.ORG_PAT }}
publish_dir: ./aptrepo/repo
external_repository: git-mastery/gitmastery-apt-repo
Voilà! You have now created a fully automated package and publishing pipeline!
Visualizing the pipeline will look like this:
Great job getting this far! Packaging and publishing on Debian can seem very confusing because of the little amount of comprehensive documentation there is, but I hope that this guide can help to coalesce all of the available documentation into something practical and usable!
I could have used off-the-shelf solutions for this, but I liked the process of understanding how to package and publish from scratch!
These were some of the resources I had relied on when I was first starting out:
Thankfully, Debian has an ARM64 image for you to virtualize on MacOS Apple Silicon (which is what I used to experiment with packaging).
The ISO isn’t immediately obvious because it’s not (to my knowledge) listed on the same page where you regularly download ISOs. Instead, you can find this image here.
I used VirtualBox because it’s very simple to get started. Create a VM and mount the ISO. I picked XFCE as the desktop environment because I wanted to interactively download the releases from GitHub.
You can then use this VM to test the commands and try to load the packaged application!
name: Build and release Git-Mastery CLI
on:
workflow_dispatch:
push:
tags:
- "v*.*.*"
permissions:
contents: write
pull-requests: write
packages: read
issues: read
jobs:
linux-build:
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Get binary name
id: binary-name
env:
OS_VERSION: ${{ matrix.os }}
run: |
if [ $OS_VERSION = "ubuntu-latest" ]; then
ARCHITECTURE=amd64
else
ARCHITECTURE=arm64
fi
FILENAME=gitmastery-${GITHUB_REF_NAME#v}-linux-$ARCHITECTURE
echo "binary=$FILENAME" >> $GITHUB_OUTPUT
- name: Build binary
env:
BINARY_NAME: ${{ steps.binary-name.outputs.binary }}
run: |
echo "__version__ = \"${GITHUB_REF_NAME}\"" > app/version.py
pyinstaller --onefile main.py --name $BINARY_NAME
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: dist/${{ steps.binary-name.outputs.binary }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish package as artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.binary-name.outputs.binary }}
path: dist/${{ steps.binary-name.outputs.binary }}
debian-build:
# We support both ARM64 and AMD64 since Debian comes with support for
# these two out of the box
needs: linux-build
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source
uses: actions/checkout@v3
with:
path: "app"
- name: Extract variables
env:
ARCHITECTURE: ${{ matrix.architecture }}
OS_VERSION: ${{ matrix.os }}
run: |
if [ $OS_VERSION = "ubuntu-latest" ]; then
ARCHITECTURE=amd64
else
ARCHITECTURE=arm64
fi
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
echo "ARCHITECTURE=${ARCHITECTURE}" >> $GITHUB_ENV
# Get the tag's commit message
cd app/
CHANGELOG_MESSAGE=$(git show ${GITHUB_REF_NAME} --no-patch --pretty=format:%s)
echo "CHANGELOG_MESSAGE=${CHANGELOG_MESSAGE}" >> $GITHUB_ENV
- name: Install Debian packaging tools
run: |
sudo apt-get install devscripts build-essential debhelper-compat
- name: Create folder structure for ${{ env.ARCHITECTURE }} distribution
run: |
mkdir gitmastery-${VERSION}-${ARCHITECTURE}
- name: Download ${{ env.ARCHITECTURE }} binaries from artifacts
uses: actions/download-artifact@v4
with:
name: gitmastery-${{ env.VERSION }}-linux-${{ env.ARCHITECTURE }}
path: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }}/
- name: Create upstream tarball .orig.tar.gz
run: |
# Create .orig.tar.gz file
tar -czf gitmastery_${VERSION}.orig.tar.gz gitmastery-${VERSION}-${ARCHITECTURE}/gitmastery-${VERSION}-linux-${ARCHITECTURE}
- name: Generate Debian packaging files
working-directory: gitmastery-${{ env.VERSION }}-${{ env.ARCHITECTURE }}
# TODO: Update to something agnostic
env:
EMAIL: woojiahao1234@gmail.com
NAME: Jiahao, Woo
run: |
file gitmastery-${VERSION}-linux-${ARCHITECTURE}
# Create the debian folder
mkdir debian
# Generate the changelog
# TODO: Maybe detect if major version change, then make it urgent
dch --create -v ${VERSION}-1 -u low --package gitmastery "$CHANGELOG_MESSAGE"
# Create the control file
# TODO: Maybe detect if major version change, then make it mandatory
echo """Source: gitmastery
Maintainer: $NAME <$EMAIL>
Section: misc
Priority: optional
Standards-Version: 4.7.0
Build-Depends: debhelper-compat (= 13)
Package: gitmastery
Architecture: ${ARCHITECTURE}
Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 (>= 2.35), python3
Description: execute Git-Mastery
gitmastery is a Git learning tool built by the National University of Singapore School of Computing
""" > debian/control
# Copy over the MIT license from the main app to this release
cat ../app/LICENSE > debian/copyright
mkdir debian/source
echo "3.0 (quilt)" > debian/source/format
# Provide the rules for installation, using -e to preserve the tab character as per:
# https://wiki.debian.org/Packaging/Intro
# $(DESTDIR) resolves to debian/binarypackage/ as seen in
# https://www.debian.org/doc/manuals/debmake-doc/ch06.en.html#ftn.idp1797
echo -e $"""#!/usr/bin/make -f
%:
\tdh \$@
\n
override_dh_auto_install:
\tinstall -D -m 0755 gitmastery-${VERSION}-linux-${ARCHITECTURE} debian/gitmastery/usr/bin/gitmastery
""" > debian/rules
echo """usr/bin
""" > debian/gitmastery.dirs
mkdir -p debian/source
echo """gitmastery-${VERSION}-linux-${ARCHITECTURE}
""" > debian/source/include-binaries
cat debian/rules
# Build the package
dpkg-buildpackage -us -uc -a ${ARCHITECTURE}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: gitmastery_${{ env.VERSION }}-1_${{ env.ARCHITECTURE }}.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
debian-publish-apt:
needs: debian-build
permissions: write-all
uses: git-mastery/gitmastery-apt-repo/.github/workflows/debian-apt-repo.yml@main
with:
version: ${{ github.ref_name }}
secrets: inherit Enjoyed reading?
Consider subscribing to my RSS feed or reaching out to me through email!
I am open to work!
I have graduated from university (as of December 2025) and I am actively
looking for entry-level software engineering positions!
I have interned
at places like Citadel, Stripe, and Palantir, and deeply enjoy solving
user-facing, developer tooling, and infrastructure problems! (resume)
I am a Singapore Citizen so I have access to both the H-1B1 visa for the US and HPI visa for the UK, so visa sponsorship to the US and UK will not be a problem!
If I
sound like a fit for your organization, please reach out to me via email and let's chat!