Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Cache APT packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libgtk-4-dev libwebkitgtk-6.0-dev
packages: libgtk-3-dev libwebkit2gtk-4.1-dev
version: 1.0
execute_install_scripts: false

Expand Down Expand Up @@ -133,7 +133,7 @@ jobs:
- name: Install system deps (Wails CGO)
run: |
sudo apt-get update
sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev

- name: Run govulncheck
run: |
Expand Down Expand Up @@ -162,7 +162,7 @@ jobs:
- name: Cache APT packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libgtk-4-dev libwebkitgtk-6.0-dev
packages: libgtk-3-dev libwebkit2gtk-4.1-dev
version: 1.0
execute_install_scripts: false

Expand Down
181 changes: 103 additions & 78 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,113 +73,135 @@ jobs:
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev

- name: Install Task
run: |
go install github.com/go-task/task/v3/cmd/task@latest
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
shell: bash
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev

- name: Install Frontend Dependencies
run: |
cd frontend && bun install
shell: bash

- name: Install Wails CLI
- name: Check macOS signing inputs
id: macos_signing
if: matrix.os == 'macos-latest'
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
GOPATH=$(go env GOPATH)
# Create 'wails' alias for 'wails3' for future compatibility
if [ "${{ matrix.os }}" = "windows-latest" ]; then
cp "$GOPATH/bin/wails3.exe" "$GOPATH/bin/wails.exe"
echo "$GOPATH/bin" >> $GITHUB_PATH
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
ln -sf "$GOPATH/bin/wails3" "$GOPATH/bin/wails"
echo "$GOPATH/bin" >> $GITHUB_PATH
else
sudo ln -sf "$GOPATH/bin/wails3" /usr/local/bin/wails
missing=0
for name in MACOS_CERTIFICATE MACOS_CERTIFICATE_PASSWORD MACOS_SIGN_IDENTITY APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
if [ -z "${!name}" ]; then
echo "::error::$name is required for signed and notarized macOS releases"
missing=1
fi
done

if [ "$missing" -ne 0 ]; then
exit 1
fi

echo "available=true" >> "$GITHUB_OUTPUT"
shell: bash

- name: Import macOS Developer ID certificate
if: matrix.os == 'macos-latest' && steps.macos_signing.outputs.available == 'true'
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }}
p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
keychain: build
keychain-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}

- name: Build Application
run: |
# Build for current platform (native runner)
cd frontend
bun run build
cd ..

mkdir -p bin

if [ "${{ matrix.os }}" = "macos-latest" ]; then
# macOS: Build with code signing if certificates are available
if [ -n "$MACOS_CERTIFICATE" ] && [ -n "$MACOS_CERTIFICATE_PASSWORD" ]; then
echo "Building with code signing..."
wails build -sign -signIdentity "Developer ID Application"
else
echo "Building without code signing (no certificates found)..."
wails build
export GOOS=darwin
export CGO_ENABLED=1
export CGO_CFLAGS="-mmacosx-version-min=10.15"
export CGO_LDFLAGS="-mmacosx-version-min=10.15"
export MACOSX_DEPLOYMENT_TARGET="10.15"

GOARCH=amd64 go build -tags production -trimpath -buildvcs=false -ldflags="-w -s" -o bin/DevToolbox-amd64 .
GOARCH=arm64 go build -tags production -trimpath -buildvcs=false -ldflags="-w -s" -o bin/DevToolbox-arm64 .
lipo -create -output bin/DevToolbox bin/DevToolbox-amd64 bin/DevToolbox-arm64
rm bin/DevToolbox-amd64 bin/DevToolbox-arm64

mkdir -p "bin/DevToolbox.app/Contents/MacOS"
mkdir -p "bin/DevToolbox.app/Contents/Resources"
cp "bin/DevToolbox" "bin/DevToolbox.app/Contents/MacOS/"
cp "build/darwin/Info.plist" "bin/DevToolbox.app/Contents/"
cp "build/darwin/icons.icns" "bin/DevToolbox.app/Contents/Resources/"
if [ -f "build/darwin/Assets.car" ]; then
cp "build/darwin/Assets.car" "bin/DevToolbox.app/Contents/Resources/"
fi
else
wails build
if [ "${{ matrix.os }}" = "windows-latest" ]; then
go build -tags production -trimpath -buildvcs=false -ldflags="-w -s" -o bin/DevToolbox.exe .
else
go build -tags production -trimpath -buildvcs=false -ldflags="-w -s" -o bin/DevToolbox .
fi
fi
shell: bash
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}

