Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*.yml]
indent_style = space
indent_size = 2
9 changes: 9 additions & 0 deletions .github/utils/entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
112 changes: 112 additions & 0 deletions .github/utils/mac-sign-and-notarize.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
#
# This script signs and notarizes a binary on macOS.
# It performs the following steps:
# 1. Creates a temporary keychain and imports the signing certificate from a base64-encoded .p12 file provided via environment variable.
# 2. Signs the app using the imported certificate.
# 3. Notarizes the app using Apple's notarytool.
#
# [1] - https://developer.apple.com/help/account/certificates/create-developer-id-certificates/
# [2] - https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac
# [3] - https://developer.apple.com/documentation/security/customizing-the-notarization-workflow#Upload-your-app-to-the-notarization-service

set -eu

# Script Configuration
APP_PATH=qbee-cli
ZIP_PATH=${1}
KEYCHAIN_TTL=300 # 5 minutes

# Temporary working directory for keychain and certificate handling
WORKDIR="${TMPDIR:-/tmp}/mac-sign-and-notarize.$$"

# Helper functions
die() { echo "Error: $*" >&2; exit 1; }
info(){ echo "==> $*"; }
cleanup(){ [ -d "$WORKDIR" ] && rm -rf "$WORKDIR"; }
trap cleanup EXIT INT TERM

# Validate required environment variables
if [ -z "${MACOS_SIGN_P12:-}" ]; then
die "MACOS_SIGN_P12 must be set to the base64-encoded .p12 certificate"
fi

if [ -z "${MACOS_SIGN_PASSWORD:-}" ]; then
die "MACOS_SIGN_PASSWORD must be set to the password for the .p12 certificate"
fi

if [ -z "${APPLE_CODE_NOTARY_EMAIL:-}" ]; then
die "APPLE_CODE_NOTARY_EMAIL must be set to the Apple ID for notarization"
fi

if [ -z "${APPLE_CODE_NOTARY_PASSWORD:-}" ]; then
die "APPLE_CODE_NOTARY_PASSWORD must be set to the password for the Apple ID for notarization"
fi

if [ -z "${ZIP_PATH:-}" ]; then
die "$1 must be set to the path of the zip file (e.g., qbee-cli_darwin_amd64.zip)"
fi

CERT_P12_PATH=${WORKDIR}/cert.p12
KEYCHAIN_PATH=${WORKDIR}/keychain-db

info "Generating random password for temporary keychain"
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

info "Creating temporary working directory at $WORKDIR"
mkdir -p "$WORKDIR" || die "Cannot create temp dir"

info "Decoding .p12 certificate and saving to $CERT_P12_PATH"
echo -n "$MACOS_SIGN_P12" | base64 --decode -o $CERT_P12_PATH

info "Extracting signing identity from certificate"
MACOS_SIGN_IDENTITY=$(
openssl pkcs12 -legacy -in $CERT_P12_PATH -nokeys -passin env:MACOS_SIGN_PASSWORD -clcerts | \
openssl x509 -noout -subject -nameopt sep_multiline,lname | \
grep "commonName" | \
cut -d= -f2)

info $MACOS_SIGN_IDENTITY

info "Extracting Apple Team ID from certificate"
APPLE_TEAM_ID=$(
openssl pkcs12 -legacy -in $CERT_P12_PATH -nokeys -passin env:MACOS_SIGN_PASSWORD -clcerts | \
openssl x509 -noout -subject -nameopt sep_multiline,lname | \
grep "organizationalUnitName" | \
cut -d= -f2)

info $APPLE_TEAM_ID

info "Creating temporary keychain at $KEYCHAIN_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

info "Setting keychain settings to prevent locking for $KEYCHAIN_TTL seconds"
security set-keychain-settings -lut $KEYCHAIN_TTL $KEYCHAIN_PATH

info "Unlocking keychain"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

info "Importing certificate into keychain"
security import $CERT_P12_PATH -k $KEYCHAIN_PATH -P "$MACOS_SIGN_PASSWORD" -T /usr/bin/codesign

info "Allowing codesign to access the keychain item"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

info "Setting keychain search path to include temporary keychain"
security list-keychains -d user -s $KEYCHAIN_PATH login.keychain

info "Signing $APP_PATH with $MACOS_SIGN_IDENTITY certificate"
codesign --keychain $KEYCHAIN_PATH --force --options runtime --entitlements .github/utils/entitlements.plist --sign "$MACOS_SIGN_IDENTITY" --timestamp $APP_PATH

info "Creating zip archive for notarization"
zip -r $ZIP_PATH $APP_PATH README.md LICENSE

info "Storing notarization credentials in keychain for notarytool"
xcrun notarytool store-credentials "notarytool-password" \
--keychain "$KEYCHAIN_PATH" \
--apple-id "$APPLE_CODE_NOTARY_EMAIL" \
--password "$APPLE_CODE_NOTARY_PASSWORD" \
--team-id "$APPLE_TEAM_ID"

info "Notarizing binary with Apple Notary Service"
xcrun notarytool submit --keychain-profile "notarytool-password" --wait "$ZIP_PATH"
Comment thread
piotrbulinski marked this conversation as resolved.
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.25.6
go-version: 1.25.7
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
4 changes: 2 additions & 2 deletions .github/workflows/pr-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.6
go-version: 1.25.7
- name: Build and test
env:
QBEE_EMAIL: ${{ secrets.QBEE_API_USER }}
Expand All @@ -25,7 +25,7 @@ jobs:
- id: govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: 1.25.6
go-version-input: 1.25.7
go-package: ./...
- name: golint
run: go run golang.org/x/lint/golint@latest ./...
Expand Down
164 changes: 164 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: Release qbee-cli on GitHub

