I’ve gotten to become quite the fan of CLI apps as of late. Maybe it’s the allure of the terminal of my childhood (starting with DOS on a 486/33 and my dad’s old Apple IIe). I was born a little too late for the Gen X Commodore64 era, but just in time to know more then just Windows 95. It was interesting era, back when dial up and 56k modems were king. I know most blog posts these days have intro fluff, to pad out word count for SEO, but this really is even why I still love the CLI when so many the younger folks these days only know GUI apps. Nothing makes me happier then to see Gen Z kids fire up the terminal, even for simple tasks. Man, wait till Gen Alpha finds out what a BBS is. “Grandpa computers” they’ll probably say ?. “GET OFF MY LAWN” ✊✊
Projects like CoolRetroTerm definitely have a warm place in my heart, for brining back the CLI love. I still prefer to do some of my blogging in Micro on my old Netbook, really let’s you concentrate on just writing. VSCode ZenMode and MarkText come close I guess?
flowchart LR Build_App --> GH_Actions --> ??? --> Profit!!!
Anyway, I digress…
So after writing my little CLI app Stampy I ran into a small problem, how to distribute it? I was at least smart enough to think a head an write it in GoLang (as much as I wanted to just build it in Python) to avoid the dreaded wrath of Python Packaging. One thing that has always stumped me was, how folks publish their nice CLI apps to fancy package management systems such as APT and YUM.
Normally to get your app built you would just do a simple go build . and boom, instant binary. As great as this is for local dev, it doesn’t do much good for cross platform compiles. There are some nice guides to show you how to do it, but… tl;dr for my ??. So I did some more digging, there had to be a nice tool… and sure as heck, there is GoReleaser!
After reading through some very well written documentation, I was able to do a quick local cross platform build, easy-peasy.
goreleaser --snapshot --clean
Getting builds to happen with GitHub releases was easy as well, as they have nice pre-written GH Actions!
Users can now just install my app with tools like eget (good) and stew (way better)!
While you can also go install github.com/xxx all this would be doing is clone the repo, build it locally, and put the bin in your $GOBIN folder. Not really the same as proper package management tooling, but does work in a pinch for folks who already have Go installed. Not really a option for the average user IMHO. ?
And not only that, GoReleaser offers packaging as well! So now you can easily craft DEBs and RPMs. I was one step closer to the scared apt-get install stampy. The only thing missing was how to create a APT repo. This last key part is not easy for sure. I spent a hour or so looking into how to self host this with GitHub Pages, and while it is doable, it was far easier to just use a free service like Packagecloud to handle the signing and repo hosting for the low low cost of $0 per month?.
You can see a example of the whole workflow HERE
I’ll also include a stripped down version of it here in a code block, for anybody stumbling across the blog post itself.
For a high level overview the GHA does the following:
name: Release on: pull_request: push: # run only against tags tags: - "*" permissions: contents: write packages: write issues: write jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version: stable - name: Release config run: | cat /tmp/goreleaser-github.yaml project_name: EXAMPLE version: 2 builds: - env: [CGO_ENABLED=0] goos: - linux goarch: - amd64 nfpms: - maintainer: YOUbindir: /usr/local/bin description: Copy formatted timestamp to system clipboard homepage: https://github.com/USERNAME/REPO license: MIT formats: - deb release: draft: false # If set to true, will not auto-publish the release. replace_existing_draft: true replace_existing_artifacts: true target_commitish: "{{ .Commit }}" prerelease: auto make_latest: true mode: replace include_meta: true EOF - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser # 'latest', 'nightly', or a semver version: "~> v2" args: release --clean --verbose --config /tmp/goreleaser-github.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload .deb artifact x86 uses: actions/upload-artifact@v3 with: name: deb-package path: dist/*amd64.deb pkgcld_amd64-deb: runs-on: ubuntu-latest needs: - goreleaser strategy: max-parallel: 3 matrix: distro: - debian/bookworm - ubuntu/noble - ubuntu/jammy steps: - name: Download .deb artifact uses: actions/download-artifact@v3 with: name: deb-package - name: Push package to packagecloud.io uses: computology/[email protected] with: package-name: ./*.deb packagecloud-username: USERNAME packagecloud-reponame: APP_NAME packagecloud-distro: ${{ matrix.distro }} packagecloud-token: ${{ secrets.PACKAGECLOUD_TOKEN }}
ℹ️ Important
You’ll want to make sure things like program structure and your go.mod file are properly setup, or you’re going to run into issues with publishing your app properly.
Side note: You can also distribute your app with Homebrew, but I didn’t bother due to the extra GH Actions complexity involving PAT secrets and the fact that I’m pretty well covered with Apt, Yum, and Stew… tasty! ?
This leads me to the big second thing when releasing a app. ?DOCUMENTATION? and the much neglected Readme.md?!
There are a few elements that I feel like any decent readme should have, as they will help your app stand out from all the apps with little to no documentation, or worse yet, bad documentation.
I highly recommend you follow this format for crafting your own readme! I’m a big fan of badges for flair, but I feel like having a little GIF demo really shows folks what it’s about, just like listing screenshots of your GUI apps. Using ASCIINEMA was easy enough, and they have a nice GIF converter as well to get everything looking just right.
? Tip
As a side note I did have CodeGPT write me some GoLang unit tests, which I know are normally painful to write. It’s a fantastic plugin if you’re on the JetBrains suite.
Similar to when I set out to learn how to publish Python apps, I’m glad to be able to say I feel like I can properly distribute any app I write in GoLang going forward. It’s a neat skill that I picked up, and with this blog post, I hope it can help others do the same! Cheers!
-Jelloeater
? Mastodon | ? Email | ? Comments | ☕ Buy me a coffee
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3