# Import macOS certificates for signing (optional - only if secrets are configured)
- name: Import macOS Certificates
if: matrix.os == 'macos-latest' && github.event_name != 'pull_request' && env.HAS_CERTS == 'true'
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }}
p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
keychain: build
keychain-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
- name: Sign macOS app
if: matrix.os == 'macos-latest' && steps.macos_signing.outputs.available == 'true'
env:
HAS_CERTS: ${{ secrets.MACOS_CERTIFICATE != '' && secrets.MACOS_CERTIFICATE_PASSWORD != '' }}
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
run: |
APP_BUNDLE="bin/DevToolbox.app"
test -d "$APP_BUNDLE"

codesign \
--force \
--deep \
--options runtime \
--timestamp \
--sign "$MACOS_SIGN_IDENTITY" \
"$APP_BUNDLE"

codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
shell: bash

# Notarize macOS app (optional - only if secrets are configured and .app bundle exists)
- name: Notarize macOS App
if: matrix.os == 'macos-latest' && github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/v') && env.HAS_NOTARIZE_SECRETS == 'true'
if: matrix.os == 'macos-latest' && steps.macos_signing.outputs.available == 'true'
run: |
BINARY_NAME=$(ls bin/ | grep -i "devtoolbox" | head -1)
if [ -d "bin/$BINARY_NAME.app" ]; then
# Create a zip for notarization
ditto -c -k --keepParent "bin/$BINARY_NAME.app" "bin/devtoolbox.zip"

# Submit for notarization
xcrun notarytool submit "bin/devtoolbox.zip" \
--apple-id "${{ secrets.APPLE_ID }}" \
--password "${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}" \
--team-id "${{ secrets.APPLE_TEAM_ID }}" \
--wait

# Staple the notarization ticket
xcrun stapler staple "bin/$BINARY_NAME.app"
else
echo "No .app bundle found, skipping notarization (binary-only build)"
fi
APP_BUNDLE="bin/DevToolbox.app"
NOTARY_ZIP="bin/DevToolbox-notary.zip"

ditto -c -k --keepParent "$APP_BUNDLE" "$NOTARY_ZIP"

xcrun notarytool submit "$NOTARY_ZIP" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait

