Skip to content

Instantly share code, notes, and snippets.

@0xernesto
Created January 3, 2025 06:30
Show Gist options
  • Save 0xernesto/fda89508b5f73463787d102e1739dc0b to your computer and use it in GitHub Desktop.
Save 0xernesto/fda89508b5f73463787d102e1739dc0b to your computer and use it in GitHub Desktop.
How to set up an automated release flow using semantic-release and Github Actions, with signed/verified commits.

Semantic Release bot flow (signed/verified commits)

Overview

This semantic-release publishing flow consists of using a bot for publishing releases with signed/verified commits. This setup requires creating a Github App, a user account for the bot to use, and adding the following Github Actions secrets to the repo it is being used in:

  • NPM_TOKEN
    • Refer to “Generating an NPM token” section.
  • MY_RELEASER_USER_EMAIL
    • Refer to the “Create a new Github user account for the bot” section
  • MY_RELEASER_GPG_KEY_ID
    • Refer to “Generating a GPG key” section.
  • MY_RELEASER_BASE64_GPG_KEY
    • Refer to “Generating a GPG key” section.
  • MY_RELEASER_GPG_PASSPHRASE
    • Refer to “Generating a GPG key” section.
  • MY_RELEASER_ID
    • Refer to “Creating a GitHub App” section
  • MY_RELEASER_PRIVATE_KEY
    • Refer to “Creating a GitHub App” section

Create a new Github user account for the bot

Username: my-releaser

Email: [email protected]

  • Ensure this is an email that is not used on any other Github account
  • Ensure you have access to this email so that it can be verified

Once the account is created:

  1. Go to the account’s settings
  2. Click “Emails”
  3. Check “Keep my email addressees private”
  4. Copy the account’s no-reply email Example: [email protected]
  5. Create a secret in the Github repo you will use this bot in called MY_RELEASER_USER_EMAIL
    • Paste the no-reply email from the previous step

Generating an NPM token

  1. Go to npmjs.com and create an account, if necessary.
  2. Click “Access Tokens” from the menu.
  3. Click “Generate New Token”
  4. Click “Granular Access Token”
  5. Give the token a descriptive name that differentiates it from the rest of the tokens in your account, such as “My Releaser Bot”
  6. Provide a description, such as “Token for My Releaser Bot”
  7. Provide an expiration date, such as “12/31/2100”
  8. Set permissions to “Read and write”
  9. Select the package
  10. Click “Generate token”
  11. Copy the “Token”
    • ⚠️ This token will never be visible on the NPM website again.
  12. Create a secret in the Github repo this will be used and name it NPM_TOKEN
    • Paste the token value from the previous step

Generating a GPG Key

Since the bot will need to make commits to bump the package version and update the change-log after a new release, it will require a user account with a GPG key so that the commits it makes are signed/verified.

1. Get a base64-encoded GPG key

To create a new GPG key, run the following:

gpg --full-generate-key
  • (1) RSA and RSA
  • (3072) 3072 bits key size
  • (0) key does not expire
  • Real name: My Releaser
  • Email address: [email protected]
    • This should be the no-reply email obtained in the “Create a new Github user account for the bot” section
  • Comment: GPG key used by the My Releaser bot to sign commits.
  • Passphrase: ______
    • Create a passphrase that the bot can use

To use an existing GPG key, run the following to list them:

gpg --list-secret-keys --keyid-format=long

# Here is an example output
[keyboxd]
---------
sec   rsa3072/3AA5C34371567BD2 2024-07-27 [SC]
      0A9EE57EE119572FC9FA8E7C0300EBBCEFFB188D
uid                 [ultimate] John Doe <[email protected]>
ssb   rsa3072/908B69F28954074D 2024-07-27 [E]

# The 3AA5C34371567BD2 value should be saved as MY_RELEASER_GPG_KEY_ID

Run the following to display the base64-encoded secret key:

# The output of this command should be saved as MY_RELEASER_BASE64_GPG_KEY
gpg --export-secret-keys 3AA5C34371567BD2 | base64

2. Create secrets in the Github repo

  • Create a new secret called MY_RELEASER_GPG_KEY_ID
    • Paste the GPG key ID from step 1
  • Create a new secret called MY_RELEASER_BASE64_GPG_KEY
    • Paste the base64-encoded secret key from step 1
  • Create a new secret called MY_RELEASER_GPG_PASSPHRASE
    • Paste the passphrase associated with the GPG key
    • This is the passphrase that was set when creating the GPG key.

3. Ensure the decoded GPG key is uploaded to the bot's user account

In order for the signed commits to be verified, the GPG key must be exported and uploaded to the bot's Github account.

To display the full GPG key, run the following

gpg --armor --export 3AA5C34371567BD2

