This is still the header! Main site

Debian Packages for Everything!

02/25/2023

... in which /usr/local is no more. Also featuring large amounts of laziness with minor surprises.

The problem

You clone a git repo from GitHub. (... yes I know we're promoting Microsoft products here; we're not trying to solve All The Problems right now.) You compile it. And then... make install.

It then dumps everything into /usr/local. Which... kinda works... unless you want to remove it, figure out where it came from, install it on other machines, etc.

Ideally, you'd make a Debian package for it. You imagine making a Debian package for it. And... then... you realize that it's quite some work, so you just... don't.

checkinstall

Have you ever heard of checkinstall? It's a tool that does... almost what we'd want: it has you run make install, watches what happens, and packs the results into a Debian package.

It's not a lot harder than doing make install (or whatever command it takes to install) itself.

Of course, you still have to give it a package name, description, etc. on the command line:

~/some_git_repo/ $ checkinstall --pkgname some_git_repo \
      --pkgsource https://github.com/someone/some_git_repo \
      --pkgarch=amd64 \
      make install

This is still some work, so...

The shell script

... let's write a shell script that fetches metadata from GitHub & fills them out!

#!/bin/bash

# Get the URI of the local Git repository
URI=$(git remote get-url origin)

# Extract the owner and repository name from the URI
OWNER=$(echo $URI | cut -d "/" -f 4)
REPO=$(echo $URI | cut -d "/" -f 5 | cut -d "." -f 1)

# Retrieve information about the repository from the GitHub API
API_RESULT=$(curl https://api.github.com/repos/$OWNER/$REPO)

# Extract the name of the repository
NAME=$(echo $API_RESULT | jq -r '.name')

# Extract the description of the repository
echo $API_RESULT | jq -r '.description' >description-pak

# Extract the latest release tag of the repository
TAG=$(curl https://api.github.com/repos/$OWNER/$REPO/releases/latest | jq -r '.tag_name')

echo "Tag is: $TAG"

# Build the checkinstall command
echo checkinstall \
  --pkgname=$NAME \
  --pkgversion=$TAG \
  --pkgrelease=1 \
  --pkggroup=default \
  --pkgsource=$URI \
  --pkgarch=all \
  --maintainer=$OWNER \
  --install=no \
  --showinstall=yes \
  $@

We use the version hash as a "version" so that even if we come across the package later, we'll be able to tell where it came from.

Obviously, it's not a true, well-working Debian package; it doesn't have any dependencies added, it assumes that it works on any architecture, etc. Definitely not publishable right away... but... it's better than just dumping stuff in /usr/local!

... but... a shell script?

Here is a Python script doing the same thing! With some minor fixes (e.g. for missing tags):

#!/usr/bin/env python3

import subprocess
import json
import sys

# Get the URI of the local Git repository
uri = subprocess.check_output(["git", "remote", "get-url", "origin"]).decode("utf-8").strip()

# Extract the owner and repository name from the URI
owner, repo = uri.split("/")[-2:]
repo = repo.split(".")[0]

# Retrieve information about the repository from the GitHub API
api_result = json.loads(subprocess.check_output(["curl", "https://api.github.com/repos/{}/{}".format(owner, repo)]).decode("utf-8"))

# Extract the name of the repository
name = api_result["name"]

# Extract the description of the repository
with open("description-pak", "w") as f:
    f.write(api_result["description"])

# Extract the latest release tag of the repository
try:
    tag = json.loads(subprocess.check_output(["curl", "https://api.github.com/repos/{}/{}/releases/latest".format(owner, repo)]).decode("utf-8"))["tag_name"]
except:
    tag = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").strip()[:10]

print("Tag is: {}".format(tag))

# Build the checkinstall command
print("echo checkinstall \\\n  --pkgname={} \\\n  --pkgversion={} \\\n  --pkgrelease=1 \\\n  --pkggroup=default \\\n  --pkgsource={} \\\n  --pkgarch=all \\\n  --maintainer={} \\\n  --install=no \\\n  --showinstall=yes \\\n  {}".format(name, tag, uri, owner, " ".join(sys.argv[1:])))
        

... comments welcome, either in email or on the (eventual) Mastodon post on Fosstodon.

OK so why both?

As in... if we have a shell script... why rewrite it in Python? Also... isn't this code... a little bit... ugly?

Well, actually: the fun part about all this code is who it was written by. It was... ChatGPT, mostly. And then OpenAI's code model turned it into Python.

In fact, checkinstall itself was recommended by ChatGPT! And it also knows how to use it. Along with GitHub APIs. I just asked:

how do I get the author, name, description etc. from a github repo?

followed by

write a bash script that, based on the results of the above API, fills out a call to checkinstall (e.g. --pkgname, --pkgversion etc.)

It did mess up quoting $DESCRIPTION first, and made up some arguments to checkinstall that don't actually exist, but... it knows how to use it in general, and the resulting shell script is an excellent starting point (it's pretty much what you see above).

They're really nice tools. Also, in case you weren't worried about where this is going... maybe it's time to start doing so.