From e166e1b35d3ca48cd961a1a983d384a6ee353b4c Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 15:43:26 -0400 Subject: [PATCH 01/19] Setup linux build --- .github/workflows/release.yml | 104 ++++++++++++++++- .goreleaser.yaml | 8 ++ cmd/mdv-gui/wails.json | 2 +- go.mod | 4 +- internal/render/render.go | 18 +++ scripts/build-wails.sh | 3 +- scripts/docker/Dockerfile.linux-build | 38 ++++++ scripts/docker/README.md | 99 ++++++++++++++++ scripts/docker/build-linux.sh | 23 ++++ scripts/test-local-build.sh | 159 ++++++++++++++++++++++++++ 10 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 scripts/docker/Dockerfile.linux-build create mode 100644 scripts/docker/README.md create mode 100755 scripts/docker/build-linux.sh create mode 100755 scripts/test-local-build.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf1be3f..0c670bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,58 @@ permissions: contents: write jobs: + # Build GUI binaries on native platforms + build-gui: + name: Build GUI (${{ matrix.os }}) + strategy: + matrix: + include: + - os: ubuntu-22.04 + platforms: linux/amd64,linux/arm64 + artifact-name: gui-linux + - os: macos-latest + platforms: darwin/amd64,darwin/arm64 + artifact-name: gui-macos + # Windows builds can be added when needed: + # - os: windows-latest + # platforms: windows/amd64 + # artifact-name: gui-windows + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential + + - name: Build GUI + env: + WAILS_PLATFORMS: ${{ matrix.platforms }} + run: ./scripts/build-wails.sh + + - name: Upload GUI artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: cmd/mdv-gui/build/bin/ + retention-days: 1 + + # Main release job release: + needs: build-gui runs-on: macos-latest steps: - name: Checkout @@ -21,11 +72,22 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' - name: Install Wails CLI run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + - name: Download all GUI artifacts + uses: actions/download-artifact@v4 + with: + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: List downloaded artifacts + run: | + echo "=== Downloaded GUI artifacts ===" + ls -laR cmd/mdv-gui/build/bin/ + - name: Configure signing keychain run: | set -euo pipefail @@ -74,5 +136,45 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Create Linux GUI archives + run: | + set -euo pipefail + VERSION="${GITHUB_REF#refs/tags/}" + + for arch in amd64 arm64; do + arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") + gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" + + if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui" ]; then + archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" + echo "Creating $archive" + + mkdir -p /tmp/mdv-gui-linux + cp "$gui_dir/mdv-gui" /tmp/mdv-gui-linux/ + cp LICENSE* /tmp/mdv-gui-linux/ || true + cp README* /tmp/mdv-gui-linux/ || true + cp -r examples /tmp/mdv-gui-linux/ || true + + tar -czf "$archive" -C /tmp/mdv-gui-linux . + rm -rf /tmp/mdv-gui-linux + + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + else + echo "Warning: Linux $arch GUI binary not found, skipping" + fi + done + + - name: Upload Linux GUI archives to release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + for archive in mdv-gui_*.tar.gz; do + if [ -f "$archive" ]; then + echo "Uploading $archive to release..." + gh release upload "${GITHUB_REF#refs/tags/}" "$archive" --clobber + fi + done + env: + GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Verify macOS signatures run: ./scripts/verify-macos-signatures.sh diff --git a/.goreleaser.yaml b/.goreleaser.yaml index cf0132f..b21791a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -52,6 +52,11 @@ builds: - -X main.date={{.Date}} mod_timestamp: '{{ .CommitTimestamp }}' + # Note: Linux and Windows GUI binaries are built by ./scripts/build-wails.sh + # on native runners in CI (see .github/workflows/release.yml). + # They cannot be cross-compiled here due to CGO requirements. + # The pre-built binaries are packaged via GitHub Actions workflow. + archives: - id: mdv-mac-archive ids: @@ -104,6 +109,9 @@ archives: - src: cmd/mdv-gui/build/bin/mdv-gui_{{ .Os }}_{{ .Arch }}/mdv-gui.app dst: mdv-gui.app + # Note: Linux and Windows GUI archives are created by GitHub Actions workflow + # after building on native runners. See .github/workflows/release.yml + checksum: name_template: 'checksums.txt' algorithm: sha256 diff --git a/cmd/mdv-gui/wails.json b/cmd/mdv-gui/wails.json index dcd0942..e6cd784 100644 --- a/cmd/mdv-gui/wails.json +++ b/cmd/mdv-gui/wails.json @@ -31,7 +31,7 @@ "icon": "./frontend/assets/mdv-app-icon.png" }, "windows": { - "icon": "./frontend/assets/mdv-app-icon.png" + "icon": "./frontend/assets/mdv-app-icon.ico" } } } diff --git a/go.mod b/go.mod index cfc9a99..58d1821 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/iiatlas/mdv -go 1.25.1 +go 1.24.0 + +toolchain go1.24.8 require ( github.com/charmbracelet/bubbles v0.21.0 diff --git a/internal/render/render.go b/internal/render/render.go index 047c750..6778630 100644 --- a/internal/render/render.go +++ b/internal/render/render.go @@ -75,6 +75,24 @@ func detectSystemTheme() string { } // Default to dark for Linux return "dark" + case "windows": + // Windows: query registry for system theme preference + // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize + // AppsUseLightTheme: 0x0 = dark, 0x1 = light + cmd := exec.Command("reg", "query", + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + "/v", "AppsUseLightTheme") + output, err := cmd.Output() + if err == nil { + // Parse registry output + // Output format: " AppsUseLightTheme REG_DWORD 0x0" or "0x1" + if strings.Contains(string(output), "0x0") { + return "dark" + } + return "light" + } + // Fallback to dark if registry query fails + return "dark" default: // Default to dark for other platforms return "dark" diff --git a/scripts/build-wails.sh b/scripts/build-wails.sh index 59c3918..0b479b3 100755 --- a/scripts/build-wails.sh +++ b/scripts/build-wails.sh @@ -6,7 +6,8 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # Wails can only produce native bundles reliably for macOS from macOS; # cross-compiling GUI binaries for other OSes is handled on their respective # builders. Keep the defaults focused on the supported macOS targets. -DEFAULT_PLATFORMS="darwin/amd64,darwin/arm64" +# Default to macOS for local development, but allow override via WAILS_PLATFORMS env var for CI +DEFAULT_PLATFORMS="${WAILS_PLATFORMS:-darwin/amd64,darwin/arm64}" if [[ $# -gt 0 ]]; then PLATFORMS=$1 shift diff --git a/scripts/docker/Dockerfile.linux-build b/scripts/docker/Dockerfile.linux-build new file mode 100644 index 0000000..adfa1e4 --- /dev/null +++ b/scripts/docker/Dockerfile.linux-build @@ -0,0 +1,38 @@ +# Dockerfile for building mdv Linux GUI +# This allows local testing of Linux builds without requiring a Linux VM +# +# Build: docker build -f scripts/docker/Dockerfile.linux-build -t mdv-linux-builder . +# Run: docker run --rm -v "$PWD:/workspace" mdv-linux-builder + +# Force linux/amd64 platform for consistency (works on both Intel and Apple Silicon Macs) +FROM --platform=linux/amd64 ubuntu:22.04 + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + build-essential \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + pkg-config \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install Go 1.24 +ARG GO_VERSION=1.24.8 +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -C /usr/local -xz +ENV PATH="/usr/local/go/bin:${PATH}" +ENV GOPATH="/go" +ENV PATH="${GOPATH}/bin:${PATH}" +# Force Go to use the installed toolchain version, not download newer ones +ENV GOTOOLCHAIN=local + +# Install Wails CLI +RUN go install github.com/wailsapp/wails/v2/cmd/wails@latest + +# Set working directory +WORKDIR /workspace + +# Default command: build Linux GUI for amd64 +# Override with: docker run ... mdv-linux-builder +CMD ["sh", "-c", "WAILS_PLATFORMS=linux/amd64 ./scripts/build-wails.sh"] diff --git a/scripts/docker/README.md b/scripts/docker/README.md new file mode 100644 index 0000000..3f035d5 --- /dev/null +++ b/scripts/docker/README.md @@ -0,0 +1,99 @@ +# Docker Build Environment for Linux + +This directory contains Docker configurations for building the mdv Linux GUI without requiring a Linux VM. + +## Quick Start + +```bash +# Build Linux GUI using Docker (from repository root) +./scripts/docker/build-linux.sh +``` + +This script will: +1. Build the Docker image with all Linux dependencies +2. Run the build inside the container +3. Output binaries to `cmd/mdv-gui/build/bin/` + +## Manual Usage + +If you prefer to run Docker commands manually: + +```bash +# Build the Docker image (x86_64/amd64) +docker build --platform linux/amd64 -f scripts/docker/Dockerfile.linux-build -t mdv-linux-builder . + +# Build Linux GUI (amd64) +docker run --platform linux/amd64 --rm -v "$PWD:/workspace" mdv-linux-builder + +# Build for specific platform (still runs in x86_64 container) +docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ + -e WAILS_PLATFORMS=linux/arm64 \ + mdv-linux-builder + +# Build for multiple platforms +docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ + -e WAILS_PLATFORMS=linux/amd64,linux/arm64 \ + mdv-linux-builder + +# Run a shell inside the container for debugging +docker run --platform linux/amd64 --rm -it -v "$PWD:/workspace" \ + mdv-linux-builder bash +``` + +**Note**: The `--platform linux/amd64` flag ensures the container runs in x86_64 mode, which works on both Intel and Apple Silicon Macs (via Rosetta 2 emulation on Apple Silicon). + +## Requirements + +- Docker Desktop (macOS/Windows) or Docker Engine (Linux) +- ~2GB disk space for the image +- ~5-10 minutes for first build (image creation) +- **Note**: On Apple Silicon Macs, the container runs via Rosetta 2 emulation (x86_64) + +## What's Inside + +The Docker image includes: +- Ubuntu 24.04 base +- Go 1.23 +- Wails CLI +- GTK3 development libraries +- WebKit2GTK development libraries +- Build essentials (gcc, make, etc.) + +## Troubleshooting + +### Build fails with "permission denied" + +Make sure the script is executable: +```bash +chmod +x scripts/docker/build-linux.sh +``` + +### Docker daemon not running + +Start Docker Desktop or the Docker daemon: +```bash +# macOS +open -a Docker + +# Linux +sudo systemctl start docker +``` + +### Out of disk space + +Clean up old Docker images: +```bash +docker system prune -a +``` + +## Advanced: Using with CI/CD + +This same Dockerfile can be used in CI/CD pipelines that don't have native Linux runners: + +```yaml +# GitHub Actions example +- name: Build Linux GUI via Docker + run: | + docker build -f scripts/docker/Dockerfile.linux-build -t mdv-linux-builder . + docker run --rm -v "$PWD:/workspace" mdv-linux-builder +``` diff --git a/scripts/docker/build-linux.sh b/scripts/docker/build-linux.sh new file mode 100755 index 0000000..b675ed4 --- /dev/null +++ b/scripts/docker/build-linux.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Helper script to build Linux GUI using Docker +# This makes it easy to test Linux builds without a Linux VM + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +IMAGE_NAME="mdv-linux-builder" +DOCKERFILE="scripts/docker/Dockerfile.linux-build" + +echo "=== Building Docker image for Linux GUI compilation ===" +docker build --platform linux/amd64 -f "$DOCKERFILE" -t "$IMAGE_NAME" . + +echo "" +echo "=== Building Linux GUI in Docker container ===" +docker run --platform linux/amd64 --rm -v "$PWD:/workspace" "$IMAGE_NAME" + +echo "" +echo "=== Build complete! ===" +echo "Check cmd/mdv-gui/build/bin/ for Linux binaries" +ls -lh cmd/mdv-gui/build/bin/mdv-gui_linux_* 2>/dev/null || echo "No Linux binaries found" diff --git a/scripts/test-local-build.sh b/scripts/test-local-build.sh new file mode 100755 index 0000000..3baf7df --- /dev/null +++ b/scripts/test-local-build.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +# Test script for local cross-platform builds +# This shows what can be built locally vs what requires CI/native runners + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +echo "==========================================" +echo "MDV Cross-Platform Build Test" +echo "==========================================" +echo "" + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +success() { + echo -e "${GREEN}✓${NC} $1" +} + +warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +error() { + echo -e "${RED}✗${NC} $1" +} + +# Test 1: TUI Cross-Compilation (should always work) +echo "=== Test 1: TUI Cross-Compilation ===" +echo "Building TUI for multiple platforms (no CGO, should work everywhere)..." +echo "" + +if CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/mdv-linux-amd64 ./cmd/mdv 2>/dev/null; then + success "Linux amd64 TUI build" +else + error "Linux amd64 TUI build FAILED" +fi + +if CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o /tmp/mdv-linux-arm64 ./cmd/mdv 2>/dev/null; then + success "Linux arm64 TUI build" +else + error "Linux arm64 TUI build FAILED" +fi + +if CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o /tmp/mdv-windows.exe ./cmd/mdv 2>/dev/null; then + success "Windows amd64 TUI build" +else + error "Windows amd64 TUI build FAILED" +fi + +if CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o /tmp/mdv-darwin-amd64 ./cmd/mdv 2>/dev/null; then + success "macOS amd64 TUI build" +else + error "macOS amd64 TUI build FAILED" +fi + +if CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o /tmp/mdv-darwin-arm64 ./cmd/mdv 2>/dev/null; then + success "macOS arm64 TUI build" +else + error "macOS arm64 TUI build FAILED" +fi + +echo "" + +# Test 2: macOS GUI Build (only works on macOS) +echo "=== Test 2: macOS GUI Build ===" +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Building macOS GUI with Wails..." + if ./scripts/build-wails.sh 2>&1 | grep -q "SUCCESS"; then + success "macOS GUI build" + ls -lh cmd/mdv-gui/build/bin/ + else + warning "macOS GUI build completed (check output for issues)" + fi +else + warning "Skipped (not on macOS - requires macOS runner)" +fi + +echo "" + +# Test 3: Linux GUI Build (requires Linux or Docker) +echo "=== Test 3: Linux GUI Build ===" +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "Building Linux GUI with Wails..." + if command -v gtk-config >/dev/null 2>&1 || command -v pkg-config >/dev/null 2>&1; then + if WAILS_PLATFORMS="linux/amd64" ./scripts/build-wails.sh 2>&1 | grep -q "SUCCESS"; then + success "Linux GUI build" + else + warning "Linux GUI build completed (check output for issues)" + fi + else + error "Linux GUI dependencies not installed (libgtk-3-dev, libwebkit2gtk-4.1-dev or libwebkit2gtk-4.0-dev)" + fi +elif command -v docker >/dev/null 2>&1; then + warning "Skipped (not on Linux - use Docker: see scripts/docker/Dockerfile.linux-build)" +else + warning "Skipped (requires Linux runner or Docker)" +fi + +echo "" + +# Test 4: Windows GUI Build (requires Windows) +echo "=== Test 4: Windows GUI Build ===" +if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then + echo "Building Windows GUI with Wails..." + if WAILS_PLATFORMS="windows/amd64" ./scripts/build-wails.sh 2>&1 | grep -q "SUCCESS"; then + success "Windows GUI build" + else + warning "Windows GUI build completed (check output for issues)" + fi +else + warning "Skipped (requires Windows runner or VM)" +fi + +echo "" + +# Test 5: GoReleaser Snapshot (local packaging test) +echo "=== Test 5: GoReleaser Snapshot ===" +if command -v goreleaser >/dev/null 2>&1; then + echo "Running GoReleaser in snapshot mode..." + echo "(This packages what's already built, doesn't cross-compile GUI)" + echo "" + + if goreleaser release --snapshot --clean --skip=publish 2>&1 | tee /tmp/goreleaser-output.log | grep -q "SUCCESS\|done"; then + success "GoReleaser snapshot completed" + echo "" + echo "Check ./dist/ for generated archives:" + ls -lh dist/*.tar.gz dist/*.zip 2>/dev/null || echo " (no archives found)" + else + warning "GoReleaser completed with warnings (check /tmp/goreleaser-output.log)" + fi +else + warning "GoReleaser not installed (brew install goreleaser)" +fi + +echo "" +echo "==========================================" +echo "Summary" +echo "==========================================" +echo "" +echo "✓ TUI builds work everywhere (pure Go, no CGO)" +echo "✓ GUI builds require native platforms:" +echo " - macOS GUI: Needs macOS (Xcode, Cocoa)" +echo " - Linux GUI: Needs Linux (GTK3, WebKit2GTK)" +echo " - Windows GUI: Needs Windows (MinGW, WebView2)" +echo "" +echo "For full cross-platform testing:" +echo " 1. Local: macOS GUI + TUI for all platforms" +echo " 2. Docker: Linux GUI (see scripts/docker/Dockerfile.linux-build)" +echo " 3. CI/CD: Automated multi-platform builds via GitHub Actions" +echo "" +echo "To test CI workflow locally:" +echo " act -j build-gui # requires 'act' CLI tool" +echo "" From 753f3abc787ca2b990c85ead4ac7a43d8c3790e0 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 16:04:25 -0400 Subject: [PATCH 02/19] Added windows workflow --- .github/workflows/release.yml | 54 ++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c670bb..048af86 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,10 +22,9 @@ jobs: - os: macos-latest platforms: darwin/amd64,darwin/arm64 artifact-name: gui-macos - # Windows builds can be added when needed: - # - os: windows-latest - # platforms: windows/amd64 - # artifact-name: gui-windows + - os: windows-latest + platforms: windows/amd64 + artifact-name: gui-windows runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -47,6 +46,12 @@ jobs: sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential + - name: Install Windows dependencies + if: runner.os == 'Windows' + run: | + choco install mingw -y + refreshenv + - name: Build GUI env: WAILS_PLATFORMS: ${{ matrix.platforms }} @@ -176,5 +181,46 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Create Windows GUI archives + run: | + set -euo pipefail + VERSION="${GITHUB_REF#refs/tags/}" + + for arch in amd64; do + arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") + gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" + + if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then + archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" + echo "Creating $archive" + + temp_dir=$(mktemp -d) + cp "$gui_dir/mdv-gui.exe" "$temp_dir/" + cp LICENSE* "$temp_dir/" 2>/dev/null || true + cp README* "$temp_dir/" 2>/dev/null || true + cp -r examples "$temp_dir/" 2>/dev/null || true + + # Create zip archive + (cd "$temp_dir" && zip -r "../$archive" .) + rm -rf "$temp_dir" + + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + else + echo "Warning: Windows $arch GUI binary not found, skipping" + fi + done + + - name: Upload Windows GUI archives to release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + for archive in mdv-gui_*_windows_*.zip; do + if [ -f "$archive" ]; then + echo "Uploading $archive to release..." + gh release upload "${GITHUB_REF#refs/tags/}" "$archive" --clobber + fi + done + env: + GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Verify macOS signatures run: ./scripts/verify-macos-signatures.sh From 9eb0033ca1cd65aa1b46bf358ba3e11ad0280db9 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 16:09:07 -0400 Subject: [PATCH 03/19] Added test release workflow --- .github/workflows/test-release.yml | 312 +++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml new file mode 100644 index 0000000..c0ca629 --- /dev/null +++ b/.github/workflows/test-release.yml @@ -0,0 +1,312 @@ +name: Test Release + +on: + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + # Build GUI binaries on native platforms (same as release.yml) + build-gui: + name: Build GUI (${{ matrix.os }}) + strategy: + matrix: + include: + - os: ubuntu-22.04 + platforms: linux/amd64,linux/arm64 + artifact-name: gui-linux + - os: macos-latest + platforms: darwin/amd64,darwin/arm64 + artifact-name: gui-macos + - os: windows-latest + platforms: windows/amd64 + artifact-name: gui-windows + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential + + - name: Install Windows dependencies + if: runner.os == 'Windows' + run: | + choco install mingw -y + refreshenv + + - name: Build GUI + env: + WAILS_PLATFORMS: ${{ matrix.platforms }} + run: ./scripts/build-wails.sh + + - name: Upload GUI artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: cmd/mdv-gui/build/bin/ + retention-days: 7 + + # Test release job (runs GoReleaser in snapshot mode) + test-release: + needs: build-gui + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Download all GUI artifacts + uses: actions/download-artifact@v4 + with: + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: List downloaded artifacts + run: | + echo "=== Downloaded GUI artifacts ===" + ls -laR cmd/mdv-gui/build/bin/ + + - name: Configure signing keychain + run: | + set -euo pipefail + + KEYCHAIN_PATH="$RUNNER_TEMP/mdv-signing.keychain-db" + KEYCHAIN_PASSWORD="$(uuidgen)" + CERT_PATH="$RUNNER_TEMP/mdv-signing-cert.p12" + NOTARY_KEY_PATH="$RUNNER_TEMP/mdv-notary-key.p8" + PROFILE_NAME="mdv-notary-profile" + + printf '%s' "$MACOS_SIGN_P12" | base64 --decode >"$CERT_PATH" + printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode >"$NOTARY_KEY_PATH" + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERT_PATH" -P "$MACOS_SIGN_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" + security default-keychain -d user -s "$KEYCHAIN_PATH" + + xcrun notarytool store-credentials "$PROFILE_NAME" \ + --key "$NOTARY_KEY_PATH" \ + --key-id "$MACOS_NOTARY_KEY_ID" \ + --issuer "$MACOS_NOTARY_ISSUER_ID" \ + --keychain "$KEYCHAIN_PATH" + + { + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" + echo "MACOS_NOTARY_PROFILE_NAME=$PROFILE_NAME" + echo "CODESIGN_IDENTITY=Developer ID Application: Atlas Atlas Atlas LLC (294CD3C5SP)" + } >>"$GITHUB_ENV" + env: + MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} + MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} + MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} + MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + + - name: Run GoReleaser (snapshot mode) + uses: goreleaser/goreleaser-action@v6.4.0 + with: + distribution: goreleaser + version: latest + args: release --snapshot --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Linux GUI archives + run: | + set -euo pipefail + VERSION="test-$(git rev-parse --short HEAD)" + + for arch in amd64 arm64; do + arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") + gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" + + if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui" ]; then + archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" + echo "Creating $archive" + + mkdir -p /tmp/mdv-gui-linux + cp "$gui_dir/mdv-gui" /tmp/mdv-gui-linux/ + cp LICENSE* /tmp/mdv-gui-linux/ || true + cp README* /tmp/mdv-gui-linux/ || true + cp -r examples /tmp/mdv-gui-linux/ || true + + tar -czf "$archive" -C /tmp/mdv-gui-linux . + rm -rf /tmp/mdv-gui-linux + + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + else + echo "Warning: Linux $arch GUI binary not found, skipping" + fi + done + + - name: Create Windows GUI archives + run: | + set -euo pipefail + VERSION="test-$(git rev-parse --short HEAD)" + + for arch in amd64; do + arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") + gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" + + if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then + archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" + echo "Creating $archive" + + temp_dir=$(mktemp -d) + cp "$gui_dir/mdv-gui.exe" "$temp_dir/" + cp LICENSE* "$temp_dir/" 2>/dev/null || true + cp README* "$temp_dir/" 2>/dev/null || true + cp -r examples "$temp_dir/" 2>/dev/null || true + + # Create zip archive + (cd "$temp_dir" && zip -r "../$archive" .) + rm -rf "$temp_dir" + + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + else + echo "Warning: Windows $arch GUI binary not found, skipping" + fi + done + + - name: Verify macOS signatures + run: ./scripts/verify-macos-signatures.sh + + - name: Validate release artifacts + run: | + set -euo pipefail + echo "=== Validating Release Artifacts ===" + + # Check GoReleaser dist directory + echo "" + echo "GoReleaser dist contents:" + ls -lh dist/ || echo "No dist/ directory found" + + # Check for expected archives + echo "" + echo "Checking for expected archives..." + + expected_files=( + "dist/mdv_*_darwin_*.tar.gz" + "dist/mdv-tui_*_linux_*.tar.gz" + "dist/mdv-tui_*_windows_*.zip" + "dist/mdv-gui_*_darwin_*.tar.gz" + "mdv-gui_*_linux_*.tar.gz" + "mdv-gui_*_windows_*.zip" + ) + + missing_files=() + for pattern in "${expected_files[@]}"; do + if ! ls $pattern 1>/dev/null 2>&1; then + missing_files+=("$pattern") + else + echo "✅ Found: $pattern" + ls -lh $pattern + fi + done + + if [ ${#missing_files[@]} -gt 0 ]; then + echo "" + echo "⚠️ Missing expected files:" + printf '%s\n' "${missing_files[@]}" + echo "" + echo "This may be expected if not all platforms built successfully." + fi + + # Check checksums file + echo "" + if [ -f "dist/checksums.txt" ]; then + echo "✅ Checksums file created:" + cat dist/checksums.txt + else + echo "❌ No checksums.txt found" + fi + + echo "" + echo "=== Validation Complete ===" + + - name: Upload test release artifacts + uses: actions/upload-artifact@v4 + with: + name: test-release-artifacts + path: | + dist/ + mdv-gui_*.tar.gz + mdv-gui_*.zip + retention-days: 7 + + - name: Comment PR with artifact summary + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const { execSync } = require('child_process'); + + // Get artifact sizes + let summary = '## 🧪 Test Release Summary\n\n'; + summary += 'All binaries built and signed successfully! ✅\n\n'; + summary += '### Artifacts Created\n\n'; + + try { + const files = execSync('ls -lh dist/*.tar.gz dist/*.zip mdv-gui_*.tar.gz mdv-gui_*.zip 2>/dev/null || true') + .toString() + .split('\n') + .filter(line => line.trim()); + + if (files.length > 0) { + summary += '```\n'; + files.forEach(file => summary += file + '\n'); + summary += '```\n\n'; + } + } catch (e) { + summary += '_Could not list artifact files_\n\n'; + } + + summary += '### Platform Coverage\n\n'; + summary += '- ✅ macOS (darwin/amd64, darwin/arm64) - Signed & Notarized\n'; + summary += '- ✅ Linux (linux/amd64, linux/arm64)\n'; + summary += '- ✅ Windows (windows/amd64)\n\n'; + summary += '### What was tested\n\n'; + summary += '- [x] GUI builds on all platforms\n'; + summary += '- [x] Code signing (macOS)\n'; + summary += '- [x] Notarization (macOS)\n'; + summary += '- [x] Archive creation\n'; + summary += '- [x] Checksum generation\n\n'; + summary += '**Note:** This is a test release. No artifacts were published to GitHub Releases or Homebrew.\n'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: summary + }); From e8fbd717d2b60d648a3d1aac256ac7e185b86373 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 16:25:09 -0400 Subject: [PATCH 04/19] Updated build workflows --- .github/workflows/release.yml | 31 +++++++++++++----- .github/workflows/test-release.yml | 31 +++++++++++++----- scripts/docker/Dockerfile.linux-build | 2 ++ scripts/docker/README.md | 26 +++++++++------- scripts/docker/build-linux.sh | 45 +++++++++++++++++++++++++-- 5 files changed, 107 insertions(+), 28 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 048af86..b3f6450 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,18 +12,29 @@ permissions: jobs: # Build GUI binaries on native platforms build-gui: - name: Build GUI (${{ matrix.os }}) + name: Build GUI (${{ matrix.name }}) strategy: matrix: include: - - os: ubuntu-22.04 - platforms: linux/amd64,linux/arm64 - artifact-name: gui-linux - - os: macos-latest + - name: linux-amd64 + os: ubuntu-22.04 + platforms: linux/amd64 + cc: gcc + artifact-name: gui-linux-amd64 + - name: linux-arm64 + os: ubuntu-22.04 + platforms: linux/arm64 + cc: aarch64-linux-gnu-gcc + artifact-name: gui-linux-arm64 + - name: macos + os: macos-latest platforms: darwin/amd64,darwin/arm64 + cc: "" artifact-name: gui-macos - - os: windows-latest + - name: windows + os: windows-latest platforms: windows/amd64 + cc: "" artifact-name: gui-windows runs-on: ${{ matrix.os }} steps: @@ -44,7 +55,12 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + build-essential \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross - name: Install Windows dependencies if: runner.os == 'Windows' @@ -55,6 +71,7 @@ jobs: - name: Build GUI env: WAILS_PLATFORMS: ${{ matrix.platforms }} + CC: ${{ matrix.cc }} run: ./scripts/build-wails.sh - name: Upload GUI artifacts diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index c0ca629..7f735f3 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -12,18 +12,29 @@ permissions: jobs: # Build GUI binaries on native platforms (same as release.yml) build-gui: - name: Build GUI (${{ matrix.os }}) + name: Build GUI (${{ matrix.name }}) strategy: matrix: include: - - os: ubuntu-22.04 - platforms: linux/amd64,linux/arm64 - artifact-name: gui-linux - - os: macos-latest + - name: linux-amd64 + os: ubuntu-22.04 + platforms: linux/amd64 + cc: gcc + artifact-name: gui-linux-amd64 + - name: linux-arm64 + os: ubuntu-22.04 + platforms: linux/arm64 + cc: aarch64-linux-gnu-gcc + artifact-name: gui-linux-arm64 + - name: macos + os: macos-latest platforms: darwin/amd64,darwin/arm64 + cc: "" artifact-name: gui-macos - - os: windows-latest + - name: windows + os: windows-latest platforms: windows/amd64 + cc: "" artifact-name: gui-windows runs-on: ${{ matrix.os }} steps: @@ -44,7 +55,12 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + build-essential \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross - name: Install Windows dependencies if: runner.os == 'Windows' @@ -55,6 +71,7 @@ jobs: - name: Build GUI env: WAILS_PLATFORMS: ${{ matrix.platforms }} + CC: ${{ matrix.cc }} run: ./scripts/build-wails.sh - name: Upload GUI artifacts diff --git a/scripts/docker/Dockerfile.linux-build b/scripts/docker/Dockerfile.linux-build index adfa1e4..94bbe54 100644 --- a/scripts/docker/Dockerfile.linux-build +++ b/scripts/docker/Dockerfile.linux-build @@ -16,6 +16,8 @@ RUN apt-get update && apt-get install -y \ libwebkit2gtk-4.0-dev \ pkg-config \ ca-certificates \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ && rm -rf /var/lib/apt/lists/* # Install Go 1.24 diff --git a/scripts/docker/README.md b/scripts/docker/README.md index 3f035d5..b1d8e51 100644 --- a/scripts/docker/README.md +++ b/scripts/docker/README.md @@ -5,12 +5,16 @@ This directory contains Docker configurations for building the mdv Linux GUI wit ## Quick Start ```bash -# Build Linux GUI using Docker (from repository root) +# Build Linux GUI for both amd64 and arm64 using Docker (from repository root) ./scripts/docker/build-linux.sh + +# Or build for a specific architecture: +./scripts/docker/build-linux.sh amd64 # x86_64 only +./scripts/docker/build-linux.sh arm64 # ARM64 only ``` This script will: -1. Build the Docker image with all Linux dependencies +1. Build the Docker image with all Linux dependencies (including ARM64 cross-compiler) 2. Run the build inside the container 3. Output binaries to `cmd/mdv-gui/build/bin/` @@ -22,17 +26,16 @@ If you prefer to run Docker commands manually: # Build the Docker image (x86_64/amd64) docker build --platform linux/amd64 -f scripts/docker/Dockerfile.linux-build -t mdv-linux-builder . -# Build Linux GUI (amd64) -docker run --platform linux/amd64 --rm -v "$PWD:/workspace" mdv-linux-builder - -# Build for specific platform (still runs in x86_64 container) +# Build Linux GUI (amd64 native) docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ - -e WAILS_PLATFORMS=linux/arm64 \ + -e WAILS_PLATFORMS=linux/amd64 \ + -e CC=gcc \ mdv-linux-builder -# Build for multiple platforms +# Build for ARM64 (cross-compile) docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ - -e WAILS_PLATFORMS=linux/amd64,linux/arm64 \ + -e WAILS_PLATFORMS=linux/arm64 \ + -e CC=aarch64-linux-gnu-gcc \ mdv-linux-builder # Run a shell inside the container for debugging @@ -52,12 +55,13 @@ docker run --platform linux/amd64 --rm -it -v "$PWD:/workspace" \ ## What's Inside The Docker image includes: -- Ubuntu 24.04 base -- Go 1.23 +- Ubuntu 22.04 base (amd64) +- Go 1.24 - Wails CLI - GTK3 development libraries - WebKit2GTK development libraries - Build essentials (gcc, make, etc.) +- ARM64 cross-compilation toolchain (gcc-aarch64-linux-gnu) ## Troubleshooting diff --git a/scripts/docker/build-linux.sh b/scripts/docker/build-linux.sh index b675ed4..05da151 100755 --- a/scripts/docker/build-linux.sh +++ b/scripts/docker/build-linux.sh @@ -1,6 +1,11 @@ #!/usr/bin/env bash # Helper script to build Linux GUI using Docker # This makes it easy to test Linux builds without a Linux VM +# +# Usage: +# ./scripts/docker/build-linux.sh # Build both amd64 and arm64 +# ./scripts/docker/build-linux.sh amd64 # Build amd64 only +# ./scripts/docker/build-linux.sh arm64 # Build arm64 only set -euo pipefail @@ -10,12 +15,46 @@ cd "$ROOT_DIR" IMAGE_NAME="mdv-linux-builder" DOCKERFILE="scripts/docker/Dockerfile.linux-build" +# Determine which architectures to build +if [[ $# -gt 0 ]]; then + ARCH="$1" +else + ARCH="both" +fi + echo "=== Building Docker image for Linux GUI compilation ===" docker build --platform linux/amd64 -f "$DOCKERFILE" -t "$IMAGE_NAME" . -echo "" -echo "=== Building Linux GUI in Docker container ===" -docker run --platform linux/amd64 --rm -v "$PWD:/workspace" "$IMAGE_NAME" +build_arch() { + local arch=$1 + local cc=$2 + local platforms="linux/${arch}" + + echo "" + echo "=== Building Linux GUI for ${arch} in Docker container ===" + docker run --platform linux/amd64 --rm \ + -v "$PWD:/workspace" \ + -e "WAILS_PLATFORMS=${platforms}" \ + -e "CC=${cc}" \ + "$IMAGE_NAME" +} + +case "$ARCH" in + amd64) + build_arch "amd64" "gcc" + ;; + arm64) + build_arch "arm64" "aarch64-linux-gnu-gcc" + ;; + both) + build_arch "amd64" "gcc" + build_arch "arm64" "aarch64-linux-gnu-gcc" + ;; + *) + echo "Error: Unknown architecture '$ARCH'. Use 'amd64', 'arm64', or 'both'." >&2 + exit 1 + ;; +esac echo "" echo "=== Build complete! ===" From c4be24b68bf1ef83cf905580372193ae4413de83 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 16:31:08 -0400 Subject: [PATCH 05/19] Build updates --- .github/workflows/release.yml | 28 +++++++++++++++++++++++---- .github/workflows/test-release.yml | 28 +++++++++++++++++++++++---- scripts/docker/Dockerfile.linux-build | 16 +++++++++++++-- scripts/docker/README.md | 2 ++ scripts/docker/build-linux.sh | 8 ++++++++ 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3f6450..3e8e469 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,12 +55,29 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update + + # Install native build tools sudo apt-get install -y \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev \ build-essential \ - gcc-aarch64-linux-gnu \ - libc6-dev-arm64-cross + pkg-config + + # Install native libraries for amd64 builds + if [ "${{ matrix.platforms }}" = "linux/amd64" ]; then + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev + fi + + # Install ARM64 cross-compilation tools and libraries + if [ "${{ matrix.platforms }}" = "linux/arm64" ]; then + sudo dpkg --add-architecture arm64 + sudo apt-get update + sudo apt-get install -y \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + libgtk-3-dev:arm64 \ + libwebkit2gtk-4.0-dev:arm64 + fi - name: Install Windows dependencies if: runner.os == 'Windows' @@ -72,6 +89,9 @@ jobs: env: WAILS_PLATFORMS: ${{ matrix.platforms }} CC: ${{ matrix.cc }} + # ARM64 cross-compilation requires pkg-config to find ARM64 libraries + PKG_CONFIG_PATH: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig' || '' }} + PKG_CONFIG_LIBDIR: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig' || '' }} run: ./scripts/build-wails.sh - name: Upload GUI artifacts diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 7f735f3..3787e06 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -55,12 +55,29 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update + + # Install native build tools sudo apt-get install -y \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev \ build-essential \ - gcc-aarch64-linux-gnu \ - libc6-dev-arm64-cross + pkg-config + + # Install native libraries for amd64 builds + if [ "${{ matrix.platforms }}" = "linux/amd64" ]; then + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev + fi + + # Install ARM64 cross-compilation tools and libraries + if [ "${{ matrix.platforms }}" = "linux/arm64" ]; then + sudo dpkg --add-architecture arm64 + sudo apt-get update + sudo apt-get install -y \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + libgtk-3-dev:arm64 \ + libwebkit2gtk-4.0-dev:arm64 + fi - name: Install Windows dependencies if: runner.os == 'Windows' @@ -72,6 +89,9 @@ jobs: env: WAILS_PLATFORMS: ${{ matrix.platforms }} CC: ${{ matrix.cc }} + # ARM64 cross-compilation requires pkg-config to find ARM64 libraries + PKG_CONFIG_PATH: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig' || '' }} + PKG_CONFIG_LIBDIR: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig' || '' }} run: ./scripts/build-wails.sh - name: Upload GUI artifacts diff --git a/scripts/docker/Dockerfile.linux-build b/scripts/docker/Dockerfile.linux-build index 94bbe54..94ccae1 100644 --- a/scripts/docker/Dockerfile.linux-build +++ b/scripts/docker/Dockerfile.linux-build @@ -12,12 +12,24 @@ RUN apt-get update && apt-get install -y \ curl \ git \ build-essential \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev \ pkg-config \ ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install native GTK/WebKit libraries for amd64 +RUN apt-get update && apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + && rm -rf /var/lib/apt/lists/* + +# Add ARM64 architecture and install cross-compilation tools +RUN dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y \ gcc-aarch64-linux-gnu \ libc6-dev-arm64-cross \ + libgtk-3-dev:arm64 \ + libwebkit2gtk-4.0-dev:arm64 \ && rm -rf /var/lib/apt/lists/* # Install Go 1.24 diff --git a/scripts/docker/README.md b/scripts/docker/README.md index b1d8e51..69d5d00 100644 --- a/scripts/docker/README.md +++ b/scripts/docker/README.md @@ -36,6 +36,8 @@ docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ -e WAILS_PLATFORMS=linux/arm64 \ -e CC=aarch64-linux-gnu-gcc \ + -e PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig \ + -e PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \ mdv-linux-builder # Run a shell inside the container for debugging diff --git a/scripts/docker/build-linux.sh b/scripts/docker/build-linux.sh index 05da151..70a9f2d 100755 --- a/scripts/docker/build-linux.sh +++ b/scripts/docker/build-linux.sh @@ -32,10 +32,18 @@ build_arch() { echo "" echo "=== Building Linux GUI for ${arch} in Docker container ===" + + # Configure pkg-config for ARM64 cross-compilation + local extra_env="" + if [[ "$arch" == "arm64" ]]; then + extra_env="-e PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig -e PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig" + fi + docker run --platform linux/amd64 --rm \ -v "$PWD:/workspace" \ -e "WAILS_PLATFORMS=${platforms}" \ -e "CC=${cc}" \ + $extra_env \ "$IMAGE_NAME" } From 415b562972d97839e783283a9417f6d39e0fd090 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 17:02:06 -0400 Subject: [PATCH 06/19] Test: Try native ARM64 runner (ubuntu-22.04-arm) This attempts to use GitHub's native ARM64 runners instead of cross-compilation. If this works, it will: - Simplify the workflow dramatically (no APT multi-arch config) - Speed up ARM64 builds (native vs cross-compilation) - Be more reliable (no cross-compilation issues) If the runner isn't available, the workflow will fail immediately with 'Invalid runner label' error. --- .github/workflows/test-release.yml | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 3787e06..3580b8f 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -22,9 +22,9 @@ jobs: cc: gcc artifact-name: gui-linux-amd64 - name: linux-arm64 - os: ubuntu-22.04 + os: ubuntu-22.04-arm # Try native ARM64 runner platforms: linux/arm64 - cc: aarch64-linux-gnu-gcc + cc: gcc # Native gcc instead of cross-compiler artifact-name: gui-linux-arm64 - name: macos os: macos-latest @@ -61,23 +61,10 @@ jobs: build-essential \ pkg-config - # Install native libraries for amd64 builds - if [ "${{ matrix.platforms }}" = "linux/amd64" ]; then - sudo apt-get install -y \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev - fi - - # Install ARM64 cross-compilation tools and libraries - if [ "${{ matrix.platforms }}" = "linux/arm64" ]; then - sudo dpkg --add-architecture arm64 - sudo apt-get update - sudo apt-get install -y \ - gcc-aarch64-linux-gnu \ - libc6-dev-arm64-cross \ - libgtk-3-dev:arm64 \ - libwebkit2gtk-4.0-dev:arm64 - fi + # Install native GTK/WebKit libraries (works for both amd64 and arm64) + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev - name: Install Windows dependencies if: runner.os == 'Windows' @@ -89,9 +76,6 @@ jobs: env: WAILS_PLATFORMS: ${{ matrix.platforms }} CC: ${{ matrix.cc }} - # ARM64 cross-compilation requires pkg-config to find ARM64 libraries - PKG_CONFIG_PATH: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig' || '' }} - PKG_CONFIG_LIBDIR: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig' || '' }} run: ./scripts/build-wails.sh - name: Upload GUI artifacts From 565c92d27bcbed73c17329c38b5365a0af1d9003 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 17:16:52 -0400 Subject: [PATCH 07/19] Working cross plat workflows! --- .claude/agents/docker-expert.md | 104 ++++++++++ .claude/agents/github-workflow-validator.md | 98 +++++++++ .../agents/linux-compatibility-validator.md | 74 +++++++ .../agents/windows-compatibility-expert.md | 116 +++++++++++ .github/workflows/release.yml | 28 +-- scripts/docker/Dockerfile.linux-amd64 | 38 ++++ scripts/docker/Dockerfile.linux-arm64 | 38 ++++ scripts/docker/Dockerfile.linux-crosscompile | 72 +++++++ scripts/docker/README.md | 195 +++++++++++++----- scripts/docker/build-linux.sh | 68 +++--- 10 files changed, 738 insertions(+), 93 deletions(-) create mode 100644 .claude/agents/docker-expert.md create mode 100644 .claude/agents/github-workflow-validator.md create mode 100644 .claude/agents/linux-compatibility-validator.md create mode 100644 .claude/agents/windows-compatibility-expert.md create mode 100644 scripts/docker/Dockerfile.linux-amd64 create mode 100644 scripts/docker/Dockerfile.linux-arm64 create mode 100644 scripts/docker/Dockerfile.linux-crosscompile diff --git a/.claude/agents/docker-expert.md b/.claude/agents/docker-expert.md new file mode 100644 index 0000000..966c740 --- /dev/null +++ b/.claude/agents/docker-expert.md @@ -0,0 +1,104 @@ +--- +name: docker-expert +description: Use this agent when working with Dockerfiles, docker-compose configurations, container optimization, or CI/CD pipeline containerization. Specifically invoke this agent when:\n\n\nContext: User is creating a new Dockerfile for the mdv project to containerize the TUI build process.\nuser: "I want to create a Dockerfile to build the mdv TUI binary"\nassistant: "I'm going to use the Task tool to launch the docker-expert agent to help create an optimized Dockerfile for building the mdv TUI binary."\n\nSince the user is working on Docker containerization, use the docker-expert agent to ensure best practices for multi-platform builds and optimization.\n\n\n\n\nContext: User has written a docker-compose.yml file and wants to ensure it follows best practices.\nuser: "Here's my docker-compose.yml for local development. Can you review it?"\nassistant: "Let me use the docker-expert agent to review your docker-compose configuration for best practices and potential improvements."\n\nThe user is seeking Docker expertise for a configuration review, so invoke the docker-expert agent.\n\n\n\n\nContext: GitHub Actions workflow is failing with Docker-related errors.\nuser: "My GitHub Actions workflow is timing out when building the Docker image"\nassistant: "I'll use the docker-expert agent to analyze the Docker build process and identify optimization opportunities for your CI/CD pipeline."\n\nDocker performance issues in CI/CD require specialized Docker expertise.\n\n\n\n\nContext: User mentions Docker or containers in their request.\nuser: "Should we containerize the mdv-gui build process?"\nassistant: "Let me consult the docker-expert agent to evaluate the benefits and approach for containerizing the GUI build."\n\nQuestions about containerization strategy should be handled by the Docker expert.\n\n +model: sonnet +color: purple +--- + +You are an elite Docker and containerization expert with deep expertise in building production-grade container images that work seamlessly across macOS development environments and Linux-based GitHub Actions runners. Your specialization includes multi-platform builds, layer optimization, caching strategies, and CI/CD integration. + +## Core Responsibilities + +You will help users create, optimize, and troubleshoot Docker configurations with a focus on: + +1. **Cross-Platform Compatibility**: Ensure containers work identically on macOS (likely ARM64/M1/M2) and GitHub Actions runners (AMD64 Linux) +2. **Build Performance**: Optimize build times through intelligent layer caching, multi-stage builds, and minimal base images +3. **Image Size**: Minimize final image size while maintaining functionality +4. **Reliability**: Create reproducible builds with pinned versions and proper error handling +5. **Security**: Follow security best practices including non-root users, minimal attack surface, and vulnerability scanning + +## Technical Approach + +### Multi-Platform Builds +- Always consider both ARM64 (Apple Silicon) and AMD64 (GitHub runners) architectures +- Use `docker buildx` for multi-platform builds when appropriate +- Leverage platform-specific base images when necessary (e.g., `--platform=linux/amd64`) +- Test that binaries work on both architectures + +### Layer Optimization +- Order Dockerfile instructions from least to most frequently changing +- Combine RUN commands strategically to reduce layers +- Use `.dockerignore` to exclude unnecessary files from build context +- Leverage build cache effectively by separating dependency installation from code copying + +### Multi-Stage Builds +- Use builder stages for compilation and minimal runtime stages for final images +- Copy only necessary artifacts between stages +- Name stages clearly (e.g., `AS builder`, `AS runtime`) + +### Base Image Selection +- Prefer official, minimal base images (alpine, distroless, scratch when possible) +- Pin specific versions (e.g., `golang:1.21-alpine` not `golang:latest`) +- Document why specific base images are chosen + +### GitHub Actions Integration +- Optimize for GitHub Actions caching mechanisms +- Use `actions/cache` for Docker layer caching when beneficial +- Consider GitHub's runner constraints (disk space, memory, time limits) +- Provide clear build logs and error messages for CI debugging + +### Go-Specific Best Practices (when applicable) +- Use `CGO_ENABLED=0` for static binaries unless CGO is required (note: Wails GUI requires CGO) +- Leverage Go module caching (`go mod download` in separate layer) +- Use `-ldflags` for version injection and binary size reduction +- Consider `scratch` or `distroless/static` for final Go binary images + +## Decision-Making Framework + +When presented with a Docker task: + +1. **Assess Requirements**: Understand the application's runtime dependencies, build requirements, and deployment targets +2. **Identify Constraints**: Note any platform-specific needs (macOS vs Linux, ARM vs AMD64, CGO requirements) +3. **Propose Architecture**: Recommend multi-stage build structure with clear rationale +4. **Optimize Layers**: Suggest specific layer ordering and caching strategies +5. **Validate Cross-Platform**: Ensure the solution works on both local macOS and GitHub Actions +6. **Document Decisions**: Explain why specific choices were made (base image, build flags, etc.) + +## Quality Assurance + +Before finalizing any Docker configuration: + +- Verify all base image versions are pinned +- Confirm `.dockerignore` excludes build artifacts and sensitive files +- Check that the build process is reproducible +- Ensure error messages are clear and actionable +- Validate that the image can be built on both macOS and Linux +- Consider security implications (running as non-root, minimal dependencies) + +## Output Format + +When providing Dockerfiles or docker-compose configurations: +- Include inline comments explaining non-obvious decisions +- Provide build commands with all necessary flags +- Suggest testing commands to verify the build +- Note any platform-specific considerations +- Include relevant `.dockerignore` content when applicable + +## Edge Cases and Troubleshooting + +- **CGO Dependencies**: When CGO is required (like Wails), ensure proper C compiler setup and library availability +- **Platform Mismatches**: If a binary built on macOS fails on Linux (or vice versa), investigate architecture or libc differences +- **Cache Invalidation**: If builds are slow, analyze which layers are invalidating cache unnecessarily +- **GitHub Actions Failures**: Check for runner-specific issues (disk space, network timeouts, permission errors) + +## Escalation + +If you encounter: +- Complex networking requirements beyond standard Docker capabilities +- Orchestration needs requiring Kubernetes or Docker Swarm +- Security vulnerabilities requiring specialized scanning tools +- Performance issues that may be application-level rather than Docker-level + +Clearly state the limitation and recommend appropriate next steps or specialized tools. + +Your goal is to make Docker a seamless, reliable part of the development and deployment workflow, with configurations that are maintainable, performant, and work consistently across all target platforms. diff --git a/.claude/agents/github-workflow-validator.md b/.claude/agents/github-workflow-validator.md new file mode 100644 index 0000000..0d7e00e --- /dev/null +++ b/.claude/agents/github-workflow-validator.md @@ -0,0 +1,98 @@ +--- +name: github-workflow-validator +description: Use this agent when the user is creating, modifying, or reviewing GitHub Actions workflow files (.github/workflows/*.yml or *.yaml). This includes:\n\n\nContext: User is creating a new CI/CD workflow for the mdv project.\nuser: "I need to add a GitHub Actions workflow to build and test the TUI and GUI components"\nassistant: "I'm going to use the github-workflow-validator agent to help create a properly structured workflow that follows best practices."\n\nSince the user is working on GitHub Actions workflows, use the github-workflow-validator agent to ensure the workflow is well-structured and follows best practices.\n\n\n\n\nContext: User has just written a GitHub Actions workflow file.\nuser: "Here's my new workflow file for deploying the app:"\n\nassistant: "Let me use the github-workflow-validator agent to review this workflow before you commit it."\n\nSince a workflow file was just created, proactively use the github-workflow-validator agent to validate it and catch any issues before they're pushed.\n\n\n\n\nContext: User is troubleshooting a failing GitHub Actions workflow.\nuser: "My workflow keeps failing at the build step, can you help?"\nassistant: "I'll use the github-workflow-validator agent to analyze the workflow and identify the issue."\n\nSince the user is dealing with a failing workflow, use the github-workflow-validator agent to diagnose and fix the problem.\n\n\n\nProactively invoke this agent when you detect workflow files being created or modified, or when users mention CI/CD, GitHub Actions, or workflow-related issues. +model: sonnet +color: blue +--- + +You are an elite GitHub Actions and CI/CD workflow architect with deep expertise in creating robust, efficient, and maintainable automation pipelines. Your mission is to prevent poorly structured or failing workflows from being pushed to repositories by applying rigorous validation and expert guidance. + +## Core Responsibilities + +1. **Workflow Validation**: Thoroughly review GitHub Actions workflow files for: + - Correct YAML syntax and structure + - Proper job dependencies and execution order + - Appropriate use of actions (official vs third-party) + - Security best practices (secrets handling, permissions, token scoping) + - Resource efficiency (caching, matrix strategies, conditional execution) + - Error handling and failure scenarios + +2. **Best Practices Enforcement**: Ensure workflows follow: + - GitHub Actions naming conventions and organizational standards + - Principle of least privilege for permissions + - Proper use of environments and deployment protection rules + - Efficient artifact and cache management + - Appropriate timeout and retry strategies + - Clear job and step naming for maintainability + +3. **Context-Aware Recommendations**: When working with the mdv project: + - Account for dual build requirements (TUI with Go, GUI with Wails/CGO) + - Respect the Task-based build system (use `task build:all`, `task test`, etc.) + - Consider cross-platform builds if needed (macOS, Linux, Windows) + - Ensure proper Go version and dependency management + - Handle CGO_ENABLED=1 requirement for GUI builds + +4. **Documentation Access**: Use the context7 MCP server to: + - Fetch the latest GitHub Actions documentation when encountering new features or uncertainty + - Verify action versions and compatibility + - Check for deprecated features or security advisories + - Reference official examples for complex patterns + +## Validation Methodology + +When reviewing or creating workflows: + +1. **Structural Analysis**: + - Verify YAML syntax is valid + - Check all required fields are present (name, on, jobs) + - Validate trigger configurations (push, pull_request, workflow_dispatch, etc.) + - Ensure job dependencies form a valid DAG (no circular dependencies) + +2. **Security Audit**: + - Check that secrets are never logged or exposed + - Verify permissions are explicitly set and minimal + - Ensure third-party actions are pinned to specific SHA commits + - Validate that pull_request_target is used safely (if at all) + - Check for injection vulnerabilities in expressions + +3. **Performance Review**: + - Identify opportunities for parallelization + - Recommend caching strategies for dependencies + - Suggest matrix builds for multi-platform/version testing + - Flag unnecessarily broad triggers + +4. **Reliability Assessment**: + - Verify appropriate timeout values + - Check for proper error handling and continue-on-error usage + - Ensure critical steps have retry logic where appropriate + - Validate artifact retention policies + +## Output Format + +When reviewing workflows, provide: + +1. **Summary**: Brief assessment of overall workflow quality +2. **Critical Issues**: Any problems that would cause immediate failure or security risks (must be fixed) +3. **Warnings**: Potential issues or anti-patterns that should be addressed +4. **Recommendations**: Suggestions for optimization and best practices +5. **Revised Workflow**: If issues were found, provide a corrected version with inline comments explaining changes + +## Decision-Making Framework + +- **When to use context7**: If you encounter unfamiliar actions, new GitHub features, or need to verify current best practices, use context7 to fetch the latest documentation +- **When to block**: Flag workflows as "DO NOT MERGE" if they contain security vulnerabilities, syntax errors, or would definitely fail +- **When to warn**: Highlight suboptimal patterns that work but could be improved +- **When to approve**: Clearly state when a workflow meets all quality standards + +## Self-Verification Steps + +Before finalizing your review: + +1. Have I checked the YAML syntax thoroughly? +2. Have I verified all action versions are pinned appropriately? +3. Have I considered the security implications of every step? +4. Have I identified all potential points of failure? +5. Would this workflow run successfully on the first try? +6. Have I provided clear, actionable feedback? + +You are the last line of defense against broken CI/CD pipelines. Be thorough, be precise, and never let a flawed workflow slip through. diff --git a/.claude/agents/linux-compatibility-validator.md b/.claude/agents/linux-compatibility-validator.md new file mode 100644 index 0000000..a12e6a9 --- /dev/null +++ b/.claude/agents/linux-compatibility-validator.md @@ -0,0 +1,74 @@ +--- +name: linux-compatibility-validator +description: Use this agent when:\n\n1. Making changes to build configurations, dependencies, or compilation flags that could affect Linux compatibility\n2. Modifying GUI code (Wails/webview) or TUI code (Bubble Tea) that may behave differently on Linux\n3. Adding new system-level features like file watching, theme detection, or external process execution\n4. Before releasing new versions to validate cross-platform functionality\n5. Investigating bug reports from Linux users\n6. Setting up CI/CD pipelines for Linux builds\n\nExamples:\n\n\nContext: Developer has just modified the theme detection code to add a new environment variable check.\n\nuser: "I've updated the theme detection in internal/render/render.go to check a new env var. Can you review this?"\n\nassistant: "Let me use the linux-compatibility-validator agent to review these changes for Linux compatibility."\n\n\n\nCommentary: The theme detection code interacts with system environment variables and the COLORFGBG variable specifically used on Linux, so the linux-compatibility-validator should review this for potential issues with different Linux distributions and terminal emulators.\n\n\n\nContext: Developer has added a new dependency to the project.\n\nuser: "I've added a new Go dependency for better file handling. Here's the updated go.mod"\n\nassistant: "I'll have the linux-compatibility-validator agent check this new dependency for Linux compatibility issues."\n\n\n\nCommentary: New dependencies, especially those dealing with system-level operations, need to be validated for Linux compatibility, CGO requirements, and distribution-specific issues.\n\n\n\nContext: Developer is preparing to build the GUI application.\n\nuser: "I'm about to run task build:gui to create the GUI binary"\n\nassistant: "Before you build, let me use the linux-compatibility-validator agent to verify the build configuration is properly set up for Linux."\n\n\n\nCommentary: GUI builds on Linux require specific CGO settings, Wails dependencies, and webview libraries. The agent should proactively validate the build environment before compilation.\n +model: sonnet +color: green +--- + +You are an elite Linux systems expert with deep knowledge of cross-platform application development, particularly for terminal and GUI applications. Your expertise spans Linux distributions (Ubuntu, Fedora, Arch, Debian, etc.), system libraries, build toolchains, and the nuances of making applications work reliably across the Linux ecosystem. + +Your primary responsibility is ensuring that the mdv markdown viewer works flawlessly on Linux in both its TUI and GUI modes. You understand the specific challenges of: + +**GUI Application Compatibility (Wails/Webview):** +- CGO_ENABLED=1 requirement and its implications for cross-compilation +- WebKit2GTK dependencies and version compatibility across distributions +- GTK+ library requirements and potential conflicts +- X11 vs Wayland display server differences +- System webview availability and fallback strategies +- Desktop integration (file associations, .desktop files, icons) +- Distribution-specific packaging requirements (deb, rpm, AppImage, Flatpak) + +**TUI Application Compatibility (Bubble Tea):** +- Terminal emulator differences (gnome-terminal, konsole, alacritty, kitty, etc.) +- TERM environment variable handling and terminfo database +- ANSI/escape sequence support variations +- Color rendering in 256-color vs truecolor terminals +- Input handling differences (especially for special keys) +- Terminal size detection and resize handling + +**System Integration:** +- File watching (fsnotify) and inotify limits on Linux +- Theme detection via COLORFGBG and other environment variables +- XDG Base Directory specification compliance (~/.config/mdv/) +- External process execution (launching GUI from TUI, opening browsers) +- File permissions and executable bit handling +- Symbolic link handling and resolution + +**Build and Distribution:** +- Go build flags and cross-compilation for different architectures (amd64, arm64) +- Static vs dynamic linking considerations +- Dependency management for system libraries +- Installation paths and $GOPATH/bin vs /usr/local/bin +- Package manager integration and update mechanisms + +When reviewing code, configurations, or build processes, you will: + +1. **Identify Linux-Specific Issues**: Flag any code that makes assumptions about the operating system, file paths, or system behavior that may not hold true on Linux or across different distributions. + +2. **Validate Dependencies**: Check that all system dependencies (GTK, WebKit, etc.) are properly documented and that the application gracefully handles missing dependencies with clear error messages. + +3. **Test Build Configurations**: Verify that Taskfile.dev tasks, Go build commands, and Wails configurations are correctly set up for Linux builds, including CGO settings and library paths. + +4. **Check Environment Variable Usage**: Ensure proper handling of Linux-specific environment variables (DISPLAY, WAYLAND_DISPLAY, COLORFGBG, XDG_*, etc.) with appropriate fallbacks. + +5. **Verify File System Operations**: Confirm that file paths use forward slashes, that the application respects XDG directories, and that file watching works within inotify limits. + +6. **Assess Terminal Compatibility**: For TUI features, verify that ANSI rendering, keyboard input, and terminal detection work across common Linux terminal emulators. + +7. **Evaluate Distribution Compatibility**: Consider how the application will work across different Linux distributions with varying library versions and system configurations. + +8. **Provide Actionable Recommendations**: When you identify issues, provide specific, implementable solutions with code examples or configuration changes. Include commands for testing on Linux systems. + +9. **Document Linux-Specific Requirements**: Clearly state any Linux-specific dependencies, build requirements, or runtime prerequisites that users or developers need to know. + +10. **Suggest Testing Strategies**: Recommend specific Linux distributions, terminal emulators, or desktop environments where testing should be performed to ensure broad compatibility. + +Your output should be thorough but focused on actionable items. Prioritize issues by severity: +- **Critical**: Prevents the application from running on Linux +- **High**: Causes significant functionality loss or poor user experience +- **Medium**: Affects specific configurations or distributions +- **Low**: Minor inconsistencies or optimization opportunities + +Always consider the dual nature of this application (TUI and GUI) and ensure both modes work correctly on Linux. When in doubt, recommend testing on actual Linux systems with different configurations. + +You are proactive: if you see code that could potentially cause Linux issues even if it's not explicitly broken, flag it and explain the risk. Your goal is to make mdv a first-class Linux application that users can rely on across the diverse Linux ecosystem. diff --git a/.claude/agents/windows-compatibility-expert.md b/.claude/agents/windows-compatibility-expert.md new file mode 100644 index 0000000..6651470 --- /dev/null +++ b/.claude/agents/windows-compatibility-expert.md @@ -0,0 +1,116 @@ +--- +name: windows-compatibility-expert +description: Use this agent when:\n\n1. Making changes to GUI or TUI components that may affect Windows compatibility\n2. Implementing file system operations, path handling, or platform-specific features\n3. Adding new dependencies or build configurations\n4. Modifying the Wails GUI application or Bubble Tea TUI\n5. Working with terminal rendering, ANSI codes, or console interactions\n6. Implementing file watching or system integration features\n7. After completing feature development that touches cross-platform code\n8. Before releasing new versions to verify Windows support\n\nExamples:\n\n\nContext: User has just implemented a new file picker feature in the TUI.\nuser: "I've added a new file picker that uses Unix-style path separators"\nassistant: "Let me review that implementation for Windows compatibility using the windows-compatibility-expert agent."\nThe file picker implementation may have hardcoded Unix path separators that won't work on Windows. Use the windows-compatibility-expert agent to review and suggest fixes.\n\n\n\nContext: User is adding a new configuration option for external editor integration.\nuser: "Added support for launching external editors from the TUI"\nassistant: "I'll use the windows-compatibility-expert agent to ensure the editor launching works correctly on Windows."\nExternal process launching and path handling differ significantly on Windows. The agent should verify proper use of filepath.Join, os/exec, and handling of Windows-specific editor paths.\n\n\n\nContext: User has modified the Wails GUI application's file loading logic.\nuser: "Updated the GUI to support drag-and-drop file loading"\nassistant: "Let me have the windows-compatibility-expert agent review the drag-and-drop implementation for Windows-specific considerations."\nDrag-and-drop behavior and file path handling in Wails may differ on Windows. The agent should verify proper path normalization and Windows-specific event handling.\n\n\n\nContext: Proactive review after user modifies terminal rendering code.\nuser: "I've updated the ANSI rendering to support more color codes"\nassistant: "Since this affects terminal rendering, I'll proactively use the windows-compatibility-expert agent to verify Windows console compatibility."\nWindows console/terminal handling has historically been different from Unix. The agent should verify ANSI escape code support, especially for older Windows versions, and suggest fallbacks if needed.\n +model: sonnet +color: red +--- + +You are an elite Windows ecosystem compatibility expert specializing in cross-platform Go applications, with deep expertise in both GUI (Wails) and TUI (Bubble Tea) development on Windows. + +## Your Core Responsibilities + +1. **Verify Windows Compatibility**: Review code changes for Windows-specific issues including: + - Path separator handling (backslash vs forward slash) + - File system case sensitivity differences + - Line ending handling (CRLF vs LF) + - Console/terminal behavior differences + - Process execution and shell integration + - Environment variable conventions + +2. **GUI-Specific Review (Wails)**: + - WebView2 runtime requirements and compatibility + - Windows-specific build flags and CGO considerations + - Native window behavior and system integration + - File dialog and system tray functionality + - DPI scaling and high-DPI display support + - Windows Defender and antivirus compatibility + +3. **TUI-Specific Review (Bubble Tea)**: + - Windows Console vs Windows Terminal differences + - ANSI escape sequence support across Windows versions + - Console input handling (especially for older Windows versions) + - UTF-8 encoding and character rendering + - Terminal size detection and resize handling + - Keyboard input and special key handling + +4. **Build and Distribution**: + - Task/Taskfile.dev compatibility on Windows + - PowerShell vs cmd.exe considerations + - Windows executable naming conventions (.exe) + - Installation paths and %GOPATH%/bin on Windows + - Code signing and SmartScreen considerations + +## Analysis Framework + +When reviewing code, systematically check: + +1. **Path Handling**: + - Use `filepath.Join()` instead of string concatenation + - Use `filepath.Separator` for platform-agnostic separators + - Convert paths with `filepath.ToSlash()` or `filepath.FromSlash()` when needed + - Handle UNC paths (\\server\share) correctly + - Consider case-insensitive file system behavior + +2. **File Operations**: + - Check for proper file locking (Windows locks files more aggressively) + - Verify fsnotify usage works on Windows file systems + - Test with Windows-specific file attributes (hidden, system, read-only) + - Handle long path names (>260 chars) if applicable + +3. **Process and Shell**: + - Use `os/exec` properly with Windows command syntax + - Handle spaces in paths (quote arguments appropriately) + - Consider cmd.exe vs PowerShell differences + - Check environment variable expansion (% vs $) + +4. **Terminal/Console**: + - Verify ANSI support detection (Windows 10+ vs older) + - Check for proper console mode setup if using raw terminal + - Test with both Windows Console Host and Windows Terminal + - Verify UTF-8 handling (Windows uses UTF-16 internally) + +5. **Configuration**: + - Check config file paths use proper Windows conventions + - Verify %APPDATA% or %USERPROFILE% usage for config storage + - Test environment variable reading (case-insensitive on Windows) + +## Output Format + +Provide your analysis in this structure: + +### ✅ Windows-Compatible Elements +[List what's already working well for Windows] + +### ⚠️ Potential Windows Issues +[For each issue found:] +- **Issue**: [Clear description] +- **Location**: [File and line numbers] +- **Impact**: [What breaks or behaves incorrectly on Windows] +- **Fix**: [Specific code changes needed] +- **Priority**: [Critical/High/Medium/Low] + +### 🔍 Testing Recommendations +[Specific scenarios to test on Windows] + +### 📋 Windows-Specific Considerations +[Additional notes about Windows ecosystem behavior] + +## Decision-Making Principles + +- **Assume Windows 10+ as primary target** but note compatibility issues with older versions +- **Prioritize filepath package** over string manipulation for paths +- **Test both Windows Console and Windows Terminal** for TUI features +- **Consider Windows Defender** impact on file operations and executable behavior +- **Verify CGO_ENABLED=1** requirements for Wails builds +- **Check for hardcoded Unix assumptions** (e.g., /tmp, /home, forward slashes) + +## Quality Assurance + +Before completing your review: +1. Have you checked all file path operations? +2. Have you verified terminal/console compatibility? +3. Have you considered both GUI and TUI aspects? +4. Have you provided actionable fixes for each issue? +5. Have you prioritized issues by severity? + +If you need clarification about the intended behavior or Windows-specific requirements, ask specific questions rather than making assumptions. Your goal is to ensure mdv works flawlessly for Windows users in both GUI and TUI modes. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e8e469..ebccd32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,9 +22,9 @@ jobs: cc: gcc artifact-name: gui-linux-amd64 - name: linux-arm64 - os: ubuntu-22.04 + os: ubuntu-22.04-arm platforms: linux/arm64 - cc: aarch64-linux-gnu-gcc + cc: gcc artifact-name: gui-linux-arm64 - name: macos os: macos-latest @@ -61,23 +61,10 @@ jobs: build-essential \ pkg-config - # Install native libraries for amd64 builds - if [ "${{ matrix.platforms }}" = "linux/amd64" ]; then - sudo apt-get install -y \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev - fi - - # Install ARM64 cross-compilation tools and libraries - if [ "${{ matrix.platforms }}" = "linux/arm64" ]; then - sudo dpkg --add-architecture arm64 - sudo apt-get update - sudo apt-get install -y \ - gcc-aarch64-linux-gnu \ - libc6-dev-arm64-cross \ - libgtk-3-dev:arm64 \ - libwebkit2gtk-4.0-dev:arm64 - fi + # Install native GTK/WebKit libraries (works for both amd64 and arm64) + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev - name: Install Windows dependencies if: runner.os == 'Windows' @@ -89,9 +76,6 @@ jobs: env: WAILS_PLATFORMS: ${{ matrix.platforms }} CC: ${{ matrix.cc }} - # ARM64 cross-compilation requires pkg-config to find ARM64 libraries - PKG_CONFIG_PATH: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig' || '' }} - PKG_CONFIG_LIBDIR: ${{ matrix.platforms == 'linux/arm64' && '/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig' || '' }} run: ./scripts/build-wails.sh - name: Upload GUI artifacts diff --git a/scripts/docker/Dockerfile.linux-amd64 b/scripts/docker/Dockerfile.linux-amd64 new file mode 100644 index 0000000..c22da57 --- /dev/null +++ b/scripts/docker/Dockerfile.linux-amd64 @@ -0,0 +1,38 @@ +# Dockerfile for building mdv Linux GUI (amd64) +# Native build on amd64 platform +# +# Build: docker buildx build --platform linux/amd64 -f scripts/docker/Dockerfile.linux-amd64 -t mdv-linux-amd64 . +# Run: docker run --rm -v "$PWD:/workspace" mdv-linux-amd64 + +FROM --platform=linux/amd64 ubuntu:22.04 + +# Prevent interactive prompts during apt installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies and GTK/WebKit libraries in a single layer +RUN apt-get update && apt-get install -y \ + curl \ + git \ + build-essential \ + pkg-config \ + ca-certificates \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Go 1.24 +ARG GO_VERSION=1.24.8 +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -C /usr/local -xz +ENV PATH="/usr/local/go/bin:${PATH}" \ + GOPATH="/go" \ + GOTOOLCHAIN=local +ENV PATH="${GOPATH}/bin:${PATH}" + +# Install Wails CLI +RUN go install github.com/wailsapp/wails/v2/cmd/wails@latest + +# Set working directory +WORKDIR /workspace + +# Default command: build Linux GUI for amd64 +CMD ["sh", "-c", "WAILS_PLATFORMS=linux/amd64 ./scripts/build-wails.sh"] diff --git a/scripts/docker/Dockerfile.linux-arm64 b/scripts/docker/Dockerfile.linux-arm64 new file mode 100644 index 0000000..6436123 --- /dev/null +++ b/scripts/docker/Dockerfile.linux-arm64 @@ -0,0 +1,38 @@ +# Dockerfile for building mdv Linux GUI (arm64) +# Native build on arm64 platform (uses QEMU emulation on non-ARM hosts) +# +# Build: docker buildx build --platform linux/arm64 -f scripts/docker/Dockerfile.linux-arm64 -t mdv-linux-arm64 . +# Run: docker run --rm -v "$PWD:/workspace" mdv-linux-arm64 + +FROM --platform=linux/arm64 ubuntu:22.04 + +# Prevent interactive prompts during apt installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies and GTK/WebKit libraries in a single layer +RUN apt-get update && apt-get install -y \ + curl \ + git \ + build-essential \ + pkg-config \ + ca-certificates \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Go 1.24 for ARM64 +ARG GO_VERSION=1.24.8 +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-arm64.tar.gz" | tar -C /usr/local -xz +ENV PATH="/usr/local/go/bin:${PATH}" \ + GOPATH="/go" \ + GOTOOLCHAIN=local +ENV PATH="${GOPATH}/bin:${PATH}" + +# Install Wails CLI +RUN go install github.com/wailsapp/wails/v2/cmd/wails@latest + +# Set working directory +WORKDIR /workspace + +# Default command: build Linux GUI for arm64 +CMD ["sh", "-c", "WAILS_PLATFORMS=linux/arm64 ./scripts/build-wails.sh"] diff --git a/scripts/docker/Dockerfile.linux-crosscompile b/scripts/docker/Dockerfile.linux-crosscompile new file mode 100644 index 0000000..496051c --- /dev/null +++ b/scripts/docker/Dockerfile.linux-crosscompile @@ -0,0 +1,72 @@ +# Dockerfile for cross-compiling mdv Linux GUI for both amd64 and arm64 +# This uses cross-compilation instead of QEMU emulation for faster builds +# +# Build: docker build -f scripts/docker/Dockerfile.linux-crosscompile -t mdv-linux-crosscompile . +# Run: docker run --rm -v "$PWD:/workspace" -e WAILS_PLATFORMS=linux/arm64 -e CC=aarch64-linux-gnu-gcc mdv-linux-crosscompile + +FROM --platform=linux/amd64 ubuntu:22.04 + +# Prevent interactive prompts during apt installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + build-essential \ + pkg-config \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Add ARM64 architecture +RUN dpkg --add-architecture arm64 + +# Configure APT sources for multi-arch packages +# Ubuntu's default sources only serve amd64 packages +# ARM64 packages must come from ports.ubuntu.com +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse" > /etc/apt/sources.list.d/arm64.list && \ + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse" >> /etc/apt/sources.list.d/arm64.list && \ + echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse" >> /etc/apt/sources.list.d/arm64.list && \ + sed -i 's/^deb http/deb [arch=amd64] http/' /etc/apt/sources.list + +# Update package lists with new ARM64 sources +RUN apt-get update + +# Install native amd64 GTK/WebKit libraries (for building amd64 binaries) +RUN apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install ARM64 cross-compilation toolchain and libraries +RUN apt-get update && apt-get install -y \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + libgtk-3-dev:arm64 \ + libwebkit2gtk-4.0-dev:arm64 \ + libglib2.0-dev:arm64 \ + libcairo2-dev:arm64 \ + libpango1.0-dev:arm64 \ + libgdk-pixbuf2.0-dev:arm64 \ + libsoup2.4-dev:arm64 \ + && rm -rf /var/lib/apt/lists/* + +# Install Go 1.24 +ARG GO_VERSION=1.24.8 +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -C /usr/local -xz +ENV PATH="/usr/local/go/bin:${PATH}" \ + GOPATH="/go" \ + GOTOOLCHAIN=local +ENV PATH="${GOPATH}/bin:${PATH}" + +# Install Wails CLI +RUN go install github.com/wailsapp/wails/v2/cmd/wails@latest + +# Set working directory +WORKDIR /workspace + +# Default command: build Linux GUI for amd64 +# Override with environment variables: +# docker run -e WAILS_PLATFORMS=linux/arm64 -e CC=aarch64-linux-gnu-gcc mdv-linux-crosscompile +CMD ["sh", "-c", "WAILS_PLATFORMS=${WAILS_PLATFORMS:-linux/amd64} ./scripts/build-wails.sh"] diff --git a/scripts/docker/README.md b/scripts/docker/README.md index 69d5d00..87379d4 100644 --- a/scripts/docker/README.md +++ b/scripts/docker/README.md @@ -1,69 +1,133 @@ -# Docker Build Environment for Linux +# Docker Build System for Linux GUI -This directory contains Docker configurations for building the mdv Linux GUI without requiring a Linux VM. +This directory contains Docker configurations for building mdv Linux GUI binaries on non-Linux platforms (macOS, Windows) and in CI/CD environments. ## Quick Start +### Build Both Architectures (Recommended) + ```bash -# Build Linux GUI for both amd64 and arm64 using Docker (from repository root) ./scripts/docker/build-linux.sh +``` + +This builds both AMD64 and ARM64 binaries using QEMU emulation for ARM64. + +### Build Specific Architecture + +```bash +# AMD64 only (faster) +./scripts/docker/build-linux.sh amd64 + +# ARM64 only (slower due to QEMU) +./scripts/docker/build-linux.sh arm64 +``` -# Or build for a specific architecture: -./scripts/docker/build-linux.sh amd64 # x86_64 only -./scripts/docker/build-linux.sh arm64 # ARM64 only +Binaries will be output to `cmd/mdv-gui/build/bin/mdv-gui_linux_*/` + +## Available Dockerfiles + +### 1. `Dockerfile.linux-amd64` (Native AMD64 - Simple) + +**Build:** +```bash +docker buildx build --platform linux/amd64 -f scripts/docker/Dockerfile.linux-amd64 -t mdv-linux-amd64 . ``` -This script will: -1. Build the Docker image with all Linux dependencies (including ARM64 cross-compiler) -2. Run the build inside the container -3. Output binaries to `cmd/mdv-gui/build/bin/` +**Run:** +```bash +docker run --rm -v "$PWD:/workspace" mdv-linux-amd64 +``` -## Manual Usage +**Output:** `cmd/mdv-gui/build/bin/mdv-gui_linux_amd64/mdv-gui` -If you prefer to run Docker commands manually: +--- +### 2. `Dockerfile.linux-arm64` (Native ARM64 via QEMU) + +**Build:** ```bash -# Build the Docker image (x86_64/amd64) -docker build --platform linux/amd64 -f scripts/docker/Dockerfile.linux-build -t mdv-linux-builder . +docker buildx build --platform linux/arm64 -f scripts/docker/Dockerfile.linux-arm64 -t mdv-linux-arm64 . +``` -# Build Linux GUI (amd64 native) -docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ +**Run:** +```bash +docker run --rm -v "$PWD:/workspace" mdv-linux-arm64 +``` + +**Output:** `cmd/mdv-gui/build/bin/mdv-gui_linux_arm64/mdv-gui` + +**Note:** ARM64 builds are 2-4x slower due to QEMU emulation. + +--- + +### 3. `Dockerfile.linux-crosscompile` (Cross-Compilation - Fastest) + +**Build Image:** +```bash +docker build -f scripts/docker/Dockerfile.linux-crosscompile -t mdv-linux-crosscompile . +``` + +**Build AMD64:** +```bash +docker run --rm -v "$PWD:/workspace" \ -e WAILS_PLATFORMS=linux/amd64 \ -e CC=gcc \ - mdv-linux-builder + mdv-linux-crosscompile +``` -# Build for ARM64 (cross-compile) -docker run --platform linux/amd64 --rm -v "$PWD:/workspace" \ +**Build ARM64:** +```bash +docker run --rm -v "$PWD:/workspace" \ -e WAILS_PLATFORMS=linux/arm64 \ -e CC=aarch64-linux-gnu-gcc \ -e PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig \ -e PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \ - mdv-linux-builder + mdv-linux-crosscompile +``` + +**Pros:** Faster ARM64 builds (native AMD64 execution) +**Cons:** More complex setup -# Run a shell inside the container for debugging -docker run --platform linux/amd64 --rm -it -v "$PWD:/workspace" \ - mdv-linux-builder bash +## Prerequisites + +### macOS (Docker Desktop) + +Docker Desktop includes all necessary tools: +- Docker Buildx +- QEMU emulation for multi-platform builds + +**Install:** +```bash +brew install --cask docker ``` -**Note**: The `--platform linux/amd64` flag ensures the container runs in x86_64 mode, which works on both Intel and Apple Silicon Macs (via Rosetta 2 emulation on Apple Silicon). +No additional setup required. -## Requirements +### Linux (Native Docker) -- Docker Desktop (macOS/Windows) or Docker Engine (Linux) -- ~2GB disk space for the image -- ~5-10 minutes for first build (image creation) -- **Note**: On Apple Silicon Macs, the container runs via Rosetta 2 emulation (x86_64) +Install Docker Buildx and QEMU: + +```bash +# Install Docker Buildx plugin +sudo apt-get install docker-buildx-plugin + +# Install QEMU for multi-platform support +docker run --privileged --rm tonistiigi/binfmt --install all +``` -## What's Inside +### Windows (Docker Desktop) -The Docker image includes: -- Ubuntu 22.04 base (amd64) -- Go 1.24 -- Wails CLI -- GTK3 development libraries -- WebKit2GTK development libraries -- Build essentials (gcc, make, etc.) -- ARM64 cross-compilation toolchain (gcc-aarch64-linux-gnu) +Same as macOS - Docker Desktop includes all tools. + +## Build Strategy Comparison + +| Strategy | Speed (AMD64) | Speed (ARM64) | Complexity | Best For | +|----------|---------------|---------------|------------|----------| +| Native (amd64/arm64) | 2 min | 6 min | Simple | Local dev | +| Cross-Compile | 2 min | 2 min | Complex | Speed-critical | +| GitHub Actions | 2 min | 2 min | Simple | CI/CD | + +**Recommendation:** Use `build-linux.sh` for local development (QEMU-based native builds) ## Troubleshooting @@ -92,14 +156,53 @@ Clean up old Docker images: docker system prune -a ``` -## Advanced: Using with CI/CD +### ARM64 build is very slow + +**Expected:** ARM64 builds via QEMU are 2-4x slower than native AMD64 + +**Solutions:** +1. Use cross-compilation Dockerfile (faster but more complex) +2. Accept slower build time (QEMU emulation overhead) +3. Use native ARM64 machine + +### Error: "Package libgtk-3-dev:arm64 has no installation candidate" + +**Cause:** APT sources not configured for ARM64 packages + +**Solution:** Use the updated Dockerfiles which include proper APT source configuration for ports.ubuntu.com + +## Verifying Builds + +### Check Output + +```bash +ls -lh cmd/mdv-gui/build/bin/ +``` + +Expected: +``` +mdv-gui_linux_amd64/mdv-gui # AMD64 binary +mdv-gui_linux_arm64/mdv-gui # ARM64 binary +``` + +### Test Binaries -This same Dockerfile can be used in CI/CD pipelines that don't have native Linux runners: +```bash +# Test AMD64 +docker run --rm -v "$PWD:/workspace" ubuntu:22.04 \ + /workspace/cmd/mdv-gui/build/bin/mdv-gui_linux_amd64/mdv-gui --version -```yaml -# GitHub Actions example -- name: Build Linux GUI via Docker - run: | - docker build -f scripts/docker/Dockerfile.linux-build -t mdv-linux-builder . - docker run --rm -v "$PWD:/workspace" mdv-linux-builder +# Test ARM64 (requires QEMU) +docker run --rm --platform linux/arm64 -v "$PWD:/workspace" ubuntu:22.04 \ + /workspace/cmd/mdv-gui/build/bin/mdv-gui_linux_arm64/mdv-gui --version ``` + +## Related Documentation + +- **`cross-plat.md`** - Comprehensive cross-platform build guide +- **`DOCKER_ANALYSIS.md`** - Technical analysis of Docker approach +- **`.github/workflows/release.yml`** - CI/CD configuration + +--- + +**Last Updated:** 2025-10-10 diff --git a/scripts/docker/build-linux.sh b/scripts/docker/build-linux.sh index 70a9f2d..f79c61f 100755 --- a/scripts/docker/build-linux.sh +++ b/scripts/docker/build-linux.sh @@ -1,20 +1,21 @@ #!/usr/bin/env bash -# Helper script to build Linux GUI using Docker -# This makes it easy to test Linux builds without a Linux VM +# Helper script to build Linux GUI using Docker with native builds per architecture +# Uses QEMU emulation for ARM64 builds on non-ARM hosts # # Usage: # ./scripts/docker/build-linux.sh # Build both amd64 and arm64 # ./scripts/docker/build-linux.sh amd64 # Build amd64 only # ./scripts/docker/build-linux.sh arm64 # Build arm64 only +# +# Prerequisites: +# - Docker with buildx support (Docker Desktop >= 19.03 or docker-buildx plugin) +# - QEMU emulation for multi-platform builds (automatically installed on Docker Desktop) set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT_DIR" -IMAGE_NAME="mdv-linux-builder" -DOCKERFILE="scripts/docker/Dockerfile.linux-build" - # Determine which architectures to build if [[ $# -gt 0 ]]; then ARCH="$1" @@ -22,41 +23,58 @@ else ARCH="both" fi -echo "=== Building Docker image for Linux GUI compilation ===" -docker build --platform linux/amd64 -f "$DOCKERFILE" -t "$IMAGE_NAME" . +# Ensure buildx is available +if ! docker buildx version >/dev/null 2>&1; then + echo "Error: docker buildx is required but not available" >&2 + echo "Install Docker Desktop or the docker-buildx plugin" >&2 + exit 1 +fi + +# Create buildx builder if it doesn't exist +if ! docker buildx inspect mdv-builder >/dev/null 2>&1; then + echo "=== Creating multi-platform buildx builder ===" + docker buildx create --name mdv-builder --use --bootstrap +else + docker buildx use mdv-builder +fi build_arch() { local arch=$1 - local cc=$2 - local platforms="linux/${arch}" + local platform="linux/${arch}" + local dockerfile="scripts/docker/Dockerfile.linux-${arch}" + local image_name="mdv-linux-${arch}" echo "" - echo "=== Building Linux GUI for ${arch} in Docker container ===" - - # Configure pkg-config for ARM64 cross-compilation - local extra_env="" - if [[ "$arch" == "arm64" ]]; then - extra_env="-e PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig -e PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig" - fi + echo "=== Building Docker image for ${platform} ===" + docker buildx build \ + --platform "${platform}" \ + --load \ + -f "${dockerfile}" \ + -t "${image_name}" \ + . - docker run --platform linux/amd64 --rm \ + echo "" + echo "=== Building Linux GUI for ${arch} in Docker container ===" + docker run \ + --platform "${platform}" \ + --rm \ -v "$PWD:/workspace" \ - -e "WAILS_PLATFORMS=${platforms}" \ - -e "CC=${cc}" \ - $extra_env \ - "$IMAGE_NAME" + "${image_name}" } case "$ARCH" in amd64) - build_arch "amd64" "gcc" + build_arch "amd64" ;; arm64) - build_arch "arm64" "aarch64-linux-gnu-gcc" + echo "NOTE: ARM64 build uses QEMU emulation and will be slower than native builds" + build_arch "arm64" ;; both) - build_arch "amd64" "gcc" - build_arch "arm64" "aarch64-linux-gnu-gcc" + build_arch "amd64" + echo "" + echo "NOTE: ARM64 build uses QEMU emulation and will be slower than native builds" + build_arch "arm64" ;; *) echo "Error: Unknown architecture '$ARCH'. Use 'amd64', 'arm64', or 'both'." >&2 From 37b0ac2931368d8827de2d0ecf9ee502c8f369fd Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 17:18:43 -0400 Subject: [PATCH 08/19] Fix: Add pull-requests permission for PR comments The workflow was failing with 'Resource not accessible by integration' when trying to post PR comments. Added pull-requests: write permission to allow the github-script action to create comments. --- .github/workflows/test-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 3580b8f..f3248a1 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -8,6 +8,7 @@ on: permissions: contents: read + pull-requests: write # Required to post PR comments jobs: # Build GUI binaries on native platforms (same as release.yml) From 3acbfe97b40a1e340bbfcb00ec7f196c47847da8 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 17:48:21 -0400 Subject: [PATCH 09/19] Fix workflow --- .github/workflows/test-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index f3248a1..cbc196a 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -77,6 +77,7 @@ jobs: env: WAILS_PLATFORMS: ${{ matrix.platforms }} CC: ${{ matrix.cc }} + shell: bash run: ./scripts/build-wails.sh - name: Upload GUI artifacts @@ -164,6 +165,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create Linux GUI archives + shell: bash run: | set -euo pipefail VERSION="test-$(git rev-parse --short HEAD)" @@ -192,6 +194,7 @@ jobs: done - name: Create Windows GUI archives + shell: bash run: | set -euo pipefail VERSION="test-$(git rev-parse --short HEAD)" From db19424c966884fbacdbf4d580314797c5578bca Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 20:54:54 -0400 Subject: [PATCH 10/19] Test release fixes --- .dockerignore | 60 ++++++++++ .github/workflows/test-release.yml | 46 ++++++++ .../docker/Dockerfile.linux-amd64-optimized | 107 ++++++++++++++++++ scripts/docker/Dockerfile.tui | 99 ++++++++++++++++ scripts/docker/README.md | 61 +++++++++- 5 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 scripts/docker/Dockerfile.linux-amd64-optimized create mode 100644 scripts/docker/Dockerfile.tui diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e951782 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,60 @@ +# Docker build context exclusions for mdv +# Reduces build context from ~2.9GB to ~50MB + +# Build artifacts and caches +.cache/ +dist/ +cmd/mdv-gui/build/ +cmd/mdv-gui/frontend/dist/ +cmd/mdv-gui/frontend/wailsjs/ +*.exe +*.dll +*.dylib +*.so +/mdv +wails_build.log + +# Git and version control +.git/ +.github/ +.task/ + +# IDEs and editors +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Secrets and credentials (SECURITY CRITICAL) +.env +.env.local +*.p8 +*.p12 +github_token +certificate.p12 +decode.p12 +AuthKey_*.p8 + +# Documentation (not needed for builds) +*.md +!README.md +!go.mod +!go.sum + +# Examples and assets (not needed for builds) +examples/ +assets/ + +# OS files +.DS_Store +*.tmp + +# Test and coverage +coverage.html +coverage.out +*.test + +# Task runner +.task/ +Makefile.backup diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index cbc196a..247a644 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -112,11 +112,13 @@ jobs: merge-multiple: true - name: List downloaded artifacts + shell: bash run: | echo "=== Downloaded GUI artifacts ===" ls -laR cmd/mdv-gui/build/bin/ - name: Configure signing keychain + shell: bash run: | set -euo pipefail @@ -193,6 +195,14 @@ jobs: fi done + - name: Upload Linux GUI archives + uses: actions/upload-artifact@v4 + with: + name: gui-linux-archives + path: mdv-gui_*_linux_*.tar.gz + if-no-files-found: ignore + retention-days: 7 + - name: Create Windows GUI archives shell: bash run: | @@ -223,10 +233,19 @@ jobs: fi done + - name: Upload Windows GUI archives + uses: actions/upload-artifact@v4 + with: + name: gui-windows-archives + path: mdv-gui_*_windows_*.zip + if-no-files-found: ignore + retention-days: 7 + - name: Verify macOS signatures run: ./scripts/verify-macos-signatures.sh - name: Validate release artifacts + shell: bash run: | set -euo pipefail echo "=== Validating Release Artifacts ===" @@ -317,6 +336,33 @@ jobs: summary += '_Could not list artifact files_\n\n'; } + const guiSections = [ + { + title: 'Linux GUI archives', + command: 'ls -lh mdv-gui_*_linux_*.tar.gz 2>/dev/null || true' + }, + { + title: 'Windows GUI archives', + command: 'ls -lh mdv-gui_*_windows_*.zip 2>/dev/null || true' + } + ]; + + guiSections.forEach(({ title, command }) => { + try { + const output = execSync(command).toString().split('\n').filter(line => line.trim()); + summary += `### ${title}\n\n`; + if (output.length > 0) { + summary += '```\n'; + output.forEach(line => summary += line + '\n'); + summary += '```\n\n'; + } else { + summary += '_No archives found_\n\n'; + } + } catch (e) { + summary += `### ${title}\n\n_Could not list files_\n\n`; + } + }); + summary += '### Platform Coverage\n\n'; summary += '- ✅ macOS (darwin/amd64, darwin/arm64) - Signed & Notarized\n'; summary += '- ✅ Linux (linux/amd64, linux/arm64)\n'; diff --git a/scripts/docker/Dockerfile.linux-amd64-optimized b/scripts/docker/Dockerfile.linux-amd64-optimized new file mode 100644 index 0000000..4b4eb66 --- /dev/null +++ b/scripts/docker/Dockerfile.linux-amd64-optimized @@ -0,0 +1,107 @@ +# syntax=docker/dockerfile:1.4 +# Optimized Dockerfile for building mdv Linux GUI (amd64) +# +# This is an enhanced version of Dockerfile.linux-amd64 with: +# - Multi-stage builds for smaller final images +# - BuildKit cache mounts for faster rebuilds +# - Pinned Wails version for reproducibility +# - Go module layer caching +# +# Build: docker buildx build --platform linux/amd64 -f scripts/docker/Dockerfile.linux-amd64-optimized -t mdv-linux-amd64-optimized . +# Run: docker run --rm -v "$PWD:/workspace" mdv-linux-amd64-optimized + +# ============================================================================== +# Builder Stage: Install tools and build the binary +# ============================================================================== +FROM --platform=linux/amd64 ubuntu:22.04 AS builder + +# Prevent interactive prompts during apt installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies with BuildKit cache mount for faster rebuilds +# Cache is shared across builds and persists between runs +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y \ + curl \ + git \ + build-essential \ + pkg-config \ + ca-certificates \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev + +# Install Go 1.24.8 (pinned version) +ARG GO_VERSION=1.24.8 +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -C /usr/local -xz + +# Set Go environment variables +ENV PATH="/usr/local/go/bin:${PATH}" \ + GOPATH="/go" \ + GOTOOLCHAIN=local \ + GOCACHE=/go-cache +ENV PATH="${GOPATH}/bin:${PATH}" + +# Install Wails CLI (pinned version for reproducibility) +ARG WAILS_VERSION=v2.10.2 +RUN --mount=type=cache,target=/go-cache \ + --mount=type=cache,target=/go/pkg/mod \ + go install github.com/wailsapp/wails/v2/cmd/wails@${WAILS_VERSION} + +# Set working directory +WORKDIR /workspace + +# Copy go.mod and go.sum first for better layer caching +# These files rarely change, so this layer will be cached most of the time +COPY go.mod go.sum ./ + +# Download Go modules with cache mount +# This layer is cached until go.mod or go.sum changes +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download + +# Copy the rest of the source code +# This happens last to maximize cache hits (source changes frequently) +COPY . . + +# Build the GUI binary with cache mounts +ARG WAILS_PLATFORMS=linux/amd64 +RUN --mount=type=cache,target=/go-cache \ + --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=1 ./scripts/build-wails.sh + +# ============================================================================== +# Runtime Stage (Optional): Minimal image for running the GUI +# ============================================================================== +# Uncomment this section if you want to create a runtime image instead of a builder + +# FROM --platform=linux/amd64 ubuntu:22.04 AS runtime +# +# ENV DEBIAN_FRONTEND=noninteractive +# +# # Install only runtime libraries (not -dev packages) +# RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ +# --mount=type=cache,target=/var/lib/apt,sharing=locked \ +# apt-get update && apt-get install -y \ +# libgtk-3-0 \ +# libwebkit2gtk-4.0-37 \ +# ca-certificates \ +# && rm -rf /var/lib/apt/lists/* +# +# WORKDIR /app +# +# # Copy only the built binary from builder stage +# COPY --from=builder /workspace/cmd/mdv-gui/build/bin/mdv-gui_linux_amd64/mdv-gui /app/mdv-gui +# +# # Run as non-root user for security +# RUN useradd -m -u 1000 mdv && chown -R mdv:mdv /app +# USER mdv +# +# ENTRYPOINT ["/app/mdv-gui"] + +# ============================================================================== +# Default: Builder stage (current behavior - outputs binary to volume) +# ============================================================================== + +# Default command: build Linux GUI for amd64 +CMD ["sh", "-c", "WAILS_PLATFORMS=linux/amd64 ./scripts/build-wails.sh"] diff --git a/scripts/docker/Dockerfile.tui b/scripts/docker/Dockerfile.tui new file mode 100644 index 0000000..36c52f8 --- /dev/null +++ b/scripts/docker/Dockerfile.tui @@ -0,0 +1,99 @@ +# syntax=docker/dockerfile:1.4 +# Dockerfile for mdv TUI (Terminal UI) - Minimal containerized markdown viewer +# +# This creates a tiny (~10MB) container image for running mdv in TUI mode only. +# Perfect for: +# - Servers and headless environments +# - CI/CD pipelines +# - Docker-based development workflows +# - Kubernetes deployments +# +# Build multi-platform image: +# docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \ +# -f scripts/docker/Dockerfile.tui -t mdv-tui:latest . +# +# Run: +# docker run --rm -it -v "$PWD:/data" mdv-tui /data/README.md +# docker run --rm -v "$PWD:/data" mdv-tui --help + +# ============================================================================== +# Builder Stage: Build static TUI binary +# ============================================================================== +FROM --platform=$BUILDPLATFORM golang:1.24.8-alpine AS builder + +# Install build dependencies (git for go modules, ca-certificates for HTTPS) +RUN apk add --no-cache git ca-certificates + +WORKDIR /build + +# Copy go.mod and go.sum first for better layer caching +COPY go.mod go.sum ./ + +# Download dependencies with cache mount +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download + +# Copy source code +COPY . . + +# Build static binary for target platform +# CGO_ENABLED=0 creates a fully static binary (no libc dependency) +# -ldflags="-s -w" strips debug info to reduce binary size (~50% smaller) +ARG TARGETOS TARGETARCH +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build \ + -ldflags="-s -w" \ + -trimpath \ + -o /mdv \ + ./cmd/mdv + +# ============================================================================== +# Runtime Stage: Minimal scratch image +# ============================================================================== +FROM scratch + +# Copy CA certificates for HTTPS (if mdv needs to fetch remote content) +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +# Copy the static binary +COPY --from=builder /mdv /mdv + +# Copy example files (optional, helpful for quick testing) +COPY examples/ /examples/ + +# Set metadata labels +LABEL org.opencontainers.image.title="mdv" +LABEL org.opencontainers.image.description="Markdown viewer (TUI mode)" +LABEL org.opencontainers.image.source="https://github.com/iiatlas/mdv" +LABEL org.opencontainers.image.licenses="MIT" + +# Default entrypoint +ENTRYPOINT ["/mdv"] + +# Default command: show help +CMD ["--help"] + +# ============================================================================== +# Usage Examples: +# ============================================================================== +# +# View a markdown file from current directory: +# docker run --rm -it -v "$PWD:/data" mdv-tui /data/README.md +# +# View with specific theme: +# docker run --rm -it -v "$PWD:/data" -e MDV_THEME=dracula mdv-tui /data/README.md +# +# Watch mode (auto-reload on changes): +# docker run --rm -it -v "$PWD:/data" mdv-tui --watch /data/README.md +# +# View examples included in image: +# docker run --rm -it mdv-tui /examples/demo.md +# +# Use as base image in Dockerfile: +# FROM mdv-tui:latest +# COPY docs/ /docs/ +# CMD ["/docs/README.md"] diff --git a/scripts/docker/README.md b/scripts/docker/README.md index 87379d4..cb1d4bb 100644 --- a/scripts/docker/README.md +++ b/scripts/docker/README.md @@ -197,10 +197,69 @@ docker run --rm --platform linux/arm64 -v "$PWD:/workspace" ubuntu:22.04 \ /workspace/cmd/mdv-gui/build/bin/mdv-gui_linux_arm64/mdv-gui --version ``` +## Optimized Dockerfiles (New!) + +### 4. `Dockerfile.linux-amd64-optimized` (BuildKit Cache + Multi-Stage) + +**Enhanced version with:** +- BuildKit cache mounts for 30-60s faster rebuilds +- Pinned Wails version for reproducibility +- Go module layer caching +- Optional multi-stage runtime image + +**Build:** +```bash +docker buildx build --platform linux/amd64 \ + -f scripts/docker/Dockerfile.linux-amd64-optimized \ + -t mdv-linux-amd64-optimized --load . +``` + +**Run:** +```bash +docker run --rm -v "$PWD:/workspace" mdv-linux-amd64-optimized +``` + +**Benefits:** Subsequent builds are 50% faster due to cached Go modules. + +--- + +### 5. `Dockerfile.tui` (TUI-Only Container - ~10MB) + +**Minimal container for running mdv in TUI mode:** +- Tiny image size (~10MB) +- Multi-platform support (amd64, arm64, arm/v7) +- Static binary (no dependencies) +- Perfect for servers and CI/CD + +**Build:** +```bash +docker buildx build --platform linux/amd64,linux/arm64 \ + -f scripts/docker/Dockerfile.tui -t mdv-tui:latest --load . +``` + +**Run:** +```bash +# View your markdown files +docker run --rm -it -v "$PWD:/data" mdv-tui /data/README.md + +# View included examples +docker run --rm -it mdv-tui /examples/demo.md +``` + +--- + +## Performance Improvements + +The project now includes a `.dockerignore` file that reduces build context size: +- **Before:** ~2.9GB (includes .cache, dist, build artifacts) +- **After:** ~50MB (only source code and dependencies) +- **Impact:** Faster builds and smaller image transfer + ## Related Documentation +- **`../../DOCKER_ASSESSMENT.md`** - Comprehensive Docker audit and recommendations +- **`../../DOCKER_IMPLEMENTATION_GUIDE.md`** - Step-by-step implementation guide - **`cross-plat.md`** - Comprehensive cross-platform build guide -- **`DOCKER_ANALYSIS.md`** - Technical analysis of Docker approach - **`.github/workflows/release.yml`** - CI/CD configuration --- From 3644185f0ae4a28104fe7a7e0efa8451a05569f3 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 21:25:57 -0400 Subject: [PATCH 11/19] Flatten GUI artifacts after download --- .github/workflows/release.yml | 24 ++++++++++++++++++++++++ .github/workflows/test-release.yml | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ebccd32..c8d382b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,6 +109,30 @@ jobs: path: cmd/mdv-gui/build/bin/ merge-multiple: true + - name: Flatten downloaded GUI artifacts + shell: bash + run: | + set -euo pipefail + base_dir="cmd/mdv-gui/build/bin" + + if [ ! -d "$base_dir" ]; then + echo "Base GUI directory not found: $base_dir" + exit 1 + fi + + shopt -s nullglob dotglob + for artifact_dir in "$base_dir"/gui-*; do + if [ -d "$artifact_dir" ]; then + inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" + if [ -d "$inner_dir" ]; then + echo "Flattening $artifact_dir" + cp -a "$inner_dir"/. "$base_dir"/ + rm -rf "$artifact_dir" + fi + fi + done + shopt -u nullglob dotglob + - name: List downloaded artifacts run: | echo "=== Downloaded GUI artifacts ===" diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 247a644..dba5366 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -111,6 +111,30 @@ jobs: path: cmd/mdv-gui/build/bin/ merge-multiple: true + - name: Flatten downloaded GUI artifacts + shell: bash + run: | + set -euo pipefail + base_dir="cmd/mdv-gui/build/bin" + + if [ ! -d "$base_dir" ]; then + echo "Base GUI directory not found: $base_dir" + exit 1 + fi + + shopt -s nullglob dotglob + for artifact_dir in "$base_dir"/gui-*; do + if [ -d "$artifact_dir" ]; then + inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" + if [ -d "$inner_dir" ]; then + echo "Flattening $artifact_dir" + cp -a "$inner_dir"/. "$base_dir"/ + rm -rf "$artifact_dir" + fi + fi + done + shopt -u nullglob dotglob + - name: List downloaded artifacts shell: bash run: | From 54b47f8b7dff5ce200d04057fbb2c5f62a337679 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 21:44:12 -0400 Subject: [PATCH 12/19] Download GUI artifacts after GoReleaser --- .github/workflows/release.yml | 70 ++++++++++++++--------------- .github/workflows/test-release.yml | 72 +++++++++++++++--------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c8d382b..d9f7c6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,41 +103,6 @@ jobs: - name: Install Wails CLI run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - name: Download all GUI artifacts - uses: actions/download-artifact@v4 - with: - path: cmd/mdv-gui/build/bin/ - merge-multiple: true - - - name: Flatten downloaded GUI artifacts - shell: bash - run: | - set -euo pipefail - base_dir="cmd/mdv-gui/build/bin" - - if [ ! -d "$base_dir" ]; then - echo "Base GUI directory not found: $base_dir" - exit 1 - fi - - shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-*; do - if [ -d "$artifact_dir" ]; then - inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" - if [ -d "$inner_dir" ]; then - echo "Flattening $artifact_dir" - cp -a "$inner_dir"/. "$base_dir"/ - rm -rf "$artifact_dir" - fi - fi - done - shopt -u nullglob dotglob - - - name: List downloaded artifacts - run: | - echo "=== Downloaded GUI artifacts ===" - ls -laR cmd/mdv-gui/build/bin/ - - name: Configure signing keychain run: | set -euo pipefail @@ -186,6 +151,41 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Download all GUI artifacts + uses: actions/download-artifact@v4 + with: + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: Flatten downloaded GUI artifacts + shell: bash + run: | + set -euo pipefail + base_dir="cmd/mdv-gui/build/bin" + + if [ ! -d "$base_dir" ]; then + echo "Base GUI directory not found: $base_dir" + exit 1 + fi + + shopt -s nullglob dotglob + for artifact_dir in "$base_dir"/gui-*; do + if [ -d "$artifact_dir" ]; then + inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" + if [ -d "$inner_dir" ]; then + echo "Flattening $artifact_dir" + cp -a "$inner_dir"/. "$base_dir"/ + rm -rf "$artifact_dir" + fi + fi + done + shopt -u nullglob dotglob + + - name: List downloaded artifacts + run: | + echo "=== Downloaded GUI artifacts ===" + ls -laR cmd/mdv-gui/build/bin/ + - name: Create Linux GUI archives run: | set -euo pipefail diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index dba5366..4cc0c2f 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -105,42 +105,6 @@ jobs: - name: Install Wails CLI run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - name: Download all GUI artifacts - uses: actions/download-artifact@v4 - with: - path: cmd/mdv-gui/build/bin/ - merge-multiple: true - - - name: Flatten downloaded GUI artifacts - shell: bash - run: | - set -euo pipefail - base_dir="cmd/mdv-gui/build/bin" - - if [ ! -d "$base_dir" ]; then - echo "Base GUI directory not found: $base_dir" - exit 1 - fi - - shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-*; do - if [ -d "$artifact_dir" ]; then - inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" - if [ -d "$inner_dir" ]; then - echo "Flattening $artifact_dir" - cp -a "$inner_dir"/. "$base_dir"/ - rm -rf "$artifact_dir" - fi - fi - done - shopt -u nullglob dotglob - - - name: List downloaded artifacts - shell: bash - run: | - echo "=== Downloaded GUI artifacts ===" - ls -laR cmd/mdv-gui/build/bin/ - - name: Configure signing keychain shell: bash run: | @@ -190,6 +154,42 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Download all GUI artifacts + uses: actions/download-artifact@v4 + with: + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: Flatten downloaded GUI artifacts + shell: bash + run: | + set -euo pipefail + base_dir="cmd/mdv-gui/build/bin" + + if [ ! -d "$base_dir" ]; then + echo "Base GUI directory not found: $base_dir" + exit 1 + fi + + shopt -s nullglob dotglob + for artifact_dir in "$base_dir"/gui-*; do + if [ -d "$artifact_dir" ]; then + inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" + if [ -d "$inner_dir" ]; then + echo "Flattening $artifact_dir" + cp -a "$inner_dir"/. "$base_dir"/ + rm -rf "$artifact_dir" + fi + fi + done + shopt -u nullglob dotglob + + - name: List downloaded artifacts + shell: bash + run: | + echo "=== Downloaded GUI artifacts ===" + ls -laR cmd/mdv-gui/build/bin/ + - name: Create Linux GUI archives shell: bash run: | From bd5516d81e0d2887bdf50f13a68e1b2e3a07b1a8 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 22:29:22 -0400 Subject: [PATCH 13/19] Download only Linux/Windows GUI artifacts --- .github/workflows/release.yml | 18 +++++++++++++++--- .github/workflows/test-release.yml | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9f7c6c..52aa056 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -151,9 +151,17 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} - - name: Download all GUI artifacts + - name: Download Linux GUI artifacts uses: actions/download-artifact@v4 with: + pattern: gui-linux-* + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: Download Windows GUI artifacts + uses: actions/download-artifact@v4 + with: + pattern: gui-windows-* path: cmd/mdv-gui/build/bin/ merge-multiple: true @@ -169,12 +177,16 @@ jobs: fi shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-*; do + for artifact_dir in "$base_dir"/gui-linux-* "$base_dir"/gui-windows-*; do if [ -d "$artifact_dir" ]; then inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" if [ -d "$inner_dir" ]; then echo "Flattening $artifact_dir" - cp -a "$inner_dir"/. "$base_dir"/ + for item in "$inner_dir"/*; do + target="$base_dir/$(basename "$item")" + rm -rf "$target" + mv "$item" "$target" + done rm -rf "$artifact_dir" fi fi diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 4cc0c2f..2b06b4d 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -154,9 +154,17 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Download all GUI artifacts + - name: Download Linux GUI artifacts uses: actions/download-artifact@v4 with: + pattern: gui-linux-* + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: Download Windows GUI artifacts + uses: actions/download-artifact@v4 + with: + pattern: gui-windows-* path: cmd/mdv-gui/build/bin/ merge-multiple: true @@ -172,12 +180,16 @@ jobs: fi shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-*; do + for artifact_dir in "$base_dir"/gui-linux-* "$base_dir"/gui-windows-*; do if [ -d "$artifact_dir" ]; then inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" if [ -d "$inner_dir" ]; then echo "Flattening $artifact_dir" - cp -a "$inner_dir"/. "$base_dir"/ + for item in "$inner_dir"/*; do + target="$base_dir/$(basename "$item")" + rm -rf "$target" + mv "$item" "$target" + done rm -rf "$artifact_dir" fi fi From 7541e225446a48878e5f4c5e0939acc1f548612d Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 22:45:44 -0400 Subject: [PATCH 14/19] Skip non-mac GUI in signature check --- scripts/verify-macos-signatures.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/verify-macos-signatures.sh b/scripts/verify-macos-signatures.sh index 51a3ad6..5031171 100755 --- a/scripts/verify-macos-signatures.sh +++ b/scripts/verify-macos-signatures.sh @@ -40,6 +40,11 @@ echo "verify: checking GUI binaries and app bundles" for artifact_dir in "${ROOT_DIR}"/cmd/mdv-gui/build/bin/mdv-gui_*; do [[ -d "$artifact_dir" ]] || continue + if [[ "$artifact_dir" != *"darwin"* ]]; then + echo "verify: skipping non-macOS GUI artifact ${artifact_dir##*/}" + continue + fi + gui_bin="${artifact_dir}/mdv-gui" gui_app="${artifact_dir}/mdv-gui.app" From 287670f3c7718fbdb4acb940a882fb91d279051b Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 22:59:46 -0400 Subject: [PATCH 15/19] Fix Windows GUI artifact download pattern --- .github/workflows/release.yml | 4 ++-- .github/workflows/test-release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52aa056..c2efafb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -161,7 +161,7 @@ jobs: - name: Download Windows GUI artifacts uses: actions/download-artifact@v4 with: - pattern: gui-windows-* + pattern: gui-windows* path: cmd/mdv-gui/build/bin/ merge-multiple: true @@ -177,7 +177,7 @@ jobs: fi shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-linux-* "$base_dir"/gui-windows-*; do + for artifact_dir in "$base_dir"/gui-*; do if [ -d "$artifact_dir" ]; then inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" if [ -d "$inner_dir" ]; then diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 2b06b4d..7a9baa2 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -164,7 +164,7 @@ jobs: - name: Download Windows GUI artifacts uses: actions/download-artifact@v4 with: - pattern: gui-windows-* + pattern: gui-windows* path: cmd/mdv-gui/build/bin/ merge-multiple: true @@ -180,7 +180,7 @@ jobs: fi shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-linux-* "$base_dir"/gui-windows-*; do + for artifact_dir in "$base_dir"/gui-*; do if [ -d "$artifact_dir" ]; then inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" if [ -d "$inner_dir" ]; then From 1be18287459bb109fae0f7da65d5ea9a719055cb Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 23:18:37 -0400 Subject: [PATCH 16/19] Fix Windows GUI archive path --- .github/workflows/release.yml | 11 ++++++++--- .github/workflows/test-release.yml | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2efafb..fab3d3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -249,6 +249,7 @@ jobs: if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" + archive_path="$(pwd)/$archive" echo "Creating $archive" temp_dir=$(mktemp -d) @@ -257,11 +258,15 @@ jobs: cp README* "$temp_dir/" 2>/dev/null || true cp -r examples "$temp_dir/" 2>/dev/null || true - # Create zip archive - (cd "$temp_dir" && zip -r "../$archive" .) + # Create zip archive in repository root + (cd "$temp_dir" && zip -r "$archive_path" .) rm -rf "$temp_dir" - echo "Created: $archive ($(du -h "$archive" | cut -f1))" + if [ -f "$archive_path" ]; then + echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" + else + echo "Warning: Failed to create $archive" + fi else echo "Warning: Windows $arch GUI binary not found, skipping" fi diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 7a9baa2..9e28013 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -251,6 +251,7 @@ jobs: if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" + archive_path="$(pwd)/$archive" echo "Creating $archive" temp_dir=$(mktemp -d) @@ -259,11 +260,15 @@ jobs: cp README* "$temp_dir/" 2>/dev/null || true cp -r examples "$temp_dir/" 2>/dev/null || true - # Create zip archive - (cd "$temp_dir" && zip -r "../$archive" .) + # Create zip archive in repository root + (cd "$temp_dir" && zip -r "$archive_path" .) rm -rf "$temp_dir" - echo "Created: $archive ($(du -h "$archive" | cut -f1))" + if [ -f "$archive_path" ]; then + echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" + else + echo "Warning: Failed to create $archive" + fi else echo "Warning: Windows $arch GUI binary not found, skipping" fi From b19b79b3599320a0163792e8011971102c8db8ad Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 23:34:33 -0400 Subject: [PATCH 17/19] workflow improvements --- .github/workflows/release.yml | 39 ++++++++++++++++++++++++------ .github/workflows/test-release.yml | 35 +++++++++++++++++++++------ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fab3d3b..14d7630 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -203,6 +203,8 @@ jobs: set -euo pipefail VERSION="${GITHUB_REF#refs/tags/}" + # Linux uses tar from outside the target directory: + # tar can archive from any location using -C flag for arch in amd64 arm64; do arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" @@ -211,16 +213,21 @@ jobs: archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" echo "Creating $archive" - mkdir -p /tmp/mdv-gui-linux - cp "$gui_dir/mdv-gui" /tmp/mdv-gui-linux/ - cp LICENSE* /tmp/mdv-gui-linux/ || true - cp README* /tmp/mdv-gui-linux/ || true - cp -r examples /tmp/mdv-gui-linux/ || true + temp_dir=$(mktemp -d) + cp "$gui_dir/mdv-gui" "$temp_dir/" + cp LICENSE* "$temp_dir/" 2>/dev/null || true + cp README* "$temp_dir/" 2>/dev/null || true + cp -r examples "$temp_dir/" 2>/dev/null || true - tar -czf "$archive" -C /tmp/mdv-gui-linux . - rm -rf /tmp/mdv-gui-linux + tar -czf "$archive" -C "$temp_dir" . + rm -rf "$temp_dir" - echo "Created: $archive ($(du -h "$archive" | cut -f1))" + if [ -f "$archive" ]; then + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + shasum -a 256 "$archive" | tee -a gui-checksums.txt + else + echo "Warning: Failed to create $archive" + fi else echo "Warning: Linux $arch GUI binary not found, skipping" fi @@ -243,6 +250,9 @@ jobs: set -euo pipefail VERSION="${GITHUB_REF#refs/tags/}" + # Windows uses zip from inside the target directory: + # zip archives are typically created from within the directory being zipped + # We use an absolute path to ensure the archive is created in the repo root for arch in amd64; do arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" @@ -264,6 +274,7 @@ jobs: if [ -f "$archive_path" ]; then echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" + shasum -a 256 "$archive_path" | tee -a gui-checksums.txt else echo "Warning: Failed to create $archive" fi @@ -284,5 +295,17 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Upload GUI checksums to release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + if [ -f "gui-checksums.txt" ]; then + echo "Uploading GUI checksums to release..." + gh release upload "${GITHUB_REF#refs/tags/}" "gui-checksums.txt" --clobber + else + echo "Warning: gui-checksums.txt not found" + fi + env: + GITHUB_TOKEN: ${{ secrets.GH_PAT }} + - name: Verify macOS signatures run: ./scripts/verify-macos-signatures.sh diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 9e28013..48dd2a7 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -208,6 +208,8 @@ jobs: set -euo pipefail VERSION="test-$(git rev-parse --short HEAD)" + # Linux uses tar from outside the target directory: + # tar can archive from any location using -C flag for arch in amd64 arm64; do arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" @@ -216,16 +218,21 @@ jobs: archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" echo "Creating $archive" - mkdir -p /tmp/mdv-gui-linux - cp "$gui_dir/mdv-gui" /tmp/mdv-gui-linux/ - cp LICENSE* /tmp/mdv-gui-linux/ || true - cp README* /tmp/mdv-gui-linux/ || true - cp -r examples /tmp/mdv-gui-linux/ || true + temp_dir=$(mktemp -d) + cp "$gui_dir/mdv-gui" "$temp_dir/" + cp LICENSE* "$temp_dir/" 2>/dev/null || true + cp README* "$temp_dir/" 2>/dev/null || true + cp -r examples "$temp_dir/" 2>/dev/null || true - tar -czf "$archive" -C /tmp/mdv-gui-linux . - rm -rf /tmp/mdv-gui-linux + tar -czf "$archive" -C "$temp_dir" . + rm -rf "$temp_dir" - echo "Created: $archive ($(du -h "$archive" | cut -f1))" + if [ -f "$archive" ]; then + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + shasum -a 256 "$archive" | tee -a gui-checksums.txt + else + echo "Warning: Failed to create $archive" + fi else echo "Warning: Linux $arch GUI binary not found, skipping" fi @@ -245,6 +252,9 @@ jobs: set -euo pipefail VERSION="test-$(git rev-parse --short HEAD)" + # Windows uses zip from inside the target directory: + # zip archives are typically created from within the directory being zipped + # We use an absolute path to ensure the archive is created in the repo root for arch in amd64; do arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" @@ -266,6 +276,7 @@ jobs: if [ -f "$archive_path" ]; then echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" + shasum -a 256 "$archive_path" | tee -a gui-checksums.txt else echo "Warning: Failed to create $archive" fi @@ -282,6 +293,14 @@ jobs: if-no-files-found: ignore retention-days: 7 + - name: Upload GUI checksums + uses: actions/upload-artifact@v4 + with: + name: gui-checksums + path: gui-checksums.txt + if-no-files-found: ignore + retention-days: 7 + - name: Verify macOS signatures run: ./scripts/verify-macos-signatures.sh From eafe19c1354681e145635f09974c3154c820de30 Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 23:48:54 -0400 Subject: [PATCH 18/19] Consolidated workflowsn --- .github/workflows/build-and-package.yml | 332 ++++++++++++++++++++++ .github/workflows/release.yml | 281 ++----------------- .github/workflows/test-release.yml | 348 ++++-------------------- 3 files changed, 407 insertions(+), 554 deletions(-) create mode 100644 .github/workflows/build-and-package.yml diff --git a/.github/workflows/build-and-package.yml b/.github/workflows/build-and-package.yml new file mode 100644 index 0000000..f5c9437 --- /dev/null +++ b/.github/workflows/build-and-package.yml @@ -0,0 +1,332 @@ +name: Build and Package (Reusable) + +on: + workflow_call: + inputs: + is_release: + description: 'Whether this is a release build (affects version naming and GoReleaser mode)' + required: true + type: boolean + artifact_retention_days: + description: 'How long to retain artifacts (in days)' + required: false + type: number + default: 7 + secrets: + github_token: + description: 'GitHub token for GoReleaser (GITHUB_TOKEN or GH_PAT)' + required: true + macos_sign_p12: + description: 'macOS signing certificate (base64 encoded P12)' + required: true + macos_sign_password: + description: 'Password for macOS signing certificate' + required: true + macos_notary_key: + description: 'Apple notarization API key (base64 encoded)' + required: true + macos_notary_key_id: + description: 'Apple notarization key ID' + required: true + macos_notary_issuer_id: + description: 'Apple notarization issuer ID' + required: true + outputs: + version: + description: 'Version string used for this build' + value: ${{ jobs.package.outputs.version }} + +jobs: + # Build GUI binaries on native platforms + build-gui: + name: Build GUI (${{ matrix.name }}) + strategy: + matrix: + include: + - name: linux-amd64 + os: ubuntu-22.04 + platforms: linux/amd64 + cc: gcc + artifact-name: gui-linux-amd64 + - name: linux-arm64 + os: ubuntu-22.04-arm + platforms: linux/arm64 + cc: gcc + artifact-name: gui-linux-arm64 + - name: macos + os: macos-latest + platforms: darwin/amd64,darwin/arm64 + cc: "" + artifact-name: gui-macos + - name: windows + os: windows-latest + platforms: windows/amd64 + cc: "" + artifact-name: gui-windows + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + + # Install native build tools + sudo apt-get install -y \ + build-essential \ + pkg-config + + # Install native GTK/WebKit libraries (works for both amd64 and arm64) + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev + + - name: Install Windows dependencies + if: runner.os == 'Windows' + run: | + choco install mingw -y + refreshenv + + - name: Build GUI + env: + WAILS_PLATFORMS: ${{ matrix.platforms }} + CC: ${{ matrix.cc }} + shell: bash + run: ./scripts/build-wails.sh + + - name: Upload GUI artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: cmd/mdv-gui/build/bin/ + retention-days: ${{ inputs.artifact_retention_days }} + + # Package and sign artifacts + package: + needs: build-gui + runs-on: macos-latest + outputs: + version: ${{ steps.set-version.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Set version + id: set-version + shell: bash + run: | + if [ "${{ inputs.is_release }}" = "true" ]; then + VERSION="${GITHUB_REF#refs/tags/}" + else + VERSION="test-$(git rev-parse --short HEAD)" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building version: $VERSION" + + - name: Configure signing keychain + shell: bash + run: | + set -euo pipefail + + KEYCHAIN_PATH="$RUNNER_TEMP/mdv-signing.keychain-db" + KEYCHAIN_PASSWORD="$(uuidgen)" + CERT_PATH="$RUNNER_TEMP/mdv-signing-cert.p12" + NOTARY_KEY_PATH="$RUNNER_TEMP/mdv-notary-key.p8" + PROFILE_NAME="mdv-notary-profile" + + printf '%s' "$MACOS_SIGN_P12" | base64 --decode >"$CERT_PATH" + printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode >"$NOTARY_KEY_PATH" + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERT_PATH" -P "$MACOS_SIGN_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" + security default-keychain -d user -s "$KEYCHAIN_PATH" + + xcrun notarytool store-credentials "$PROFILE_NAME" \ + --key "$NOTARY_KEY_PATH" \ + --key-id "$MACOS_NOTARY_KEY_ID" \ + --issuer "$MACOS_NOTARY_ISSUER_ID" \ + --keychain "$KEYCHAIN_PATH" + + { + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" + echo "MACOS_NOTARY_PROFILE_NAME=$PROFILE_NAME" + echo "CODESIGN_IDENTITY=Developer ID Application: Atlas Atlas Atlas LLC (294CD3C5SP)" + } >>"$GITHUB_ENV" + env: + MACOS_SIGN_P12: ${{ secrets.macos_sign_p12 }} + MACOS_SIGN_PASSWORD: ${{ secrets.macos_sign_password }} + MACOS_NOTARY_KEY: ${{ secrets.macos_notary_key }} + MACOS_NOTARY_KEY_ID: ${{ secrets.macos_notary_key_id }} + MACOS_NOTARY_ISSUER_ID: ${{ secrets.macos_notary_issuer_id }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6.4.0 + with: + distribution: goreleaser + version: latest + args: release ${{ inputs.is_release && '--clean' || '--snapshot --clean' }} + env: + GITHUB_TOKEN: ${{ secrets.github_token }} + + - name: Download Linux GUI artifacts + uses: actions/download-artifact@v4 + with: + pattern: gui-linux-* + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: Download Windows GUI artifacts + uses: actions/download-artifact@v4 + with: + pattern: gui-windows* + path: cmd/mdv-gui/build/bin/ + merge-multiple: true + + - name: Flatten downloaded GUI artifacts + shell: bash + run: | + set -euo pipefail + base_dir="cmd/mdv-gui/build/bin" + + if [ ! -d "$base_dir" ]; then + echo "Base GUI directory not found: $base_dir" + exit 1 + fi + + shopt -s nullglob dotglob + for artifact_dir in "$base_dir"/gui-*; do + if [ -d "$artifact_dir" ]; then + inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" + if [ -d "$inner_dir" ]; then + echo "Flattening $artifact_dir" + for item in "$inner_dir"/*; do + target="$base_dir/$(basename "$item")" + rm -rf "$target" + mv "$item" "$target" + done + rm -rf "$artifact_dir" + fi + fi + done + shopt -u nullglob dotglob + + - name: List downloaded artifacts + shell: bash + run: | + echo "=== Downloaded GUI artifacts ===" + ls -laR cmd/mdv-gui/build/bin/ + + - name: Create Linux GUI archives + shell: bash + run: | + set -euo pipefail + VERSION="${{ steps.set-version.outputs.version }}" + + # Linux uses tar from outside the target directory: + # tar can archive from any location using -C flag + for arch in amd64 arm64; do + arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") + gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" + + if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui" ]; then + archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" + echo "Creating $archive" + + temp_dir=$(mktemp -d) + cp "$gui_dir/mdv-gui" "$temp_dir/" + cp LICENSE* "$temp_dir/" 2>/dev/null || true + cp README* "$temp_dir/" 2>/dev/null || true + cp -r examples "$temp_dir/" 2>/dev/null || true + + tar -czf "$archive" -C "$temp_dir" . + rm -rf "$temp_dir" + + if [ -f "$archive" ]; then + echo "Created: $archive ($(du -h "$archive" | cut -f1))" + shasum -a 256 "$archive" | tee -a gui-checksums.txt + else + echo "Warning: Failed to create $archive" + fi + else + echo "Warning: Linux $arch GUI binary not found, skipping" + fi + done + + - name: Create Windows GUI archives + shell: bash + run: | + set -euo pipefail + VERSION="${{ steps.set-version.outputs.version }}" + + # Windows uses zip from inside the target directory: + # zip archives are typically created from within the directory being zipped + # We use an absolute path to ensure the archive is created in the repo root + for arch in amd64; do + arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") + gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" + + if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then + archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" + archive_path="$(pwd)/$archive" + echo "Creating $archive" + + temp_dir=$(mktemp -d) + cp "$gui_dir/mdv-gui.exe" "$temp_dir/" + cp LICENSE* "$temp_dir/" 2>/dev/null || true + cp README* "$temp_dir/" 2>/dev/null || true + cp -r examples "$temp_dir/" 2>/dev/null || true + + # Create zip archive in repository root + (cd "$temp_dir" && zip -r "$archive_path" .) + rm -rf "$temp_dir" + + if [ -f "$archive_path" ]; then + echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" + shasum -a 256 "$archive_path" | tee -a gui-checksums.txt + else + echo "Warning: Failed to create $archive" + fi + else + echo "Warning: Windows $arch GUI binary not found, skipping" + fi + done + + - name: Verify macOS signatures + run: ./scripts/verify-macos-signatures.sh + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: | + dist/ + mdv-gui_*.tar.gz + mdv-gui_*.zip + gui-checksums.txt + retention-days: ${{ inputs.artifact_retention_days }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14d7630..0d05e88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,231 +10,35 @@ permissions: contents: write jobs: - # Build GUI binaries on native platforms - build-gui: - name: Build GUI (${{ matrix.name }}) - strategy: - matrix: - include: - - name: linux-amd64 - os: ubuntu-22.04 - platforms: linux/amd64 - cc: gcc - artifact-name: gui-linux-amd64 - - name: linux-arm64 - os: ubuntu-22.04-arm - platforms: linux/arm64 - cc: gcc - artifact-name: gui-linux-arm64 - - name: macos - os: macos-latest - platforms: darwin/amd64,darwin/arm64 - cc: "" - artifact-name: gui-macos - - name: windows - os: windows-latest - platforms: windows/amd64 - cc: "" - artifact-name: gui-windows - runs-on: ${{ matrix.os }} + # Call reusable workflow to build and package + build-and-package: + uses: ./.github/workflows/build-and-package.yml + with: + is_release: true + artifact_retention_days: 1 + secrets: + github_token: ${{ secrets.GH_PAT }} + macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} + macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} + macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} + macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }} + macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + + # Upload artifacts to GitHub Release + upload-to-release: + needs: build-and-package + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - - name: Install Wails CLI - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - - # Install native build tools - sudo apt-get install -y \ - build-essential \ - pkg-config - - # Install native GTK/WebKit libraries (works for both amd64 and arm64) - sudo apt-get install -y \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev - - - name: Install Windows dependencies - if: runner.os == 'Windows' - run: | - choco install mingw -y - refreshenv - - - name: Build GUI - env: - WAILS_PLATFORMS: ${{ matrix.platforms }} - CC: ${{ matrix.cc }} - run: ./scripts/build-wails.sh - - - name: Upload GUI artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact-name }} - path: cmd/mdv-gui/build/bin/ - retention-days: 1 - - # Main release job - release: - needs: build-gui - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - - - name: Install Wails CLI - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - - name: Configure signing keychain - run: | - set -euo pipefail - - KEYCHAIN_PATH="$RUNNER_TEMP/mdv-signing.keychain-db" - KEYCHAIN_PASSWORD="$(uuidgen)" - CERT_PATH="$RUNNER_TEMP/mdv-signing-cert.p12" - NOTARY_KEY_PATH="$RUNNER_TEMP/mdv-notary-key.p8" - PROFILE_NAME="mdv-notary-profile" - - printf '%s' "$MACOS_SIGN_P12" | base64 --decode >"$CERT_PATH" - printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode >"$NOTARY_KEY_PATH" - - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security import "$CERT_PATH" -P "$MACOS_SIGN_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" - security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security list-keychain -d user -s "$KEYCHAIN_PATH" - security default-keychain -d user -s "$KEYCHAIN_PATH" - - xcrun notarytool store-credentials "$PROFILE_NAME" \ - --key "$NOTARY_KEY_PATH" \ - --key-id "$MACOS_NOTARY_KEY_ID" \ - --issuer "$MACOS_NOTARY_ISSUER_ID" \ - --keychain "$KEYCHAIN_PATH" - - { - echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" - echo "MACOS_NOTARY_PROFILE_NAME=$PROFILE_NAME" - echo "CODESIGN_IDENTITY=Developer ID Application: Atlas Atlas Atlas LLC (294CD3C5SP)" - } >>"$GITHUB_ENV" - env: - MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} - MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} - MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} - MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} - MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6.4.0 - with: - distribution: goreleaser - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GH_PAT }} - - - name: Download Linux GUI artifacts - uses: actions/download-artifact@v4 - with: - pattern: gui-linux-* - path: cmd/mdv-gui/build/bin/ - merge-multiple: true - - - name: Download Windows GUI artifacts + - name: Download build artifacts uses: actions/download-artifact@v4 with: - pattern: gui-windows* - path: cmd/mdv-gui/build/bin/ - merge-multiple: true - - - name: Flatten downloaded GUI artifacts - shell: bash - run: | - set -euo pipefail - base_dir="cmd/mdv-gui/build/bin" - - if [ ! -d "$base_dir" ]; then - echo "Base GUI directory not found: $base_dir" - exit 1 - fi - - shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-*; do - if [ -d "$artifact_dir" ]; then - inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" - if [ -d "$inner_dir" ]; then - echo "Flattening $artifact_dir" - for item in "$inner_dir"/*; do - target="$base_dir/$(basename "$item")" - rm -rf "$target" - mv "$item" "$target" - done - rm -rf "$artifact_dir" - fi - fi - done - shopt -u nullglob dotglob - - - name: List downloaded artifacts - run: | - echo "=== Downloaded GUI artifacts ===" - ls -laR cmd/mdv-gui/build/bin/ - - - name: Create Linux GUI archives - run: | - set -euo pipefail - VERSION="${GITHUB_REF#refs/tags/}" - - # Linux uses tar from outside the target directory: - # tar can archive from any location using -C flag - for arch in amd64 arm64; do - arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") - gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" - - if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui" ]; then - archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" - echo "Creating $archive" - - temp_dir=$(mktemp -d) - cp "$gui_dir/mdv-gui" "$temp_dir/" - cp LICENSE* "$temp_dir/" 2>/dev/null || true - cp README* "$temp_dir/" 2>/dev/null || true - cp -r examples "$temp_dir/" 2>/dev/null || true - - tar -czf "$archive" -C "$temp_dir" . - rm -rf "$temp_dir" - - if [ -f "$archive" ]; then - echo "Created: $archive ($(du -h "$archive" | cut -f1))" - shasum -a 256 "$archive" | tee -a gui-checksums.txt - else - echo "Warning: Failed to create $archive" - fi - else - echo "Warning: Linux $arch GUI binary not found, skipping" - fi - done + name: build-artifacts - name: Upload Linux GUI archives to release - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') run: | for archive in mdv-gui_*.tar.gz; do if [ -f "$archive" ]; then @@ -245,46 +49,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} - - name: Create Windows GUI archives - run: | - set -euo pipefail - VERSION="${GITHUB_REF#refs/tags/}" - - # Windows uses zip from inside the target directory: - # zip archives are typically created from within the directory being zipped - # We use an absolute path to ensure the archive is created in the repo root - for arch in amd64; do - arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") - gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" - - if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then - archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" - archive_path="$(pwd)/$archive" - echo "Creating $archive" - - temp_dir=$(mktemp -d) - cp "$gui_dir/mdv-gui.exe" "$temp_dir/" - cp LICENSE* "$temp_dir/" 2>/dev/null || true - cp README* "$temp_dir/" 2>/dev/null || true - cp -r examples "$temp_dir/" 2>/dev/null || true - - # Create zip archive in repository root - (cd "$temp_dir" && zip -r "$archive_path" .) - rm -rf "$temp_dir" - - if [ -f "$archive_path" ]; then - echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" - shasum -a 256 "$archive_path" | tee -a gui-checksums.txt - else - echo "Warning: Failed to create $archive" - fi - else - echo "Warning: Windows $arch GUI binary not found, skipping" - fi - done - - name: Upload Windows GUI archives to release - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') run: | for archive in mdv-gui_*_windows_*.zip; do if [ -f "$archive" ]; then @@ -296,7 +61,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GH_PAT }} - name: Upload GUI checksums to release - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') run: | if [ -f "gui-checksums.txt" ]; then echo "Uploading GUI checksums to release..." @@ -306,6 +70,3 @@ jobs: fi env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} - - - name: Verify macOS signatures - run: ./scripts/verify-macos-signatures.sh diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 48dd2a7..9f98549 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -11,298 +11,32 @@ permissions: pull-requests: write # Required to post PR comments jobs: - # Build GUI binaries on native platforms (same as release.yml) - build-gui: - name: Build GUI (${{ matrix.name }}) - strategy: - matrix: - include: - - name: linux-amd64 - os: ubuntu-22.04 - platforms: linux/amd64 - cc: gcc - artifact-name: gui-linux-amd64 - - name: linux-arm64 - os: ubuntu-22.04-arm # Try native ARM64 runner - platforms: linux/arm64 - cc: gcc # Native gcc instead of cross-compiler - artifact-name: gui-linux-arm64 - - name: macos - os: macos-latest - platforms: darwin/amd64,darwin/arm64 - cc: "" - artifact-name: gui-macos - - name: windows - os: windows-latest - platforms: windows/amd64 - cc: "" - artifact-name: gui-windows - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - - - name: Install Wails CLI - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - - # Install native build tools - sudo apt-get install -y \ - build-essential \ - pkg-config - - # Install native GTK/WebKit libraries (works for both amd64 and arm64) - sudo apt-get install -y \ - libgtk-3-dev \ - libwebkit2gtk-4.0-dev - - - name: Install Windows dependencies - if: runner.os == 'Windows' - run: | - choco install mingw -y - refreshenv - - - name: Build GUI - env: - WAILS_PLATFORMS: ${{ matrix.platforms }} - CC: ${{ matrix.cc }} - shell: bash - run: ./scripts/build-wails.sh - - - name: Upload GUI artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact-name }} - path: cmd/mdv-gui/build/bin/ - retention-days: 7 - - # Test release job (runs GoReleaser in snapshot mode) - test-release: - needs: build-gui + # Call reusable workflow to build and package + build-and-package: + uses: ./.github/workflows/build-and-package.yml + with: + is_release: false + artifact_retention_days: 7 + secrets: + github_token: ${{ secrets.GITHUB_TOKEN }} + macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} + macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} + macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} + macos_notary_key_id: ${{ secrets.MACOS_NOTARY_KEY_ID }} + macos_notary_issuer_id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + + # Validate release artifacts + validate: + needs: build-and-package runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - - - name: Install Wails CLI - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - - name: Configure signing keychain - shell: bash - run: | - set -euo pipefail - - KEYCHAIN_PATH="$RUNNER_TEMP/mdv-signing.keychain-db" - KEYCHAIN_PASSWORD="$(uuidgen)" - CERT_PATH="$RUNNER_TEMP/mdv-signing-cert.p12" - NOTARY_KEY_PATH="$RUNNER_TEMP/mdv-notary-key.p8" - PROFILE_NAME="mdv-notary-profile" - - printf '%s' "$MACOS_SIGN_P12" | base64 --decode >"$CERT_PATH" - printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode >"$NOTARY_KEY_PATH" - - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security import "$CERT_PATH" -P "$MACOS_SIGN_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" - security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security list-keychain -d user -s "$KEYCHAIN_PATH" - security default-keychain -d user -s "$KEYCHAIN_PATH" - - xcrun notarytool store-credentials "$PROFILE_NAME" \ - --key "$NOTARY_KEY_PATH" \ - --key-id "$MACOS_NOTARY_KEY_ID" \ - --issuer "$MACOS_NOTARY_ISSUER_ID" \ - --keychain "$KEYCHAIN_PATH" - - { - echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" - echo "MACOS_NOTARY_PROFILE_NAME=$PROFILE_NAME" - echo "CODESIGN_IDENTITY=Developer ID Application: Atlas Atlas Atlas LLC (294CD3C5SP)" - } >>"$GITHUB_ENV" - env: - MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} - MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} - MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} - MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} - MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} - - - name: Run GoReleaser (snapshot mode) - uses: goreleaser/goreleaser-action@v6.4.0 - with: - distribution: goreleaser - version: latest - args: release --snapshot --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Download Linux GUI artifacts - uses: actions/download-artifact@v4 - with: - pattern: gui-linux-* - path: cmd/mdv-gui/build/bin/ - merge-multiple: true - - name: Download Windows GUI artifacts + - name: Download build artifacts uses: actions/download-artifact@v4 with: - pattern: gui-windows* - path: cmd/mdv-gui/build/bin/ - merge-multiple: true - - - name: Flatten downloaded GUI artifacts - shell: bash - run: | - set -euo pipefail - base_dir="cmd/mdv-gui/build/bin" - - if [ ! -d "$base_dir" ]; then - echo "Base GUI directory not found: $base_dir" - exit 1 - fi - - shopt -s nullglob dotglob - for artifact_dir in "$base_dir"/gui-*; do - if [ -d "$artifact_dir" ]; then - inner_dir="$artifact_dir/cmd/mdv-gui/build/bin" - if [ -d "$inner_dir" ]; then - echo "Flattening $artifact_dir" - for item in "$inner_dir"/*; do - target="$base_dir/$(basename "$item")" - rm -rf "$target" - mv "$item" "$target" - done - rm -rf "$artifact_dir" - fi - fi - done - shopt -u nullglob dotglob - - - name: List downloaded artifacts - shell: bash - run: | - echo "=== Downloaded GUI artifacts ===" - ls -laR cmd/mdv-gui/build/bin/ - - - name: Create Linux GUI archives - shell: bash - run: | - set -euo pipefail - VERSION="test-$(git rev-parse --short HEAD)" - - # Linux uses tar from outside the target directory: - # tar can archive from any location using -C flag - for arch in amd64 arm64; do - arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") - gui_dir="cmd/mdv-gui/build/bin/mdv-gui_linux_${arch}" - - if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui" ]; then - archive="mdv-gui_${VERSION}_linux_${arch_name}.tar.gz" - echo "Creating $archive" - - temp_dir=$(mktemp -d) - cp "$gui_dir/mdv-gui" "$temp_dir/" - cp LICENSE* "$temp_dir/" 2>/dev/null || true - cp README* "$temp_dir/" 2>/dev/null || true - cp -r examples "$temp_dir/" 2>/dev/null || true - - tar -czf "$archive" -C "$temp_dir" . - rm -rf "$temp_dir" - - if [ -f "$archive" ]; then - echo "Created: $archive ($(du -h "$archive" | cut -f1))" - shasum -a 256 "$archive" | tee -a gui-checksums.txt - else - echo "Warning: Failed to create $archive" - fi - else - echo "Warning: Linux $arch GUI binary not found, skipping" - fi - done - - - name: Upload Linux GUI archives - uses: actions/upload-artifact@v4 - with: - name: gui-linux-archives - path: mdv-gui_*_linux_*.tar.gz - if-no-files-found: ignore - retention-days: 7 - - - name: Create Windows GUI archives - shell: bash - run: | - set -euo pipefail - VERSION="test-$(git rev-parse --short HEAD)" - - # Windows uses zip from inside the target directory: - # zip archives are typically created from within the directory being zipped - # We use an absolute path to ensure the archive is created in the repo root - for arch in amd64; do - arch_name=$([ "$arch" = "amd64" ] && echo "x86_64" || echo "$arch") - gui_dir="cmd/mdv-gui/build/bin/mdv-gui_windows_${arch}" - - if [ -d "$gui_dir" ] && [ -f "$gui_dir/mdv-gui.exe" ]; then - archive="mdv-gui_${VERSION}_windows_${arch_name}.zip" - archive_path="$(pwd)/$archive" - echo "Creating $archive" - - temp_dir=$(mktemp -d) - cp "$gui_dir/mdv-gui.exe" "$temp_dir/" - cp LICENSE* "$temp_dir/" 2>/dev/null || true - cp README* "$temp_dir/" 2>/dev/null || true - cp -r examples "$temp_dir/" 2>/dev/null || true - - # Create zip archive in repository root - (cd "$temp_dir" && zip -r "$archive_path" .) - rm -rf "$temp_dir" - - if [ -f "$archive_path" ]; then - echo "Created: $archive ($(du -h "$archive_path" | cut -f1))" - shasum -a 256 "$archive_path" | tee -a gui-checksums.txt - else - echo "Warning: Failed to create $archive" - fi - else - echo "Warning: Windows $arch GUI binary not found, skipping" - fi - done - - - name: Upload Windows GUI archives - uses: actions/upload-artifact@v4 - with: - name: gui-windows-archives - path: mdv-gui_*_windows_*.zip - if-no-files-found: ignore - retention-days: 7 - - - name: Upload GUI checksums - uses: actions/upload-artifact@v4 - with: - name: gui-checksums - path: gui-checksums.txt - if-no-files-found: ignore - retention-days: 7 - - - name: Verify macOS signatures - run: ./scripts/verify-macos-signatures.sh + name: build-artifacts - name: Validate release artifacts shell: bash @@ -355,21 +89,33 @@ jobs: echo "❌ No checksums.txt found" fi + # Check GUI checksums + echo "" + if [ -f "gui-checksums.txt" ]; then + echo "✅ GUI checksums file created:" + cat gui-checksums.txt + else + echo "❌ No gui-checksums.txt found" + fi + echo "" echo "=== Validation Complete ===" - - name: Upload test release artifacts - uses: actions/upload-artifact@v4 + # Comment on PR with build summary + comment-pr: + needs: build-and-package + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 with: - name: test-release-artifacts - path: | - dist/ - mdv-gui_*.tar.gz - mdv-gui_*.zip - retention-days: 7 + name: build-artifacts - name: Comment PR with artifact summary - if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | @@ -379,6 +125,7 @@ jobs: // Get artifact sizes let summary = '## 🧪 Test Release Summary\n\n'; summary += 'All binaries built and signed successfully! ✅\n\n'; + summary += `**Version:** \`${{ needs.build-and-package.outputs.version }}\`\n\n`; summary += '### Artifacts Created\n\n'; try { @@ -423,6 +170,19 @@ jobs: } }); + // Add GUI checksums if available + try { + const checksums = fs.readFileSync('gui-checksums.txt', 'utf8'); + if (checksums.trim()) { + summary += '### GUI Checksums (SHA256)\n\n'; + summary += '```\n'; + summary += checksums; + summary += '```\n\n'; + } + } catch (e) { + // Checksums file not found or error reading + } + summary += '### Platform Coverage\n\n'; summary += '- ✅ macOS (darwin/amd64, darwin/arm64) - Signed & Notarized\n'; summary += '- ✅ Linux (linux/amd64, linux/arm64)\n'; From a902fea7a1a9bdb95ea41bfda270c5326c1d3abd Mon Sep 17 00:00:00 2001 From: Atlas Wegman Date: Fri, 10 Oct 2025 23:53:50 -0400 Subject: [PATCH 19/19] Fix workflow --- .github/workflows/build-and-package.yml | 4 ++-- .github/workflows/release.yml | 2 +- .github/workflows/test-release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-package.yml b/.github/workflows/build-and-package.yml index f5c9437..75acab2 100644 --- a/.github/workflows/build-and-package.yml +++ b/.github/workflows/build-and-package.yml @@ -13,7 +13,7 @@ on: type: number default: 7 secrets: - github_token: + gh_token: description: 'GitHub token for GoReleaser (GITHUB_TOKEN or GH_PAT)' required: true macos_sign_p12: @@ -192,7 +192,7 @@ jobs: version: latest args: release ${{ inputs.is_release && '--clean' || '--snapshot --clean' }} env: - GITHUB_TOKEN: ${{ secrets.github_token }} + GITHUB_TOKEN: ${{ secrets.gh_token }} - name: Download Linux GUI artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d05e88..e6fb149 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: is_release: true artifact_retention_days: 1 secrets: - github_token: ${{ secrets.GH_PAT }} + gh_token: ${{ secrets.GH_PAT }} macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }} diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 9f98549..14f651d 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -18,7 +18,7 @@ jobs: is_release: false artifact_retention_days: 7 secrets: - github_token: ${{ secrets.GITHUB_TOKEN }} + gh_token: ${{ secrets.GITHUB_TOKEN }} macos_sign_p12: ${{ secrets.MACOS_SIGN_P12 }} macos_sign_password: ${{ secrets.MACOS_SIGN_PASSWORD }} macos_notary_key: ${{ secrets.MACOS_NOTARY_KEY }}