DEV Community

primenumsdev
primenumsdev

Posted on • Updated on

Publish Clojure library as private GitHub Package

Recently, I faced a problem, I was need to share a Clojure library internally, without exposing it to the public Clojars repo, and surprisingly I didn't find a good tutorial on how to do it. So I decided to describe my story here.

Since our company uses GitHub, I knew that GitHub provides a private package registry, but never used it before.

It appears that it is very simple solution to provide an internal maven package registry for sharing libraries within multiple projects, and accompanied with GitHub Actions you can easily setup an automated release pipeline.

Let's start.

Step 1 Create a new library

I use Leiningen to start a new project, so let's first create a test library:
$ lein new cljcloud/my-lib

It will create a new project based on default Leiningen template, with project.clj file in the root.

We need to modify project.clj and add these lines:

  :repositories [["github" {:url "https://maven.pkg.github.com/your-org-name/your-repo-name"
                            :username "private-token"
                            :password :env/GITHUB_TOKEN}]]
Enter fullscreen mode Exit fullscreen mode

Pls, update your-org-name and your-repo-name with real values accordingly.

It tells Leiningen to use GitHub private registry of your GitHub organization to push your package.

It's also possible to store packages at your personal account, you just need to use your GitHub username instead of your-org-name.

The line :password :env/GITHUB_TOKEN instructs Leiningen to read password from environment variable called GITHUB_TOKEN, we will get back to this latter.

I set :username "private-token" because we going to use GitHub private access token, and it doesn't matter what we specify for the username, it only needs to be a string.

Another thing that I changed is library version, I removed the SNAPSHOT from it, so that final project.clj looked like this:

(defproject cljcloud/my-lib "0.1.0"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :repositories [["github" {:url      "https://maven.pkg.github.com/cljcloud/my-lib"
                            :username "private-token"
                            :password :env/GITHUB_TOKEN}]]
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.3"]])

Enter fullscreen mode Exit fullscreen mode

Step 2 Set up GitHub Actions workflow to publish package

In order to create GitHub workflow, you need to make a new YAML file:

.github/workflows/publish-package.yml

with the following content:

name: publish package
# Manually trigger
on:
  workflow_dispatch:
jobs:
  publish:
    runs-on: ubuntu-latest
    # Add permissions for GITHUB_TOKEN
    permissions: 
      contents: read
      packages: write 
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-java@v2
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Install clojure tools
        uses: DeLaGuardo/setup-clojure@3.5
        with:
          lein: 'latest'
      - name: Configure GPG Key
        run: echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
        env:
          GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
      - name: Publish package
        run: cd my-lib && lein deploy github
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

A few more things, by default Leiningen, requires GPG key to be present when running lein deploy, you can opt-in to not use it with the option: :sign-releases false in project.clj

  :repositories [["github" {:url      "https://maven.pkg.github.com/cljcloud/my-lib"
                            :username "private-token"
                            :password :env/GITHUB_TOKEN
                            :sign-releases false}]]
Enter fullscreen mode Exit fullscreen mode

But it's not recommended, so I have created a new GPG key via:

$ gpg --gen-key

It will prompt for name, email, and passphrase and print out a key ID and details.
Then you can run this command to list your local keys:

$ gpg --list-keys

In GitHub Actions workflow we use secrets.GPG_SIGNING_KEY, which is our locally generated GPG signing key, exported as Base64 text.

To export your newly generated key, run:

$ gpg --export-secret-keys YOUR_ID_HERE | base64 > private.key

YOUR_ID_HERE - is a public key, long string with unique characters it will be printed after the key is generated or when you run list keys command.

private.key - is a file name that will be created in your current folder and will contain a Base64 representation of your private GPG key.

Next, you have to copy the private.key content and create a new secret in your GitHub repository with the name: GPG_SIGNING_KEY.

Note that we also pass environment variable: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}, here we use the secrets.GITHUB_TOKEN which is automatically provided by GitHub Actions context, and for which we set the permissions at the top:

    # Add permissions for GITHUB_TOKEN
    permissions: 
      contents: read
      packages: write 
Enter fullscreen mode Exit fullscreen mode

That's it. Now when you commit and push your code to the repo, you will be able to find and run a new GitHub Actions workflow that should build and release your library as a private package to GitHub private registry.

Step 3 Wait, so how do I use it now?

In order to use your package in your other projects, you have to add a similar line to project.clj:

  :repositories [["github" {:url      "https://maven.pkg.github.com/cljcloud/my-lib"
                            :username "private-token"
                            :password :env/GITHUB_TOKEN}]]
Enter fullscreen mode Exit fullscreen mode

and specify your library as usual Leiningen dependency, like this:

  :dependencies [[org.clojure/clojure "1.10.3"]
                 [cljcloud/my-lib "0.1.20"]]
Enter fullscreen mode Exit fullscreen mode

And when you run lein deps locally, Leiningen may fail, with 401 Unauthorized error, that's because you don't have a GITHUB_TOKEN environment variable, you can go to GitHub and generate one by following these simple instructions.

After that, you should set the environment variable via:

$ export GITHUB_TOKEN="your personal access token"

And retry with lein deps should work now.

Related links

Top comments (0)