Add native macOS CI build + from-source build guide#1553
Open
ofcRS wants to merge 1 commit into
Open
Conversation
Adds scripts/macos-build.sh (native per-arch static build mirroring scripts/cross-build.sh: same pinned dep versions and libwebsockets flags, adapted for macOS), a macos matrix job in backend.yml (macos-14 arm64 + macos-13 x86_64), and docs/macos-build.md linked from the README. No release.yml change needed -- artifacts use the existing ttyd.<target> convention, so the publish step picks them up unchanged. Refs tsl0922#1011. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds native macOS support to the build/release pipeline:
scripts/macos-build.shthat builds a self-containedttydbinary natively on macOS, mirroringscripts/cross-build.sh(same pinned dependency versions, same libwebsockets feature flags).macosjob in.github/workflows/backend.ymlthat runs that script on a two-arch matrix (macos-14/ Apple Silicon,macos-13/ Intel) and uploadsttyd.aarch64-apple-darwinandttyd.x86_64-apple-darwinartifacts.docs/macos-build.md, linked fromREADME.md.No change to
.github/workflows/release.ymlis required — the new artifacts use the existingttyd.<target>naming convention, so the existingpublishstep picks them up unchanged. (See "Releases" below.)Why
There is currently no macOS runner in CI and no released macOS binaries (ref #1011). This adds an opt-in, fully native path so macOS users can either build from source with one script or grab a per-arch binary from a release, while keeping you in control (releases are still created as drafts for manual review).
This follows the precedent already set by the native
msvcjob — a per-platform native build alongside thecrossjob — rather than forcing macOS through the Linux-onlycross-build.sh(which uses musl toolchains,-static, GNUsed -i, andlinux-*OpenSSL targets that don't apply on macOS).scripts/macos-build.shNative per-arch build (no cross toolchain). Pins the same dependency versions as
cross-build.sh:All four of these are linked statically. Unlike
cross-build.sh(which builds zlib for musl), the macOS build uses the systemlibz, which stays dynamically linked along withlibSystemandlibutil— normal and unavoidable on macOS (no staticlibSystem).How it mirrors
cross-build.shSame recipe, only the platform-specific substitutions macOS forces:
cross-build.sh)macos-build.sh)${TARGET}-gccCHOST/ CMake cross fileCMAKE_OSX_ARCHITECTURES+ OpenSSLdarwin64-*-cclinux-*viamap_openssl_targetdarwin64-arm64-cc/darwin64-x86_64-cc-static -no-pie -Wl,-s -Wl,--gc-sections-Wl,-dead_strip -framework CoreFoundation -framework CoreServices -framework IOKit(no-static)-Wl,-sat linkstrip -Spost-linksed -i 's/ websockets_shared//g'(GNU)sed -i '' 's/ websockets_shared//g'(BSD sed needs the empty backup arg)nprocsysctl -n hw.ncpuThe libwebsockets flag set is used as-is (
LWS_WITH_SSL=ON,LWS_WITH_LIBUV=ON,LWS_WITH_SHARED=OFF,LWS_STATIC_PIC=ON, the full feature-OFF list, etc.). The Linux-only OpenSSL-fPIC/-latomicCFLAGSare dropped (-latomicdoesn't exist on macOS; PIC is the default).CI: new
macosjob inbackend.ymlMirrors the
crossjob's shape:strategy.fail-fast: false, the sameactions/checkout@v6andactions/upload-artifact@v7pins, and the samettyd.<target>artifact-naming convention.cmakecomes from the runner's preinstalled Homebrew and is used only as a build-time tool — never linked into the binary (pkg-configisn't needed; ttyd discovers its deps via CMake'sfind_package/find_library). Each matrix leg has an explicitnameso the checks render asmacos (arm64)/macos (x86_64).Releases (no
release.ymlchange)release.yml'spublishjob already downloads every artifact, derives each released asset's filename from the artifact name (for file in ttyd.*/*; ... awk -F/ '{print $1}'), runssha256sum ttyd.*, and attaches everything viancipollo/release-action@v1(draft: true). Because the new job uploads artifacts namedttyd.aarch64-apple-darwin/ttyd.x86_64-apple-darwin(each containing a singlettydbinary, exactly like thecrossjob), they match thettyd.*/*glob, get renamed to those exact asset names, are included inSHA256SUMS, and land on the draft release for review — with norelease.ymledit. I have not run the full release flow end-to-end; it can be exercised by pushing a tag on a fork.Verified local result
A from-source
ttyd1.7.7 build was produced on macOS arm64 with this recipe.otool -Lshowed only:OpenSSL, libwebsockets, libuv and json-c do not appear — they are statically linked.
libz/libSystem/libutilresolving dynamically is expected macOS behavior.Caveats I want to be upfront about: the x86_64 (
macos-13) leg has not been run end-to-end locally — I'm relying on this PR's CI to confirm it. Andmacos-13is on GitHub's runner-retirement path; happy to drop the Intel leg or switch runners if you prefer. The arm64 (macos-14) leg is the must-have.Open question: signing & notarization
These macOS binaries are not codesigned or notarized, and this PR does not attempt to make them so. A binary downloaded from GitHub Releases without a Developer ID signature + notarization ticket is quarantined by macOS Gatekeeper; users will see "ttyd cannot be opened because the developer cannot be verified" / "is damaged and can't be opened" and must clear the quarantine attribute:
(or right-click → Open once in Finder). Users who build from source locally are unaffected.
Codesigning + notarization would remove this friction, but it requires a paid Apple Developer ID and signing credentials stored as CI secrets — something only you can provide; a contributor cannot. I've deliberately left this as a decision for you rather than implying the artifacts are signed. The docs are explicit about it. If you'd like, I can follow up with a signing/notarization step that reads from secrets you control.
Testing / how to verify
Local:
CI: this PR touches
scripts/*and.github/workflows/backend.yml, sobackendruns on the PR. Confirm bothmacos (arm64)andmacos (x86_64)legs are green and upload theirttyd.*-apple-darwinartifact.fail-fast: falsekeeps a flaky macOS leg from taking down the Linux/Windows builds.A standalone, Homebrew-free version of the build script (for users who just want a binary without cloning the repo) is also available as a gist: https://gist.github.com/ofcRS/4efc3b945765e7b4b1ff432c439dd460
🤖 Generated with Claude Code