Skip to content
Draft
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
186 changes: 16 additions & 170 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,186 +1,32 @@
name: Build and Attach Release Binaries
permissions:
contents: read
name: Release

on:
workflow_dispatch:
inputs:
release_tag:
description: "Release tag to attach binaries to (format: vX.Y.Z)"
required: true
type: string
push:
tags:
- "v*"

permissions:
contents: write

jobs:
validate-and-test:
release:
runs-on: ubuntu-latest
outputs:
major: ${{ steps.extract-version.outputs.major }}
minor: ${{ steps.extract-version.outputs.minor }}
patch: ${{ steps.extract-version.outputs.patch }}
steps:
- name: Checkout code
- name: Checkout
uses: actions/checkout@v6

- name: Validate version format and extract components
id: extract-version
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag }}
run: |
# Validate format (must be vX.Y.Z)
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Invalid version format. Must be vX.Y.Z (e.g., v0.1.0)"
exit 1
fi

# Extract components
VERSION_NUM="${RELEASE_TAG#v}"
IFS=. read -r MAJOR MINOR PATCH <<< "$VERSION_NUM"

echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
echo "patch=$PATCH" >> "$GITHUB_OUTPUT"

- name: Verify draft release exists
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.release_tag }}
run: |
# Check if release exists
if ! gh release view "$RELEASE_TAG" > /dev/null 2>&1; then
echo "::error::Release with tag $RELEASE_TAG does not exist"
exit 1
fi

# Check if it's a draft
IS_DRAFT=$(gh release view "$RELEASE_TAG" --json isDraft -q .isDraft)
if [ "$IS_DRAFT" != "true" ]; then
echo "::error::Release $RELEASE_TAG is not a draft release"
exit 1
fi

echo "✓ Draft release $RELEASE_TAG found"

- name: Extract and validate source code version
env:
INPUT_MAJOR: ${{ steps.extract-version.outputs.major }}
INPUT_MINOR: ${{ steps.extract-version.outputs.minor }}
INPUT_PATCH: ${{ steps.extract-version.outputs.patch }}
run: |
# Parse version from pkg/config/env.go
MAJOR=$(grep -E '^\s*VersionMajor\s*=\s*"[0-9]+"' pkg/config/env.go | sed -E 's/.*"([0-9]+)".*/\1/')
MINOR=$(grep -E '^\s*VersionMinor\s*=\s*"[0-9]+"' pkg/config/env.go | sed -E 's/.*"([0-9]+)".*/\1/')
PATCH=$(grep -E '^\s*VersionPatch\s*=\s*"[0-9]+"' pkg/config/env.go | sed -E 's/.*"([0-9]+)".*/\1/')

if [ -z "$MAJOR" ] || [ -z "$MINOR" ] || [ -z "$PATCH" ]; then
echo "::error::Failed to extract version from pkg/config/env.go"
exit 1
fi

SOURCE_VERSION="$MAJOR.$MINOR.$PATCH"
INPUT_VERSION="$INPUT_MAJOR.$INPUT_MINOR.$INPUT_PATCH"

echo "Workflow input version: $INPUT_VERSION"
echo "Source code version: $SOURCE_VERSION"

if [ "$INPUT_VERSION" != "$SOURCE_VERSION" ]; then
echo "::error::Version mismatch!"
echo "::error::Workflow input: v$INPUT_VERSION"
echo "::error::Source code (pkg/config/env.go): $SOURCE_VERSION"
echo "::error::Please update VersionMajor, VersionMinor, VersionPatch in pkg/config/env.go before releasing."
exit 1
fi

echo "✓ Version validation passed: $SOURCE_VERSION"
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24"

- name: Install Task
uses: go-task/setup-task@v1

- name: Run unit tests
run: |
echo "Running unit tests..."
task test-unit
echo "✓ Unit tests passed"

- name: Build release binary
run: |
echo "Building release binary..."
task build-release

if [ ! -f mbvpn ]; then
echo "::error::Build failed - mbvpn binary not found"
exit 1
fi

echo "✓ Build successful"

- name: Validate binary version output
env:
EXPECTED_VERSION: ${{ github.event.inputs.release_tag }}
run: |
echo "Testing binary version output..."

# Execute version command and capture output
BINARY_OUTPUT=$(./mbvpn version)

# Trim any whitespace
BINARY_OUTPUT=$(echo "$BINARY_OUTPUT" | tr -d '[:space:]')
EXPECTED_VERSION=$(echo "$EXPECTED_VERSION" | tr -d '[:space:]')

echo "Binary output: $BINARY_OUTPUT"
echo "Expected version: $EXPECTED_VERSION"

if [ "$BINARY_OUTPUT" != "$EXPECTED_VERSION" ]; then
echo "::error::Binary version mismatch!"
echo "::error::Expected: $EXPECTED_VERSION"
echo "::error::Got: $BINARY_OUTPUT"
exit 1
fi

echo "✓ Binary version validation passed"