xcrun stapler staple "$APP_BUNDLE"
xcrun stapler validate "$APP_BUNDLE"
spctl --assess --type execute --verbose=4 "$APP_BUNDLE"
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
HAS_NOTARIZE_SECRETS: ${{ secrets.APPLE_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' && secrets.APPLE_TEAM_ID != '' }}
shell: bash

# Package and upload build artifacts
- name: Package Artifacts
run: |
mkdir -p release

# Discover the actual binary name (DevToolbox, devtoolbox, etc.)
BINARY_NAME=$(ls bin/ | grep -i "devtoolbox" | head -1)
echo "Found binary: $BINARY_NAME"

if [ -z "$BINARY_NAME" ]; then
echo "ERROR: Could not find devtoolbox binary in bin/"
ls -la bin/
exit 1
fi

# Check if it's an .app bundle (macOS) or just a binary
if [ -d "bin/$BINARY_NAME.app" ]; then
# macOS with .app bundle
echo "Found .app bundle, creating DMG..."
if [ "${{ matrix.os }}" = "macos-latest" ]; then
APP_BUNDLE="bin/DevToolbox.app"
test -d "$APP_BUNDLE"
brew install create-dmg
create-dmg \
--volname "DevToolbox" \
Expand All @@ -188,12 +210,15 @@ jobs:
--icon-size 100 \
--app-drop-link 600 185 \
"release/DevToolbox-${{ matrix.build }}.dmg" \
"bin/$BINARY_NAME.app"
"$APP_BUNDLE"
hdiutil verify "release/DevToolbox-${{ matrix.build }}.dmg"
elif [ "${{ matrix.os }}" = "windows-latest" ]; then
# Windows: copy .exe
BINARY_NAME=$(ls bin/ | grep -i "devtoolbox.*\.exe$" | head -1)
test -n "$BINARY_NAME"
cp "bin/$BINARY_NAME" "release/DevToolbox-${{ matrix.build }}.exe"
else
# Linux or macOS binary (no .app): create tar.gz
BINARY_NAME=$(ls bin/ | grep -i "devtoolbox" | head -1)
test -n "$BINARY_NAME"
tar -czf "release/DevToolbox-${{ matrix.build }}.tar.gz" -C bin "$BINARY_NAME"
fi

Expand Down
12 changes: 6 additions & 6 deletions build/darwin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>My Product</string>
<string>DevToolbox</string>
<key>CFBundleExecutable</key>
<string>devtoolbox</string>
<string>DevToolbox</string>
<key>CFBundleIdentifier</key>
<string>com.example.devtoolbox</string>
<string>com.vuon9.devtoolbox</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleGetInfoString</key>
<string>This is a comment</string>
<string>DevToolbox is a set of useful tools for daily development.</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleIconFile</key>
Expand All @@ -24,6 +24,6 @@
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>© 2026, My Company</string>
<string>(c) 2026, Vuong</string>
</dict>
</plist>
</plist>
28 changes: 12 additions & 16 deletions docs/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

- Go 1.25+
- Bun 1.0+
- Wails CLI: `go install github.com/wailsapp/wails/v2/cmd/wails@latest`

## Quick Build

Expand All @@ -13,11 +12,10 @@
git clone https://github.com/vuon9/devtoolbox.git
cd devtoolbox

# Build
wails build

# Or run in development mode
wails dev
# Build for the current platform
cd frontend && bun install && bun run build
cd ..
go build -o bin/DevToolbox .
```

## Development
Expand All @@ -30,24 +28,22 @@ cd frontend && bun dev
go run .

# Both (separate terminals)
wails dev # Terminal 1
cd frontend && bun dev # Terminal 2
go run . # Terminal 1
cd frontend && bun dev # Terminal 2
```

## Output

Built binaries are in `build/bin/`:
- `devtoolbox` (Linux/macOS)
- `devtoolbox.exe` (Windows)
Built binaries and app bundles are in `bin/`:
- `DevToolbox` (Linux/macOS binary)
- `DevToolbox.exe` (Windows)
- `DevToolbox.app` (macOS package)

For signed and notarized macOS releases, see [MACOS_RELEASE.md](./MACOS_RELEASE.md).

## Troubleshooting

**Frontend build fails:**
```bash
cd frontend && bun install
```

**Wails not found:**
```bash
go install github.com/wailsapp/wails/v2/cmd/wails@latest
```
47 changes: 47 additions & 0 deletions docs/MACOS_RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# macOS Signed Release

This project ships macOS releases as a signed, notarized, and stapled
`DevToolbox-macos.dmg` from `.github/workflows/release.yml`.

## Required GitHub Secrets

Configure these repository secrets before running a release:

- `MACOS_CERTIFICATE`: base64 encoded Developer ID Application `.p12`
- `MACOS_CERTIFICATE_PASSWORD`: password for the `.p12`
- `MACOS_SIGN_IDENTITY`: full Developer ID Application identity name
- `APPLE_ID`: Apple ID used for notarization
- `APPLE_APP_SPECIFIC_PASSWORD`: app-specific password for the Apple ID
- `APPLE_TEAM_ID`: Apple Developer Team ID

The release workflow fails early on the macOS job if any of these secrets are
missing. Unsigned macOS release artifacts are not uploaded by the release job.

## What the Workflow Does

On macOS runners, the release job:

1. Builds a universal `DevToolbox.app`.
2. Imports the Developer ID Application certificate into a temporary keychain.
3. Signs the app with hardened runtime and timestamping.
4. Verifies the signature with `codesign --verify`.
5. Submits the app to Apple notarization and waits for completion.
6. Staples and validates the notarization ticket.
7. Runs `spctl --assess --type execute`.
8. Packages the stapled app into `DevToolbox-macos.dmg`.
9. Verifies the DMG with `hdiutil verify`.

Mini owns certificate setup, notarization credentials, and final local Gatekeeper
verification for the released artifact.

## Local macOS Verification

After downloading the release DMG on macOS:

```bash
hdiutil verify DevToolbox-macos.dmg
hdiutil attach DevToolbox-macos.dmg
spctl --assess --type execute --verbose=4 /Volumes/DevToolbox/DevToolbox.app
codesign --verify --deep --strict --verbose=2 /Volumes/DevToolbox/DevToolbox.app
open /Volumes/DevToolbox/DevToolbox.app
```
Loading
Loading