From 4a1f7bcdf9b83e7ca5ba53ecb421d89351411384 Mon Sep 17 00:00:00 2001 From: Santiago Medina Rolong Date: Tue, 21 Jul 2026 12:26:19 -0700 Subject: [PATCH] build: ship cgo-enabled xurl chat in release binaries Release binaries were built with CGO_ENABLED=0 on a Linux runner, so the npm/Homebrew/GitHub-release binaries always shipped the xurl chat stub. Cut Release now runs GoReleaser on a macOS arm64 runner with three build groups: native cgo builds for darwin/arm64 and darwin/amd64 (ad-hoc signed), a zig-cross-compiled musl-static cgo build for linux/amd64 (runs on both glibc and musl distros), and CGO_ENABLED=0 stubs for windows/* and linux/{arm64,386} where chat-xdk has no library. Asset names, the Homebrew cask, the npm launcher, and the release.yml dispatch dedup are unchanged. CI gains a release-build job that compiles all release targets and asserts the darwin binary carries the real chat client, plus stub cross-compile checks on the ubuntu job. --- .github/workflows/cut-release.yml | 15 +++++- .github/workflows/go.yml | 43 ++++++++++++++++- .goreleaser.yaml | 78 ++++++++++++++++++++++++------- CHANGELOG.md | 4 ++ README.md | 6 ++- SKILL.md | 2 +- cli/chat_unsupported.go | 4 +- 7 files changed, 128 insertions(+), 24 deletions(-) diff --git a/.github/workflows/cut-release.yml b/.github/workflows/cut-release.yml index b2b9b83..2e148b8 100644 --- a/.github/workflows/cut-release.yml +++ b/.github/workflows/cut-release.yml @@ -7,6 +7,11 @@ # 4. Commit CHANGELOG on main, create annotated tag, push both # 5. Publish via GoReleaser (GitHub release + Homebrew) and npm # +# The job runs on a macOS arm64 runner so GoReleaser can produce cgo builds +# of `xurl chat` for every platform chat-xdk supports: darwin targets build +# natively with Apple clang, and linux/amd64 cross-compiles with `zig cc` +# against chat-xdk's musl static library (see .goreleaser.yaml). +# # No pull request is opened. GITHUB_TOKEN cannot create PRs unless the repo # setting "Allow GitHub Actions to create and approve pull requests" is on, # and the PR path is unnecessary for a release cut. @@ -70,7 +75,9 @@ permissions: jobs: cut: name: Cut ${{ inputs.version || inputs.bump }} - runs-on: ubuntu-latest + # macOS arm64: darwin cgo builds need Apple clang, and zig cross-compiles + # the linux/amd64 cgo build. Stub platforms cross-compile from anywhere. + runs-on: macos-latest steps: - name: Checkout main uses: actions/checkout@v4 @@ -86,6 +93,12 @@ jobs: with: go-version: "1.24" + # zig is the C cross-compiler for the linux/amd64 cgo build. + - name: Set up Zig + uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - name: Resolve version and promote changelog id: meta run: | diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b954c04..551a0e8 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,5 +24,46 @@ jobs: - name: Run tests run: go test -v ./... + # The chat stub (cli/chat_unsupported.go) must keep compiling for the + # platforms that ship without the chat client. + - name: Build chat-stub targets + run: | + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./... + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build ./... + - name: Run lint - run: gofmt -l . \ No newline at end of file + run: gofmt -l . + + # Compile every release target exactly as the Cut Release workflow does + # (macOS runner, cgo darwin builds, zig-cross-compiled static linux/amd64, + # CGO_ENABLED=0 stubs) so a broken release build is caught before a cut. + release-build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Set up Zig + uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + + - name: GoReleaser build (snapshot) + uses: goreleaser/goreleaser-action@v6 + with: + version: "~> v2" + args: build --snapshot --clean + + - name: Smoke-test the darwin binary + run: | + BIN=$(ls dist/darwin-cgo_darwin_arm64*/xurl) + "$BIN" version + # The real chat client's help must load, not the stub message. + if "$BIN" chat 2>&1 | grep -q "not available in this build"; then + echo "::error::darwin/arm64 release binary shipped the chat stub" + exit 1 + fi diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b350435..1454ee2 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,8 +1,4 @@ -# This is an example .goreleaser.yml file with some sensible defaults. -# Make sure to check the documentation at https://goreleaser.com - -# The lines below are called `modelines`. See `:help modeline` -# Feel free to remove those if you don't want/need to use them. +# GoReleaser config. Docs: https://goreleaser.com # yaml-language-server: $schema=https://goreleaser.com/static/schema.json # vim: set ts=2 sw=2 tw=0 fo=cnqoj @@ -10,23 +6,71 @@ version: 2 before: hooks: - # You may remove this if you don't use go modules. - go mod tidy - # you may remove this if you don't need go generate - go generate ./... +# The release runs on a macOS arm64 runner (see cut-release.yml) so the +# `xurl chat` XChat client — which requires cgo to link the chat-xdk static +# libraries — can be compiled into every platform chat-xdk supports: +# +# darwin/arm64 native cgo build +# darwin/amd64 cgo cross-build with Apple clang (-arch x86_64) +# linux/amd64 cgo cross-build with `zig cc` against chat-xdk's musl +# static lib, fully statically linked so the one binary +# runs on both glibc and musl distros +# +# Platforms chat-xdk has no library for (windows/*, linux/arm64, linux/386) +# are built with CGO_ENABLED=0 and ship the graceful `xurl chat` stub. builds: - # Release binaries are built with CGO disabled so a single Linux runner can - # cross-compile every platform. The `xurl chat` XChat client requires cgo - # (the chat-xdk crypto binding links prebuilt static libraries), so release - # binaries ship a stub for it; chat needs a source build with CGO_ENABLED=1 - # on macOS (amd64/arm64) or Linux (amd64). See README "Encrypted chat". - - env: + - id: darwin-cgo + targets: + - darwin_arm64 + - darwin_amd64 + env: + - CGO_ENABLED=1 + ldflags: + - -s -w + - -X github.com/xdevplatform/xurl/version.Version={{.Version}} + - -X github.com/xdevplatform/xurl/version.Commit={{.Commit}} + - -X github.com/xdevplatform/xurl/version.BuildDate={{.Date}} + hooks: + post: + # Ad-hoc sign so both darwin binaries carry a valid signature. The + # native arm64 build is already linker-signed, but the cross-built + # amd64 binary comes out unsigned. No Apple credentials involved; + # Gatekeeper quarantine is handled per install channel (the Homebrew + # cask strips it, and npm's programmatic download never sets it). + - cmd: codesign -s - --force {{ .Path }} + + - id: linux-cgo + targets: + - linux_amd64 + env: + - CGO_ENABLED=1 + - CC=zig cc -target x86_64-linux-musl + # The musl tag selects chat-xdk's linux_amd64_musl static library. + flags: + - -tags=musl + ldflags: + - -s -w + - -X github.com/xdevplatform/xurl/version.Version={{.Version}} + - -X github.com/xdevplatform/xurl/version.Commit={{.Commit}} + - -X github.com/xdevplatform/xurl/version.BuildDate={{.Date}} + - -linkmode external + # Static link against musl so the binary has no runtime libc + # dependency; -lunwind provides the unwinder symbols the Rust-built + # chat-xdk library needs (zig bundles LLVM's libunwind). + - -extldflags "-static -lunwind" + + - id: stub + targets: + - linux_arm64 + - linux_386 + - windows_amd64 + - windows_arm64 + - windows_386 + env: - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin ldflags: - -s -w - -X github.com/xdevplatform/xurl/version.Version={{.Version}} diff --git a/CHANGELOG.md b/CHANGELOG.md index b5652b7..51a5894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All user-visible bugs and enhancements should be recorded here. ## Unreleased +### Changed + +- Release binaries now include the full `xurl chat` XChat client on macOS (arm64/amd64) and Linux (amd64) — installing via Homebrew, npm, or a GitHub release tarball no longer prints the "not available in this build" stub on those platforms. The Linux amd64 binary is statically linked against musl, so it runs on both glibc and musl (e.g. Alpine) distros. Platforms without a chat-xdk library (Windows, Linux arm64/i386) still ship the graceful stub. + ## v1.3.0 - 2026-07-21 ### Added diff --git a/README.md b/README.md index 87a10bd..d5ad5b6 100644 --- a/README.md +++ b/README.md @@ -425,8 +425,10 @@ Notes: writes to Juicebox or the key-registration endpoint. - Private keys live in `~/.xurl/keys.yml` (mode 600). Losing it is safe as long as the Juicebox backup (made by the original client) still exists. -- `chat` requires a cgo build on macOS (Intel/Apple Silicon) or Linux (amd64). Prebuilt - release binaries ship a stub; build from source to enable it: +- `chat` is supported on macOS (Intel/Apple Silicon) and Linux (amd64), and the release + binaries (Homebrew, npm, GitHub releases) include it on those platforms. On other + platforms (Windows, Linux arm64/i386) `xurl chat` prints a stub explaining it is + unavailable. Source builds on supported platforms need cgo: `CGO_ENABLED=1 go install github.com/xdevplatform/xurl@latest`. ## Token Storage diff --git a/SKILL.md b/SKILL.md index 482ec86..73482af 100644 --- a/SKILL.md +++ b/SKILL.md @@ -254,7 +254,7 @@ xurl dms -n 25 ### Encrypted Chat (XChat) -`xurl chat` is an end-to-end encrypted XChat client: encryption and decryption happen locally via the chat-xdk crypto library, so the server only sees ciphertext. Requires OAuth2 user auth with `dm.read` + `dm.write` scopes, and is available on macOS (Intel/Apple Silicon) and Linux amd64 when built with cgo (prebuilt release binaries ship a stub that says so). +`xurl chat` is an end-to-end encrypted XChat client: encryption and decryption happen locally via the chat-xdk crypto library, so the server only sees ciphertext. Requires OAuth2 user auth with `dm.read` + `dm.write` scopes, and is available on macOS (Intel/Apple Silicon) and Linux amd64 — release binaries include it there; elsewhere (Windows, Linux arm64/i386) `xurl chat` prints a stub that says so. **Keys come from another XChat client** — xurl never generates or registers encryption keys. The account must already have keys (e.g. from the X app); bring them to this machine once with `restore` (Juicebox PIN recovery) or `import` (an exported key blob). Private keys are stored in `~/.xurl/keys.yml` (mode 600) — never read that file into LLM context. diff --git a/cli/chat_unsupported.go b/cli/chat_unsupported.go index 2f7dbb5..4af1b32 100644 --- a/cli/chat_unsupported.go +++ b/cli/chat_unsupported.go @@ -22,8 +22,8 @@ func CreateChatCommand(a *auth.Auth) *cobra.Command { Use: "chat", Short: "Send and read end-to-end encrypted XChat messages (unavailable in this build)", Run: func(cmd *cobra.Command, args []string) { - fmt.Fprintln(os.Stderr, "xurl chat is not available in this build: the XChat crypto library requires cgo and supports macOS (amd64/arm64) and Linux (amd64).") - fmt.Fprintln(os.Stderr, "On a supported platform, build from source with CGO enabled: CGO_ENABLED=1 go install github.com/xdevplatform/xurl@latest") + fmt.Fprintln(os.Stderr, "xurl chat is not available in this build: the XChat crypto library supports macOS (amd64/arm64) and Linux (amd64) only.") + fmt.Fprintln(os.Stderr, "Release binaries for those platforms include chat; on them a source build also needs cgo: CGO_ENABLED=1 go install github.com/xdevplatform/xurl@latest") os.Exit(1) }, }