build-binaries:
needs: validate-and-test
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
arch: [amd64, arm64, 386]
os: [linux]
env:
BINARY_NAME: mbvpn-${{ matrix.os }}-${{ matrix.arch }}
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup Go
uses: actions/setup-go@v6
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'Release' step
Uses Step
uses 'goreleaser/goreleaser-action' with ref 'v6', not a pinned commit hash
with:
go-version: "1.24"

- name: Install Task
uses: go-task/setup-task@v1

- name: Build for ${{ matrix.os }}-${{ matrix.arch }}
env:
VERSION_MAJOR: ${{ needs.validate-and-test.outputs.major }}
VERSION_MINOR: ${{ needs.validate-and-test.outputs.minor }}
VERSION_PATCH: ${{ needs.validate-and-test.outputs.patch }}
VERSION_BUILD: ${{ github.run_number }}
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
run: |
task build-release
mv mbvpn "$BINARY_NAME"

- name: Upload Release Asset
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.release_tag }}
run: |
gh release upload "$RELEASE_TAG" "$BINARY_NAME"
MWB_HOMEBREW_TAP: ${{ secrets.MWB_HOMEBREW_TAP }}
53 changes: 53 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
version: 2

builds:
- main: ./cmd/mbvpn
binary: mbvpn
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
- "386"
flags:
- -trimpath
ldflags:
- -s -w -X github.com/malwarebytes/mbvpn-linux/pkg/config.Version={{.Version}}

archives:
- formats:
- tar.gz
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- .Arch }}

checksum:
name_template: "checksums.txt"

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^chore:"
- "^style:"
- "^ci:"
- "^test:"

brews:
- name: mbvpn
repository:
owner: malwarebytes
name: homebrew-tap
token: "{{ .Env.MWB_HOMEBREW_TAP }}"
homepage: "https://github.com/malwarebytes/mbvpn-linux"
description: "Malwarebytes VPN client for Linux"
license: "Apache-2.0"
install: |
bin.install "mbvpn"
test: |
system "#{bin}/mbvpn", "version"
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ The client is in experimental mode, so it is important to know which parts of th

## Installation

### Installation via Go Package Manager (Recommended)
### Homebrew (Linux)

```bash
brew install malwarebytes/tap/mbvpn
```

Then provide network capabilities (see below).

### Installation via Go Package Manager



1. Install Go version 1.23.4 or above.

Expand Down
5 changes: 5 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ tasks:
env:
MBVPN_TEST_LICENSE_KEY: "{{.MBVPN_TEST_LICENSE_KEY}}"

release-snapshot:
desc: Build a local snapshot release with goreleaser (no publish)
cmds:
- goreleaser release --snapshot --clean

clean:
desc: Clean build artifacts
cmds:
Expand Down
2 changes: 1 addition & 1 deletion cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func NewVersionCommand() *cobra.Command {
Short: "Display version information",
Long: `Display the application version.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("v%s\n", config.Version())
fmt.Printf("v%s\n", config.Version)
},
}
}
13 changes: 5 additions & 8 deletions cmd/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,40 +32,37 @@ func TestNewVersionCommand(t *testing.T) {
}

func TestVersionCommandOutput(t *testing.T) {
// Capture stdout
origVersion := config.Version
config.Version = "1.2.3"
defer func() { config.Version = origVersion }()

oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Run the command
cmd := NewVersionCommand()
cmd.Run(cmd, []string{})

// Restore stdout and get output
w.Close()
os.Stdout = oldStdout

var buf bytes.Buffer
io.Copy(&buf, r)
output := strings.TrimSpace(buf.String())

// Verify output is single line
if strings.Contains(output, "\n") {
t.Errorf("Expected single line output, got multiple lines: %s", output)
}

// Verify output starts with "v"
if !strings.HasPrefix(output, "v") {
t.Errorf("Expected output to start with 'v', got: %s", output)
}

// Verify version format matches "vX.Y.Z"
expectedVersion := "v" + config.Version()
expectedVersion := "v" + config.Version
if output != expectedVersion {
t.Errorf("Expected version '%s', got '%s'", expectedVersion, output)
}

// Verify it contains dots (semantic version format)
if !strings.Contains(output, ".") {
t.Errorf("Expected version format 'vX.Y.Z', got: %s", output)
}
Expand Down
12 changes: 2 additions & 10 deletions pkg/config/env.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package config

import (
"fmt"
"os"
"testing"
)
Expand All @@ -11,10 +10,8 @@ var (
BuildEnv = "production"
HolocronUrl = "https://holocron.mwbsys.com/graphql"

// Version information
VersionMajor = "0"
VersionMinor = "1"
VersionPatch = "0"
// Version is injected at build time via -ldflags by GoReleaser.
Version = "dev"
)

func Debug() bool {
Expand All @@ -25,11 +22,6 @@ func Verbose() bool {
return testing.Testing() && testing.Verbose()
}

// Version returns the full version string in the format "major.minor.patch"
func Version() string {
return fmt.Sprintf("%s.%s.%s", VersionMajor, VersionMinor, VersionPatch)
}

// GetHolocronUrl returns the Holocron URL, checking environment variable first
func GetHolocronUrl() string {
if url := os.Getenv("MBVPN_HOLOCRON_URL"); url != "" {
Expand Down
Loading