Then:

  • Go to the Github profile settings of the bot’s user account
  • Click “SSH and GPG keys”
  • Click “New GPG key”
  • Paste the GPG key and save.

Create a new GitHub app

We need to create an internal GitHub app to act as the release bot with very strict permissions. If the setting is not specified here, it should NOT be checked or filled in when creating the app.

  1. Got to your Github org homepage
  2. Click “Settings”
  3. Click “Developer settings”
  4. Click the “GitHub Apps” tab
  5. Click “New GitHub App”
  6. Configure the app as follows:
    • GitHub App name:
      • My Releaser (results in a my-releaser slug)
    • Description:
      • Internal bot to handle automatic releases of packages.
    • Homepage URL:
    • Callback URL:
    • Repository permissions:
      • Checks - read/write
      • Contents - read/write
      • Issues - read/write
      • Metadata - read
      • Pull requests - read/write
      • Secrets - read
    • Where can this GitHub App be installed?
      • Only on this account (Only allow this GitHub App to be installed on the @your-org account)
  7. Click “Create GitHub App”
  8. Make note of the “App ID” to reference later.
  9. Click “generate a private key”
    • This will create a private key and trigger a download of a .pem file to your machine
    • The .pem must be stored securely.
    • If this key is lost, you will have to create a new private key in the Github app settings (you won’t have to create a new app)
  10. Click the “Install App” tab and install the app in your organization
    • Select the repos that this internal app should have access to.
      • Since this is is an internal app with very limited permissions, it should be safe to give it access to all the the org’s repos. However, it probably makes sense to gradually give the app access to more repos as necessary.
  11. In the repo that this bot will be used in, add the following GitHub Actions secrets
    • MY_RELEASER_ID
      • Paste the App ID noted earlier as the value
    • MY_RELEASER_PRIVATE_KEY
      • Paste the contents of the .pem file downloaded earlier as the value
  12. If the repo has the following protections, we will have to modify the settings so that the my-releaser Github app can bypass one of the protections
    • Protections:
      • ☑ Require a pull request before merging
      • ☑ Require approvals
      • ☑ Require signed commits
    • Modify with:
      • ☑ Allow specified actors to bypass the required pull requests - Search for and select my-releaser as an actor that can bypass this requirement - Repeat the above for every protected branch in the repo (e.g., main, beta, …)

Github Actions Workflow

The following YAML code should be placed in the projects .github/workflows/release.yml file of the repo you're using this bot in.

Note that this file makes use of all the environment variables described in the “Overview” section, so it is crucial that they’re all defined in the Github Actions secrets of the repo you're using this bot in.

The only environment variable in this file that does not need to be defined as a Github Actions secret is GITHUB_TOKEN.

name: release

on:
  push:
    branches:
      - main
      - beta
  workflow_dispatch:

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - name: Generate bot app token
        id: generate_token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.MY_RELEASER_ID }}
          private-key: ${{ secrets.MY_RELEASER_PRIVATE_KEY }}

      - name: Checkout
        uses: actions/checkout@v4
        with:
          token: ${{ steps.generate_token.outputs.token }}
          fetch-depth: 0
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20.x"

      - name: Import GPG key
        run: echo $MY_RELEASER_BASE64_GPG_KEY | base64 --decode | gpg --batch --import
        env:
          MY_RELEASER_BASE64_GPG_KEY: ${{ secrets.MY_RELEASER_BASE64_GPG_KEY }}

      - name: Run program that passes the GPG passphrase to the GPG CLI
        run: |
          rm -rf /tmp/gpg.sh
          echo '#!/bin/bash' >> /tmp/gpg.sh
          echo 'gpg --batch --pinentry-mode=loopback --passphrase $MY_RELEASER_GPG_PASSPHRASE $@' >> /tmp/gpg.sh
          chmod +x /tmp/gpg.sh

      - name: Configure GPG for automated release commits
        run: |
          git config commit.gpgsign true
          git config user.signingkey $MY_RELEASER_GPG_KEY_ID
          git config gpg.program /tmp/gpg.sh
        env:
          MY_RELEASER_GPG_KEY_ID: ${{ secrets.MY_RELEASER_GPG_KEY_ID }}

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm run test

      - name: Build package
        run: npm run-script build
        env:
          NODE_ENV: production

      - name: Publish package
        run: npx semantic-release
        env:
          MY_RELEASER_GPG_PASSPHRASE: ${{ secrets.MY_RELEASER_GPG_PASSPHRASE }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
          GIT_AUTHOR_EMAIL: ${{ secrets.MY_RELEASER_USER_EMAIL }}
          GIT_COMMITTER_EMAIL: ${{ secrets.MY_RELEASER_USER_EMAIL }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment