fix(ci): migrate to pure-Go SQLite and GoReleaser v2

Fixes nightly build failures caused by:

GoReleaser v2 requiring version 2 config syntax
Zig cross-compilation failing for macOS CGO targets
SQLite Driver Migration:

Replace gorm.io/driver/sqlite with github.com/glebarez/sqlite (pure-Go)
Execute PRAGMA statements via SQL instead of DSN parameters
All platforms now build with CGO_ENABLED=0
GoReleaser v2 Migration:

Update version: 1 → version: 2
snapshot.name_template → version_template
archives.format → formats (array syntax)
archives.builds → ids
nfpms.builds → ids
Remove Zig cross-compilation environment
Also fixes Docker Compose E2E image reference:

Use CHARON_E2E_IMAGE_TAG instead of bare digest
Add fallback default for local development
All database tests pass with the pure-Go SQLite driver.
This commit is contained in:
Jeremy
2026-01-30 13:57:01 +00:00
parent 14859df9a6
commit 9f94fdeade
8 changed files with 543 additions and 399 deletions
+471 -362
View File
@@ -1,448 +1,557 @@
# Docker Compose CI Failure Remediation Plan
# GoReleaser v2 Migration & Nightly Build Failure Remediation
**Status**: Active
**Created**: 2026-01-30
**Priority**: CRITICAL (Blocking CI)
**Priority**: CRITICAL (Blocking Nightly Builds)
---
## Executive Summary
The E2E test workflow (`e2e-tests.yml`) is failing when attempting to start containers via `docker-compose.playwright-ci.yml`. The root cause is an incorrect Docker image reference format in the compose file that attempts to use a bare SHA256 digest instead of a fully-qualified image reference with registry and repository.
The nightly build workflow (`nightly-build.yml`) is failing with multiple issues:
**Error Message**:
1. **GoReleaser v2 Compatibility**: Config uses deprecated v1 syntax
2. **Zig Cross-Compilation**: Incorrect macOS target triple format
3. **🆕 CGO/SQLite Dependency**: Disabling CGO for darwin breaks SQLite (`mattn/go-sqlite3` requires CGO)
**Error Messages**:
```
charon-app Error pull access denied for sha256, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
only version: 2 configuration files are supported, yours is version: 1, please update your configuration
```
**Root Cause**: The compose file's `image:` directive evaluates to a bare SHA256 digest (e.g., `sha256:057a9998...`) instead of a properly formatted image reference like `ghcr.io/wikid82/charon@sha256:057a9998...`.
**Deprecation Warnings**:
- `snapshot.name_template` is deprecated
- `archives.format` is deprecated
- `archives.builds` is deprecated
- `nfpms.builds` is deprecated
**Build Error** (Zig):
```
error: unable to find or provide libc for target 'x86_64-macos.11.7.1...13.3-gnu'
info: zig can provide libc for related target x86_64-macos.11-none
```
---
## Issue 1: Nightly Build - GoReleaser macOS Cross-Compile Failure
## 🔴 Critical Dependency: SQLite CGO Issue
### Problem Statement
The current SQLite driver (`gorm.io/driver/sqlite`) depends on `mattn/go-sqlite3`, which is a CGO-based library. This means:
- **CGO_ENABLED=0** will cause build failures when SQLite is used
- **Cross-compilation** for darwin from Linux is blocked by CGO complexity
- The proposed fix of disabling CGO for darwin builds **will break the application**
### Solution: Migrate to Pure-Go SQLite
**Recommended Migration Path:**
| Current | New | Notes |
|---------|-----|-------|
| `gorm.io/driver/sqlite` | `github.com/glebarez/sqlite` | GORM-compatible pure-Go driver |
| `mattn/go-sqlite3` (indirect) | `modernc.org/sqlite` (indirect) | Pure-Go SQLite implementation |
**Benefits:**
1. ✅ No CGO required for any platform
2. ✅ Simplified cross-compilation (no Zig needed for SQLite)
3. ✅ Smaller binary size
4. ✅ Faster build times
5. ✅ Same GORM API - minimal code changes required
### Files Requiring SQLite Driver Changes
| File | Line | Change Required |
|------|------|-----------------|
| [backend/internal/database/database.go](../../backend/internal/database/database.go#L10) | 10 | `gorm.io/driver/sqlite``github.com/glebarez/sqlite` |
| [backend/internal/testutil/db_test.go](../../backend/internal/testutil/db_test.go#L6) | 6 | `gorm.io/driver/sqlite``github.com/glebarez/sqlite` |
| [backend/cmd/seed/main.go](../../backend/cmd/seed/main.go#L13) | 13 | `gorm.io/driver/sqlite``github.com/glebarez/sqlite` |
| [backend/go.mod](../../backend/go.mod#L19) | 19 | Replace `gorm.io/driver/sqlite` with `github.com/glebarez/sqlite` |
---
## Issue 1: GoReleaser v1 → v2 Migration (CRITICAL)
### Problem Statement
GoReleaser v2 (currently v2.13.3) no longer supports `version: 1` configuration files. The nightly workflow uses GoReleaser `~> v2` which requires v2 config syntax.
### Root Cause Analysis
Current `.goreleaser.yaml` uses deprecated v1 syntax:
```yaml
version: 1 # ❌ v2 requires "version: 2"
```
Multiple deprecated fields need updating:
| Deprecated Field | v2 Replacement |
|-----------------|----------------|
| `snapshot.name_template` | `snapshot.version_template` |
| `archives.format` | `archives.formats` (array) |
| `archives.builds` | `archives.ids` |
| `nfpms.builds` | `nfpms.ids` |
### GoReleaser Deprecation Reference
From [goreleaser.com/deprecations](https://goreleaser.com/deprecations):
1. **`snapshot.name_template`** → `snapshot.version_template`
- Changed in v2.0.0
- The template generates a version string, not a "name"
2. **`archives.format`** → `archives.formats`
- Changed to array to support multiple formats per archive config
- Must be `formats: [tar.gz]` not `format: tar.gz`
3. **`archives.builds`** → `archives.ids`
- Renamed for clarity: it filters by build `id`, not "builds"
4. **`nfpms.builds`** → `nfpms.ids`
- Same rationale as archives
### Required Changes
```diff
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -1,4 +1,4 @@
-version: 1
+version: 2
project_name: charon
@@ -62,10 +62,10 @@
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
archives:
- - format: tar.gz
+ - formats: [tar.gz]
id: nix
- builds:
+ ids:
- linux
- darwin
name_template: >-
@@ -76,9 +76,9 @@
- LICENSE
- README.md
- - format: zip
+ - formats: [zip]
id: windows
- builds:
+ ids:
- windows
name_template: >-
{{ .ProjectName }}_
@@ -90,7 +90,7 @@
nfpms:
- id: packages
- builds:
+ ids:
- linux
package_name: charon
vendor: Charon
@@ -116,7 +116,7 @@
name_template: 'checksums.txt'
snapshot:
- name_template: "{{ .Tag }}-next"
+ version_template: "{{ .Tag }}-next"
changelog:
sort: asc
```
---
## Issue 2: Zig Cross-Compilation for macOS
### Problem Statement
The nightly build fails during GoReleaser release step when cross-compiling for macOS (darwin) using Zig:
```text
release failed after 4m19s
error=
build failed: exit status 1: go: downloading github.com/gin-gonic/gin v1.11.0
info: zig can provide libc for related target x86_64-macos.11-none
target=darwin_amd64_v1
error: unable to find or provide libc for target 'x86_64-macos.11.7.1...13.3-gnu'
info: zig can provide libc for related target x86_64-macos.11-none
```
### Root Cause Analysis
The `.goreleaser.yaml` darwin build uses incorrect Zig target specification:
The `.goreleaser.yaml` darwin build uses **`-macos-none`** which is correct, but examining the actual file shows **`-macos-none`** is already in place. The error message suggests something is injecting version numbers.
**Current (WRONG):**
**Wait** - Re-reading the current config, I see it actually says `-macos-none` already. Let me check if there's a different issue.
Actually, looking at the error more carefully:
```
target 'x86_64-macos.11.7.1...13.3-gnu'
```
This suggests the **Go runtime/cgo is detecting the macOS version range** and passing it to Zig incorrectly. The `-gnu` suffix shouldn't be there for macOS.
**Current Configuration**:
```yaml
CC=zig cc -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-gnu
CXX=zig c++ -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-gnu
CC=zig cc -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-none
```
**Issue:** macOS uses its own libc (libSystem), not GNU libc. The `-gnu` suffix is invalid for macOS targets. Zig expects `-macos-none` or `-macos.11-none` for macOS builds.
The current config is correct (`-macos-none`), but CGO may be interfering.
### Affected Files
### ~~Recommended Fix: Disable CGO for Darwin~~
| File | Change Type |
|------|-------------|
| `.goreleaser.yaml` | Fix Zig target for darwin builds |
> **⚠️ UPDATE:** This section is superseded by the SQLite driver migration (see "Critical Dependency: SQLite CGO Issue" above). Simply disabling CGO for darwin **breaks SQLite functionality**.
### Recommended Fix
### ✅ Actual Fix: Migrate to Pure-Go SQLite
Update the darwin build configuration to use the correct Zig target triple:
By migrating from `gorm.io/driver/sqlite` (CGO) to `github.com/glebarez/sqlite` (pure-Go):
**Option A: Use `-macos-none` (Recommended)**
```yaml
- id: darwin
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=1
- CC=zig cc -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-none
- CXX=zig c++ -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-none
```
1. **Zig is no longer required** for any platform
2. **CGO_ENABLED=0** can be used for ALL platforms (linux, darwin, windows)
3. **Cross-compilation is trivial** - standard Go cross-compilation works
4. **Build times are faster** - no C compiler invocation
**Option B: Specify macOS version (for specific SDK compatibility)**
```yaml
- CC=zig cc -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos.11-none
- CXX=zig c++ -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos.11-none
```
**Option C: Remove darwin builds entirely (if macOS support is not required)**
```yaml
# Remove the entire `- id: darwin` build block from .goreleaser.yaml
# Update archives section to remove darwin from the `nix` archive builds
```
### Implementation Details
```diff
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -47,8 +47,8 @@
binary: charon
env:
- CGO_ENABLED=1
- - CC=zig cc -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-gnu
- - CXX=zig c++ -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-gnu
+ - CC=zig cc -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-none
+ - CXX=zig c++ -target {{ if eq .Arch "amd64" }}x86_64{{ else }}aarch64{{ end }}-macos-none
goos:
- darwin
goarch:
```
### Verification
```bash
# Local test (requires Zig installed)
cd backend
CGO_ENABLED=1 CC="zig cc -target x86_64-macos-none" go build -o charon-darwin ./cmd/api
# Nightly workflow test
gh workflow run nightly-build.yml --ref development -f reason="Test darwin build fix"
```
This completely eliminates Issue 2 as a side effect of fixing the SQLite dependency issue.
---
## Issue 2: Playwright E2E - Admin API Socket Hang Up
## Complete Updated `.goreleaser.yaml`
### Problem Statement
Playwright test `zzz-admin-whitelist-blocking.spec.ts:126` fails with:
```text
Error: apiRequestContext.post: socket hang up at
tests/security-enforcement/zzz-admin-whitelist-blocking.spec.ts:126:21
```
The test POSTs to `http://localhost:2020/emergency/security-reset` but cannot reach the emergency server.
### Root Cause Analysis
The `playwright.yml` workflow starts the Charon container but **does not set** the `CHARON_EMERGENCY_BIND` environment variable:
**Current workflow (`.github/workflows/playwright.yml`):**
```yaml
docker run -d \
--name charon-test \
-p 8080:8080 \
-p 127.0.0.1:2019:2019 \
-p "[::1]:2019:2019" \
-p 127.0.0.1:2020:2020 \
-p "[::1]:2020:2020" \
-e CHARON_ENV="${CHARON_ENV}" \
-e CHARON_DEBUG="${CHARON_DEBUG}" \
-e CHARON_ENCRYPTION_KEY="${CHARON_ENCRYPTION_KEY}" \
-e CHARON_EMERGENCY_TOKEN="${CHARON_EMERGENCY_TOKEN}" \
-e CHARON_EMERGENCY_SERVER_ENABLED="${CHARON_EMERGENCY_SERVER_ENABLED}" \
"${IMAGE_REF}"
```
**Missing:** `CHARON_EMERGENCY_BIND=0.0.0.0:2020`
Without this variable, the emergency server may not bind to the correct address, or may bind to a loopback-only address that isn't accessible via Docker port mapping.
**Comparison with working compose file:**
```yaml
# .docker/compose/docker-compose.playwright-ci.yml
- CHARON_EMERGENCY_BIND=0.0.0.0:2020
- CHARON_EMERGENCY_USERNAME=admin
- CHARON_EMERGENCY_PASSWORD=changeme
```
### Affected Files
| File | Change Type |
|------|-------------|
| `.github/workflows/playwright.yml` | Add missing emergency server env vars |
### Recommended Fix
Add the missing emergency server environment variables to the docker run command:
```diff
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -163,6 +163,10 @@ jobs:
-e CHARON_ENCRYPTION_KEY="${CHARON_ENCRYPTION_KEY}" \
-e CHARON_EMERGENCY_TOKEN="${CHARON_EMERGENCY_TOKEN}" \
-e CHARON_EMERGENCY_SERVER_ENABLED="${CHARON_EMERGENCY_SERVER_ENABLED}" \
+ -e CHARON_EMERGENCY_BIND="0.0.0.0:2020" \
+ -e CHARON_EMERGENCY_USERNAME="admin" \
+ -e CHARON_EMERGENCY_PASSWORD="changeme" \
+ -e CHARON_SECURITY_TESTS_ENABLED="true" \
"${IMAGE_REF}"
```
### Full Updated Step
> **Note:** After migrating to pure-Go SQLite (`github.com/glebarez/sqlite`), Zig cross-compilation is no longer required. All platforms now use `CGO_ENABLED=0` for simpler, faster builds.
```yaml
- name: Start Charon container
if: steps.check-artifact.outputs.artifact_exists == 'true'
run: |
echo "🚀 Starting Charon container..."
version: 2
# Normalize image name (GitHub lowercases repository owner names in GHCR)
IMAGE_NAME=$(echo "${{ github.repository_owner }}/charon" | tr '[:upper:]' '[:lower:]')
if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then
IMAGE_REF="ghcr.io/${IMAGE_NAME}:${{ steps.sanitize.outputs.branch }}"
else
IMAGE_REF="ghcr.io/${IMAGE_NAME}:pr-${{ steps.pr-info.outputs.pr_number }}"
fi
project_name: charon
echo "📦 Starting container with image: ${IMAGE_REF}"
docker run -d \
--name charon-test \
-p 8080:8080 \
-p 127.0.0.1:2019:2019 \
-p "[::1]:2019:2019" \
-p 127.0.0.1:2020:2020 \
-p "[::1]:2020:2020" \
-e CHARON_ENV="${CHARON_ENV}" \
-e CHARON_DEBUG="${CHARON_DEBUG}" \
-e CHARON_ENCRYPTION_KEY="${CHARON_ENCRYPTION_KEY}" \
-e CHARON_EMERGENCY_TOKEN="${CHARON_EMERGENCY_TOKEN}" \
-e CHARON_EMERGENCY_SERVER_ENABLED="${CHARON_EMERGENCY_SERVER_ENABLED}" \
-e CHARON_EMERGENCY_BIND="0.0.0.0:2020" \
-e CHARON_EMERGENCY_USERNAME="admin" \
-e CHARON_EMERGENCY_PASSWORD="changeme" \
-e CHARON_SECURITY_TESTS_ENABLED="true" \
"${IMAGE_REF}"
builds:
- id: linux
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/Wikid82/charon/backend/internal/version.Version={{.Version}}
- -X github.com/Wikid82/charon/backend/internal/version.GitCommit={{.Commit}}
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
echo "✅ Container started"
```
- id: windows
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=0
goos:
- windows
goarch:
- amd64
ldflags:
- -s -w
- -X github.com/Wikid82/charon/backend/internal/version.Version={{.Version}}
- -X github.com/Wikid82/charon/backend/internal/version.GitCommit={{.Commit}}
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
### Verification
- id: darwin
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=0
goos:
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/Wikid82/charon/backend/internal/version.Version={{.Version}}
- -X github.com/Wikid82/charon/backend/internal/version.GitCommit={{.Commit}}
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
```bash
# After fix, verify emergency server is listening
docker exec charon-test curl -sf http://localhost:2020/health || echo "Failed"
archives:
- formats: [tar.gz]
id: nix
ids:
- linux
- darwin
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- .Arch }}
files:
- LICENSE
- README.md
# Test emergency reset endpoint
curl -X POST http://localhost:2020/emergency/security-reset \
-H "Authorization: Basic $(echo -n 'admin:changeme' | base64)" \
-H "X-Emergency-Token: $CHARON_EMERGENCY_TOKEN"
```
- formats: [zip]
id: windows
ids:
- windows
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- .Arch }}
files:
- LICENSE
- README.md
---
nfpms:
- id: packages
ids:
- linux
package_name: charon
vendor: Charon
homepage: https://github.com/Wikid82/charon
maintainer: Wikid82
description: "Charon - A powerful reverse proxy manager"
license: MIT
formats:
- deb
- rpm
contents:
- src: ./backend/data/
dst: /var/lib/charon/data/
type: dir
- src: ./frontend/dist/
dst: /usr/share/charon/frontend/
type: dir
dependencies:
- libc6
- ca-certificates
## Issue 3: Trivy Scan - Invalid Image Reference Format
checksum:
name_template: 'checksums.txt'
### Problem Statement
snapshot:
version_template: "{{ .Tag }}-next"
Trivy scan fails with "invalid image reference format" when:
1. PR number is missing (manual dispatch without PR number)
2. Feature branch names contain `/` characters (e.g., `feature/new-thing`)
3. `is_push` and `pr_number` are both empty/false
Resulting in invalid Docker tags like:
- `ghcr.io/owner/charon:pr-` (empty PR number)
- `ghcr.io/owner/charon:` (no tag at all)
### Root Cause Analysis
**Location:** `.github/workflows/playwright.yml` - "Start Charon container" step
```bash
if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then
IMAGE_REF="ghcr.io/${IMAGE_NAME}:${{ steps.sanitize.outputs.branch }}"
else
IMAGE_REF="ghcr.io/${IMAGE_NAME}:pr-${{ steps.pr-info.outputs.pr_number }}"
fi
```
**Problem:** When `is_push != "true"` AND `pr_number` is empty, this creates:
```
IMAGE_REF="ghcr.io/owner/charon:pr-"
```
This is an invalid Docker reference.
### Affected Files
| File | Change Type |
|------|-------------|
| `.github/workflows/playwright.yml` | Add validation for IMAGE_REF |
| `.github/workflows/docker-build.yml` | Add validation guards (CVE verification step) |
### Recommended Fix
Add defensive validation to fail fast with a clear error message:
```diff
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
# Normalize image name (GitHub lowercases repository owner names in GHCR)
IMAGE_NAME=$(echo "${{ github.repository_owner }}/charon" | tr '[:upper:]' '[:lower:]')
if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then
IMAGE_REF="ghcr.io/${IMAGE_NAME}:${{ steps.sanitize.outputs.branch }}"
- else
+ elif [[ -n "${{ steps.pr-info.outputs.pr_number }}" ]]; then
IMAGE_REF="ghcr.io/${IMAGE_NAME}:pr-${{ steps.pr-info.outputs.pr_number }}"
+ else
+ echo "❌ ERROR: Cannot determine image reference"
+ echo " - is_push: ${{ steps.pr-info.outputs.is_push }}"
+ echo " - pr_number: ${{ steps.pr-info.outputs.pr_number }}"
+ echo " - branch: ${{ steps.sanitize.outputs.branch }}"
+ echo ""
+ echo "This can happen when:"
+ echo " 1. workflow_dispatch without pr_number input"
+ echo " 2. workflow_run triggered by non-PR, non-push event"
+ exit 1
fi
+ # Validate the image reference format
+ if [[ ! "${IMAGE_REF}" =~ ^ghcr\.io/[a-z0-9_-]+/[a-z0-9_-]+:[a-zA-Z0-9._-]+$ ]]; then
+ echo "❌ ERROR: Invalid image reference format: ${IMAGE_REF}"
+ exit 1
+ fi
+
echo "📦 Starting container with image: ${IMAGE_REF}"
```
### Additional Fix for docker-build.yml
The same issue can occur in `docker-build.yml` at the CVE verification step:
```yaml
# Line ~174 in docker-build.yml
if [ "${{ github.event_name }}" = "pull_request" ]; then
IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}"
```
**Fix:**
```diff
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
# Determine the image reference based on event type
if [ "${{ github.event_name }}" = "pull_request" ]; then
- IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}"
+ PR_NUM="${{ github.event.pull_request.number }}"
+ if [ -z "${PR_NUM}" ]; then
+ echo "❌ ERROR: Pull request number is empty"
+ exit 1
+ fi
+ IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${PR_NUM}"
echo "Using PR image: $IMAGE_REF"
else
IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}"
+ if [ -z "${{ steps.build-and-push.outputs.digest }}" ]; then
+ echo "❌ ERROR: Build digest is empty"
+ exit 1
+ fi
echo "Using digest: $IMAGE_REF"
fi
```
### Verification
```bash
# Test with empty PR number (should fail fast with clear error)
gh workflow run playwright.yml --ref development
# Check IMAGE_REF construction in logs
gh run view --log | grep "IMAGE_REF"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
```
---
## Implementation Plan
### Phase 1: Immediate Fixes (Single PR)
### Phase 0: SQLite Driver Migration (PREREQUISITE)
**Objective:** Fix all three CI failures in a single PR for immediate resolution.
**Objective:** Migrate from CGO-dependent SQLite to pure-Go implementation.
**Files to Modify:**
| File | Changes |
|------|---------|
| `.goreleaser.yaml` | Change `-macos-gnu` to `-macos-none` for darwin builds |
| `.github/workflows/playwright.yml` | Add missing emergency server env vars; Add IMAGE_REF validation |
| `.github/workflows/docker-build.yml` | Add IMAGE_REF validation guards |
| File | Change | Reason |
|------|--------|--------|
| `backend/go.mod` | Replace `gorm.io/driver/sqlite` with `github.com/glebarez/sqlite` | Pure-Go SQLite driver |
| `backend/internal/database/database.go` | Update import statement | New driver package |
| `backend/internal/testutil/db_test.go` | Update import statement | New driver package |
| `backend/cmd/seed/main.go` | Update import statement | New driver package |
### Phase 2: Verification
**Steps:**
1. Push changes to a feature branch
2. Open PR to trigger docker-build.yml
3. Verify Trivy scan passes with valid IMAGE_REF
4. Verify Playwright workflow if triggered
5. Manually trigger nightly-build.yml with `--ref` pointing to feature branch
6. Verify darwin build succeeds
```bash
# 1. Update go.mod - replace CGO driver with pure-Go driver
cd backend
go get github.com/glebarez/sqlite
go mod edit -droprequire gorm.io/driver/sqlite
### Phase 3: Cleanup (Optional)
# 2. Update import statements in Go files
# (Manual step - update imports in 3 files listed above)
1. Add validation logic to a shared script (`scripts/validate-image-ref.sh`)
2. Add integration tests for emergency server connectivity
3. Document Zig target requirements for future contributors
# 3. Tidy dependencies
go mod tidy
# 4. Verify build works without CGO
CGO_ENABLED=0 go build ./cmd/api
CGO_ENABLED=0 go build ./cmd/seed
# 5. Run tests to verify SQLite functionality
CGO_ENABLED=0 go test ./internal/database/... -v
CGO_ENABLED=0 go test ./internal/testutil/... -v
```
**Validation:**
-`CGO_ENABLED=0 go build ./backend/cmd/api` succeeds
-`CGO_ENABLED=0 go build ./backend/cmd/seed` succeeds
- ✅ All database tests pass with CGO disabled
-`go.mod` no longer references `gorm.io/driver/sqlite` or `mattn/go-sqlite3`
---
### Phase 1: Update GoReleaser Config
**Files to Modify:**
| File | Change | Reason |
|------|--------|--------|
| `.goreleaser.yaml` | Update to version 2 syntax | Required for GoReleaser ~> v2 |
| `.goreleaser.yaml` | Remove Zig cross-compilation | No longer needed with pure-Go SQLite |
| `.goreleaser.yaml` | Set `CGO_ENABLED=0` for ALL platforms | Consistent pure-Go builds |
**Simplified Build Configuration (No Zig Required):**
```yaml
builds:
- id: linux
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/Wikid82/charon/backend/internal/version.Version={{.Version}}
- -X github.com/Wikid82/charon/backend/internal/version.GitCommit={{.Commit}}
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
- id: windows
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=0
goos:
- windows
goarch:
- amd64
ldflags:
- -s -w
- -X github.com/Wikid82/charon/backend/internal/version.Version={{.Version}}
- -X github.com/Wikid82/charon/backend/internal/version.GitCommit={{.Commit}}
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
- id: darwin
dir: backend
main: ./cmd/api
binary: charon
env:
- CGO_ENABLED=0
goos:
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/Wikid82/charon/backend/internal/version.Version={{.Version}}
- -X github.com/Wikid82/charon/backend/internal/version.GitCommit={{.Commit}}
- -X github.com/Wikid82/charon/backend/internal/version.BuildTime={{.Date}}
```
---
### Phase 2: Verification Steps
```bash
# 1. Verify SQLite migration (Phase 0 complete)
cd backend
CGO_ENABLED=0 go build ./cmd/api
CGO_ENABLED=0 go test ./... -count=1
# 2. Validate the GoReleaser config locally
goreleaser check
# 3. Test snapshot build locally (no Zig required!)
goreleaser release --snapshot --skip=publish --clean
# 4. Trigger nightly workflow manually
gh workflow run nightly-build.yml -f reason="Test GoReleaser v2 migration with pure-Go SQLite"
# 5. Monitor workflow execution
gh run watch
```
---
### Phase 3: Rollback Plan
If the fix fails:
**SQLite Rollback:**
1. Revert `go.mod` to use `gorm.io/driver/sqlite`
2. Revert import statement changes
3. Re-enable CGO in GoReleaser config
**GoReleaser Rollback:**
1. Revert `.goreleaser.yaml` changes
2. Pin GoReleaser to v1.x in workflows:
```yaml
version: '1.26.2' # Last v1 release
```
---
## Requirements (EARS Notation)
1. WHEN GoReleaser builds darwin targets, THE SYSTEM SHALL use `-macos-none` Zig target (not `-macos-gnu`).
2. WHEN the Playwright workflow starts the Charon container, THE SYSTEM SHALL set `CHARON_EMERGENCY_BIND=0.0.0.0:2020` to ensure the emergency server is reachable.
3. WHEN constructing Docker image references, THE SYSTEM SHALL validate that the tag portion is non-empty before attempting to use it.
4. IF the PR number is empty in a PR-triggered workflow, THEN THE SYSTEM SHALL fail fast with a clear error message explaining the issue.
5. WHEN a feature branch contains `/` characters, THE SYSTEM SHALL sanitize the branch name by replacing `/` with `-` before using it as a Docker tag.
1. WHEN building for any platform, THE SYSTEM SHALL use `CGO_ENABLED=0` (pure-Go builds).
2. WHEN importing the SQLite driver, THE SYSTEM SHALL use `github.com/glebarez/sqlite` (pure-Go driver).
3. WHEN GoReleaser executes, THE SYSTEM SHALL use version 2 configuration syntax.
4. WHEN archiving builds, THE SYSTEM SHALL use `formats` (array) instead of deprecated `format`.
5. WHEN referencing build IDs in archives/nfpms, THE SYSTEM SHALL use `ids` instead of deprecated `builds`.
6. WHEN generating snapshot versions, THE SYSTEM SHALL use `version_template` instead of deprecated `name_template`.
---
## Acceptance Criteria
1. [ ] Nightly build completes successfully with darwin binaries
2. [ ] Playwright E2E tests pass with emergency server accessible on port 2020
3. [ ] Trivy scan passes with valid image reference for all trigger types
4. [ ] Workflow failures produce clear, actionable error messages
5. [ ] No regression in existing CI functionality
**Phase 0 (SQLite Migration):**
- [ ] `backend/go.mod` uses `github.com/glebarez/sqlite` instead of `gorm.io/driver/sqlite`
- [ ] No references to `mattn/go-sqlite3` in `go.mod` or `go.sum`
- [ ] `CGO_ENABLED=0 go build ./backend/cmd/api` succeeds
- [ ] `CGO_ENABLED=0 go build ./backend/cmd/seed` succeeds
- [ ] All database tests pass with `CGO_ENABLED=0`
**Phase 1 (GoReleaser v2):**
- [ ] `goreleaser check` passes without errors or deprecation warnings
- [ ] Nightly build workflow completes successfully
- [ ] Linux amd64/arm64 binaries are produced
- [ ] Windows amd64 binary is produced
- [ ] Darwin amd64/arm64 binaries are produced
- [ ] .deb and .rpm packages are produced for Linux
- [ ] No deprecation warnings in CI logs
- [ ] No Zig-related errors in build logs
---
## Risks & Mitigations
## Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|------|------------|--------|------------|
| Zig target change breaks darwin binaries | Low | High | Test with local Zig build first |
| Emergency server env vars conflict with existing config | Low | Medium | Verify against docker-compose.playwright-ci.yml |
| IMAGE_REF validation too strict | Medium | Low | Use permissive regex, log values before validation |
---
## Handoff Contract
```json
{
"plan": "CI Workflow Failures - Fix Plan",
"status": "Ready for Implementation",
"owner": "DevOps",
"handoffTargets": ["Backend_Dev", "DevOps"],
"files": [
".goreleaser.yaml",
".github/workflows/playwright.yml",
".github/workflows/docker-build.yml"
],
"estimatedEffort": "2-3 hours",
"priority": "HIGH",
"blockedWorkflows": [
"nightly-build.yml",
"playwright.yml",
"docker-build.yml (Trivy scan step)"
]
}
```
| Pure-Go SQLite has different behavior | Low | Medium | Run full test suite; compare query results |
| Pure-Go SQLite performance differs | Low | Low | Run benchmarks; acceptable for typical workloads |
| Other undocumented v2 breaking changes | Low | Medium | Monitor GoReleaser changelog; test locally first |
| Import statement missed in some file | Low | High | Use grep to find all `gorm.io/driver/sqlite` imports |
---
## References
- [docs/actions/nightly-build-failure.md](../actions/nightly-build-failure.md)
- [docs/actions/playwright-e2e-failures.md](../actions/playwright-e2e-failures.md)
- [Zig Cross-Compilation Targets](https://ziglang.org/documentation/master/#Targets)
- [GoReleaser CGO Cross-Compilation](https://goreleaser.com/customization/build/#cross-compiling)
- [glebarez/sqlite - Pure Go SQLite driver for GORM](https://github.com/glebarez/sqlite)
- [modernc.org/sqlite - Pure Go SQLite implementation](https://pkg.go.dev/modernc.org/sqlite)
- [GoReleaser v2 Migration Guide](https://goreleaser.com/deprecations/)
- [GoReleaser Builds Documentation](https://goreleaser.com/customization/build/)
---
# ARCHIVED: Other CI Issues (Separate from GoReleaser)
The following issues are documented separately and may be addressed in future PRs:
1. **Playwright E2E - Emergency Server Connectivity** - See [docs/plans/e2e_remediation_spec.md](e2e_remediation_spec.md)
2. **Trivy Scan - Image Reference Validation** - See [docs/plans/docker_compose_ci_fix.md](docker_compose_ci_fix.md)