permissions:
contents: write # needed to write releases
id-token: write # needed for keyless signing
attestations: write # needed for provenance

on:
release:
types: [prereleased]

env:
# VERSION is set to the release tag name, e.g., "v1.0.0".
VERSION: ${{ github.ref_name }}

# NAME is the name of the application.
NAME: qbee-cli

# CGO_ENABLED disables CGO.
CGO_ENABLED: 0

# GCFLAGS with -trimpath ensures that the build is reproducible and does not contain file system paths.
GCFLAGS: -trimpath

# LDFLAGS with -s and -w reduces the binary size by omitting symbol table and debug information.
# The -X flag sets the Version variable in the code to the release version.
DFLAGS: -s -w -X go.qbee.io/client.Version=${{ github.ref_name }}

# GOPROXY ensures that we use the correct Go module proxy.
# We want our build process to start with populateding the Go proxy
# with the freshly tagged version of the module.
GOPROXY: https://proxy.golang.org

jobs:
go-proxy:
name: Go Proxy
runs-on: ubuntu-24.04
steps:
- name: Populate Go module proxy cache
run: go list -m go.qbee.io/client@${{ env.VERSION }}

linux:
name: Linux Binaries
runs-on: ubuntu-24.04
needs: go-proxy
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Install stable Go version
uses: actions/setup-go@v6
with:
go-version: stable

- name: Build binary
run: go build $GCFLAGS -ldflags "$LDFLAGS" -o ${{ env.NAME }} ./cmd

- name: Create archive and upload to the release
id: reease
env:
GH_TOKEN: ${{ github.token }}
ARCHIVE_NAME: ${{ env.NAME }}_linux_amd64.tar.gz
run: |
tar -czvf $ARCHIVE_NAME ${{ env.NAME }} LICENSE README.md
gh release upload ${{ env.VERSION }} $ARCHIVE_NAME

windows:
name: Windows Signed Binary
runs-on: windows-2025
needs: go-proxy
env:
# The following environment variables are needed for the DigiCert Software Trust signing action.
# https://docs.digicert.com/en/digicert-keylocker/ci-cd-integrations-and-deployment-pipelines/plugins/github/binary-signing-using-github-actions.html
# Following credentials are from Qbee GitHub Actions service user account.
SM_HOST: ${{ vars.SM_HOST }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_CLIENT_CERT_FILE: Certificate_pkcs12.p12
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Install stable Go version
uses: actions/setup-go@v6
with:
go-version: stable

- name: Setup DigiCert KeyLocker
shell: bash
run: |
echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > ${{ env.SM_CLIENT_CERT_FILE }}

- name: Build binary
shell: bash
run: go build $GCFLAGS -ldflags "$LDFLAGS" -o ${{ env.NAME }}.exe ./cmd

- name: Sign the binary
uses: digicert/code-signing-software-trust-action@v1
with:
simple-signing-mode: true
keypair-alias: ${{ secrets.SM_KEYPAIR_ALIAS }}
input: ${{ env.NAME }}.exe

- name: Create archive and upload to the release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
ARCHIVE_NAME: ${{ env.NAME }}_windows_amd64.zip
run: |
7z a $ARCHIVE_NAME ${{ env.NAME }}.exe LICENSE README.md
gh release upload ${{ env.VERSION }} $ARCHIVE_NAME

macos:
name: MacOS Signed Binary
runs-on: macos-26
needs: go-proxy
strategy:
matrix:
arch: [amd64, arm64]
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Install stable Go version
uses: actions/setup-go@v6
with:
go-version: stable

- name: Build binary for ${{ matrix.arch }}
env:
GOARCH: ${{ matrix.arch }}
run: go build $GCFLAGS -ldflags "$LDFLAGS" -o ${{ env.NAME }} ./cmd

- name: Sign and package
env:
TMPDIR: ${{ runner.temp }}

# MACOS_SIGN_P12 is the base64-encoded .p12 certificate file for Developer ID Application certificate type.
# The certificate can be created and downloaded from the Apple Developer account, Certificates section.
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}

# MACOS_SIGN_PASSWORD is the password for the .p12 certificate file.
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}

# APPLE_CODE_NOTARY_EMAIL is the Apple ID used for notarization.
# The user needs to have App Manager role.
APPLE_CODE_NOTARY_EMAIL: ${{ secrets.APPLE_CODE_NOTARY_EMAIL }}

# APPLE_CODE_NOTARY_PASSWORD is the password for the Apple ID used for notarization
APPLE_CODE_NOTARY_PASSWORD: ${{ secrets.APPLE_CODE_NOTARY_PASSWORD }}

run: bash ./.github/utils/mac-sign-and-notarize.sh ${{ env.NAME }}_darwin_${{ matrix.arch }}.zip

- name: Upload Zip archive to the release
env:
GH_TOKEN: ${{ github.token }}
ARCHIVE_NAME: ${{ env.NAME }}_darwin_${{ matrix.arch }}.zip
run: gh release upload ${{ env.VERSION }} ${{ env.ARCHIVE_NAME }}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module go.qbee.io/client

go 1.25.6
go 1.25.7

require (
github.com/xtaci/smux v1.5.55
Expand Down
Loading