diff --git a/.github/scripts/merge-linux-update-manifests.py b/.github/scripts/merge-linux-update-manifests.py index db4971e9247a..77219d74da26 100755 --- a/.github/scripts/merge-linux-update-manifests.py +++ b/.github/scripts/merge-linux-update-manifests.py @@ -23,6 +23,7 @@ # Order matters: the first present target supplies the legacy top-level # path/sha512 that pre-6.x clients fall back to when `files` is absent. TARGET_PRIORITY = ("deb", "rpm") +ARCH_FEED_SUFFIXES = ("-linux.yml", "-linux-arm64.yml") def main(argv: list[str]) -> int: @@ -50,6 +51,18 @@ def main(argv: list[str]) -> int: print(f"No manifests found in {manifest_dir}", file=sys.stderr) return 1 + # A half-populated feed is worse than none: clients trust it and the + # missing format silently never sees an update. + for suffix in ARCH_FEED_SUFFIXES: + if not any(feed.endswith(suffix) for feed in groups): + print(f"No *{suffix} feed; an architecture failed to build", file=sys.stderr) + return 1 + for feed, by_target in sorted(groups.items()): + absent = [t for t in TARGET_PRIORITY if t not in by_target] + if absent: + print(f"{feed}: missing {', '.join(absent)} manifest", file=sys.stderr) + return 1 + for feed, by_target in sorted(groups.items()): ordered = [t for t in TARGET_PRIORITY if t in by_target] ordered += [t for t in sorted(by_target) if t not in TARGET_PRIORITY] diff --git a/.github/workflows/mirror-linux-build.yml b/.github/workflows/mirror-linux-build.yml index 0e580572beb1..375fb188bf53 100644 --- a/.github/workflows/mirror-linux-build.yml +++ b/.github/workflows/mirror-linux-build.yml @@ -1,14 +1,14 @@ name: Mirror Linux build # Builds .deb and .rpm for x64 and arm64 from an upstream tag and publishes -# them as a release on this fork. Upstream's own release workflow ships a Linux -# x64 AppImage only, so this fills in the distro packages upstream never builds +# them as a release on this fork. Upstream's own release workflow ships Linux +# only as an AppImage, so this fills in the distro packages upstream never builds # without touching any upstream file. The AppImage is left to upstream; the # auto-update patch makes these .deb/.rpm installs updatable in place from this # fork's releases. # -# Every step here mirrors the "Linux x64" matrix entry of upstream's -# .github/workflows/release.yml. When that entry gains a dependency, this +# Every step here mirrors the Linux matrix entries of upstream's +# .github/workflows/release.yml. When those gain a dependency, this # workflow needs the same one. See MIRROR.md. on: @@ -154,6 +154,12 @@ jobs: # keeps that intentional rather than incidental, and guarantees a build # run from a differently-named repo never points users at upstream. T3CODE_DESKTOP_UPDATE_REPOSITORY: ${{ github.repository }} + # Upstream's public T3 Connect config, as baked into its own release + # builds. Without it cloud sign-in and T3 Connect are compiled out. + T3CODE_CLERK_PUBLISHABLE_KEY: pk_live_Y2xlcmsudDMuY29kZXMk + T3CODE_CLERK_JWT_TEMPLATE: t3-relay + T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: hzxSgY2cH10sDU2r + T3CODE_RELAY_URL: https://relay.t3.codes steps: - name: Checkout upstream tag uses: actions/checkout@v6 @@ -326,9 +332,65 @@ jobs: path: release-publish/** if-no-files-found: error + # Installs each package into a clean distro image, so a missing dependency, + # a package of the wrong format, or a build without the updater marker fails + # here instead of on a user's machine. + install: + name: Install ${{ matrix.target }} ${{ matrix.arch }} + needs: [build] + runs-on: ${{ matrix.runner }} + container: ${{ matrix.image }} + timeout-minutes: 15 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - { target: deb, arch: x64, runner: ubuntu-26.04, image: "debian:13" } + - { target: deb, arch: arm64, runner: ubuntu-26.04-arm, image: "debian:13" } + - { target: rpm, arch: x64, runner: ubuntu-26.04, image: "fedora:43" } + - { target: rpm, arch: arm64, runner: ubuntu-26.04-arm, image: "fedora:43" } + steps: + - name: Download package + uses: actions/download-artifact@v8 + with: + name: linux-${{ inputs.tag }}-${{ matrix.target }}-${{ matrix.arch }} + path: package + + - name: Install and inspect + shell: bash + env: + TARGET: ${{ matrix.target }} + run: | + set -euo pipefail + packages=(package/*."$TARGET") + if [[ "${#packages[@]}" -ne 1 ]]; then + echo "Expected one .$TARGET, found: ${packages[*]}" >&2 + exit 1 + fi + if [[ "$TARGET" == deb ]]; then + apt-get update + apt-get install -y "./${packages[0]}" + files=$(dpkg -L t3code) + else + dnf install -y "./${packages[0]}" + files=$(rpm -ql t3code) + fi + binary=$(grep '^/opt/.*/t3code$' <<<"$files") + dir=$(dirname "$binary") + if [[ "$(cat "$dir/resources/package-type")" != "$TARGET" ]]; then + echo "resources/package-type is not $TARGET; in-app updates would be disabled." >&2 + exit 1 + fi + if ldd "$binary" | grep 'not found'; then + echo "Package dependencies do not cover the libraries above." >&2 + exit 1 + fi + publish: name: Publish release - needs: [build] + needs: [build, install] runs-on: ubuntu-26.04 timeout-minutes: 10 permissions: @@ -427,12 +489,11 @@ jobs: fi echo "---" echo - echo "These are **not** official T3 Code builds. They are produced by this fork from unmodified upstream source at [\`$TAG\`](https://github.com/pingdotgg/t3code/releases/tag/$TAG), because upstream CI ships a Linux x64 AppImage only. This tag is $channel_note." + echo "These are **not** official T3 Code builds. They are produced by this fork from unmodified upstream source at [\`$TAG\`](https://github.com/pingdotgg/t3code/releases/tag/$TAG), because upstream CI ships Linux only as an AppImage. This tag is $channel_note." echo echo "- Formats: \`.deb\` and \`.rpm\`, x64 and arm64" echo "- **Auto-update works**, from this fork's releases. Installs prompt once for your password (via \`pkexec\`) because the update is applied with \`dpkg\`/\`rpm\`, the same way Windows prompts through UAC. A desktop without \`pkexec\` cannot auto-update; reinstall the newer package by hand." echo "- For the AppImage, use the official [pingdotgg/t3code releases](https://github.com/pingdotgg/t3code/releases) - this fork does not rebuild it." - echo "- Cloud sign-in and T3 Connect are disabled in these builds. See MIRROR.md." echo echo "For official builds, use [pingdotgg/t3code releases](https://github.com/pingdotgg/t3code/releases)." } > release-notes.md diff --git a/.github/workflows/mirror-patch-apply-check.yml b/.github/workflows/mirror-patch-apply-check.yml index 78e36c1fcea5..9af06cef65c6 100644 --- a/.github/workflows/mirror-patch-apply-check.yml +++ b/.github/workflows/mirror-patch-apply-check.yml @@ -64,16 +64,18 @@ jobs: - name: Checkout upstream tag uses: actions/checkout@v6 with: + repository: pingdotgg/t3code ref: ${{ matrix.tag }} - name: Apply mirror patches shell: bash env: + MIRROR_REPO: ${{ github.server_url }}/${{ github.repository }} MIRROR_REF: ${{ github.ref }} run: | set -euo pipefail - git fetch --no-tags --depth=1 origin "$MIRROR_REF" + git fetch --no-tags --depth=1 "$MIRROR_REPO" "$MIRROR_REF" git checkout FETCH_HEAD -- \ mirror-patches \ .github/scripts/apply-mirror-patches.sh diff --git a/MIRROR.md b/MIRROR.md index 5eac8e4bd185..25d486735b8b 100644 --- a/MIRROR.md +++ b/MIRROR.md @@ -2,24 +2,24 @@ This fork exists to build T3 Code Linux packages that upstream CI does not produce. -Upstream `pingdotgg/t3code` ships a Linux **x64 AppImage** and nothing else for Linux. This fork adds the distro packages upstream never builds: +Upstream `pingdotgg/t3code` ships Linux only as an **AppImage** (x64 and arm64), plus the AUR packages that repackage it. This fork adds the distro packages upstream never builds: - `.deb` and `.rpm` - x64 and arm64 -The AppImage is left to upstream; use their releases for it. Nothing else differs, apart from a three-line patch that lets `.deb` and `.rpm` installs use the in-app updater (see below). Everything else is upstream's source at an upstream tag. +The AppImage is left to upstream; use their releases for it. The source is upstream's at an upstream tag, plus the small patches in `mirror-patches/` that make `.deb` and `.rpm` installs updatable and properly described (see below). ## What these builds are not They are not official. Nobody at T3 Tools signs off on them, and bugs you hit here should be reproduced against an official build before being reported upstream. -Two behavioural differences worth knowing before you install: +Two things worth knowing before you install: **Auto-update works, and it updates from this fork.** The in-app "Update available" flow works on both formats. `.deb` and `.rpm` prompt once for your password through `pkexec`, because applying the update means running `dpkg -i` or `rpm -U` as root - the same trade Windows makes with its UAC prompt. No apt or dnf repository to add, and no waiting for a scheduled `apt upgrade`. -These builds update from **this fork's** releases, not upstream's, since that is where the arm64 and distro packages live. Moving to an official build later means downloading it from upstream once. +These builds update from **this fork's** releases, not upstream's, since that is where the distro packages live. Moving to an official build later means downloading it from upstream once. -**No cloud sign-in or T3 Connect.** Those need `T3CODE_CLERK_PUBLISHABLE_KEY` and `T3CODE_RELAY_URL`, which upstream injects from its own production environment. This fork does not have them, so `apps/server/vite.config.ts` bakes in empty strings and the features stay off. Local and LAN use is unaffected. Setting these would point users at the maintainers' relay infrastructure, so don't, without asking them first. +**Cloud sign-in and T3 Connect work, against upstream's service.** The build bakes in the same public Clerk and relay config upstream's own release builds carry (`T3CODE_CLERK_*`, `T3CODE_RELAY_URL` in `mirror-linux-build.yml`), so these builds use the maintainers' relay infrastructure exactly like an official install. If upstream rotates any of those values, update them there. ## How it works @@ -35,7 +35,7 @@ You can also build a specific tag by hand: Actions → Mirror Linux build → Ru ## Keeping up with upstream -The build steps are copied from the "Linux x64" matrix entry in upstream's `.github/workflows/release.yml`. When upstream adds a build dependency, this fork needs it too. +The build steps are copied from the "Linux x64" and "Linux arm64" matrix entries in upstream's `.github/workflows/release.yml`. When upstream adds a build dependency, this fork needs it too. There is no drift-detection job, deliberately. Upstream's `preflightLinuxDesktopBuild` in `scripts/build-desktop-artifact.ts` already checks `LINUX_DESKTOP_BUILD_PREREQUISITES` and fails with the missing package names, so a new dependency shows up as a legible build error rather than something subtle. Read that error before assuming the workflow is at fault. @@ -62,6 +62,8 @@ The change lives in `mirror-patches/`, not as a commit on `main`. `main` therefo A `preflight` job checks the patches before the four build jobs start, so a stale patch costs two minutes instead of four long builds. +After the builds, an `install` job installs each package into a clean `debian:13` or `fedora:43` container and fails the release if the package's dependencies leave any of the app's libraries unresolved, or if the updater marker (`resources/package-type`) is missing. + ## When a patch goes stale Preflight files an issue labelled `mirror-patch` naming the patch and linking the failed run, then assigns GitHub Copilot to it. Copilot opens a pull request against `main` with the patch file rewritten against the new upstream code. @@ -72,9 +74,6 @@ Assignment needs a `MIRROR_COPILOT_TOKEN` repository secret holding a PAT with i ## Upstreaming -Two things here are worth offering upstream, and both are small: - -- The arm64 gap is about eight lines in their release matrix, on a free runner. -- The auto-update patch is three lines and benefits them the moment they ship any distro package. It is also arguably a bug fix: the current check asks "is this an AppImage" when what it means is "can this install be updated", and electron-updater already answers that question more precisely. +The auto-update patch is worth offering upstream. It is three lines and benefits them the moment they ship any distro package. It is also arguably a bug fix: the current check asks "is this an AppImage" when what it means is "can this install be updated", and electron-updater already answers that question more precisely. This fork is worth keeping for `.deb` and `.rpm` either way, since those carry real packaging support burden upstream has not signed up for. diff --git a/mirror-patches/0002-linux-deb-rpm-package-metadata.md b/mirror-patches/0002-linux-deb-rpm-package-metadata.md index 156c8ab9c3eb..1d2882a059ed 100644 --- a/mirror-patches/0002-linux-deb-rpm-package-metadata.md +++ b/mirror-patches/0002-linux-deb-rpm-package-metadata.md @@ -14,13 +14,20 @@ there. `FpmTarget` reads `license` straight off that staged metadata for the rpm build-internal string `"T3 Code desktop build"`, and `LinuxTargetHelper.getDescription` feeds it to the deb `Description:` field, the rpm `%description`, and the `.desktop` `Comment` — so it surfaced verbatim - in `apt show` and as both title and subtitle in GNOME Software. + in `apt show` and as both title and subtitle in GNOME Software. It ends by + saying the build is unofficial, as does the AppStream description in 0003, + since both are what users see before installing. - `desktop.entry` additions (`GenericName`, `Keywords`). `Keywords` is a desktop-entry string list, so it keeps its trailing `;` — dropping it makes the whole value invalid. `Comment` is deliberately not set there: `LinuxTargetHelper.writeDesktopEntry` merges `desktop.entry` first and then overwrites `Comment` from `description`, so an entry value would be silently dropped. +- `deb.depends` and `rpm.depends`: electron-builder's defaults plus the ALSA + and GBM libraries, which Electron links against but the defaults omit, so a + minimal install could not start the app. `depends` replaces the defaults, so + they are copied; the install job in `mirror-linux-build.yml` catches it with + `ldd` if the list falls behind again. - `deb.recommends` and `deb.packageCategory`. `recommends` includes `policykit-1 | pkexec` because electron-updater installs a downloaded `.deb` through `pkexec`. It also repeats electron-builder's own default diff --git a/mirror-patches/0002-linux-deb-rpm-package-metadata.patch b/mirror-patches/0002-linux-deb-rpm-package-metadata.patch index 38c48624bbe1..a2a45f758efa 100644 --- a/mirror-patches/0002-linux-deb-rpm-package-metadata.patch +++ b/mirror-patches/0002-linux-deb-rpm-package-metadata.patch @@ -1,8 +1,8 @@ diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts -index 44d5f0ab1..ae8818c84 100644 +index cb47e40b8..17e513520 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts -@@ -944,6 +944,8 @@ interface StagePackageJson { +@@ -930,6 +930,8 @@ interface StagePackageJson { readonly packageManager: string; readonly description: string; readonly author: string; @@ -11,18 +11,18 @@ index 44d5f0ab1..ae8818c84 100644 readonly main: string; readonly build: Record; readonly dependencies: Record; -@@ -2761,6 +2763,10 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( +@@ -2737,6 +2739,10 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( executableName: "t3code", icon: "icons", category: "Development", + maintainer: "T3 Tools <97451137+TonybynMp4@users.noreply.github.com>", + synopsis: "The open-source control plane for coding agents", + description: -+ "Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. Bring your own subscriptions.", ++ "Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. Bring your own subscriptions. Unofficial community build of T3 Code.", // electron-builder turns these into MimeType=x-scheme-handler/; // in the .desktop entry (Exec already gets %U), so browsers can hand // t3code:// OAuth callbacks to the app. -@@ -2773,9 +2779,15 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( +@@ -2749,9 +2755,42 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( desktop: { entry: { StartupWMClass: "t3code", @@ -32,13 +32,40 @@ index 44d5f0ab1..ae8818c84 100644 }, }; + buildConfig.deb = { ++ depends: [ ++ "libgtk-3-0", ++ "libnotify4", ++ "libnss3", ++ "libxss1", ++ "libxtst6", ++ "xdg-utils", ++ "libatspi2.0-0", ++ "libuuid1", ++ "libsecret-1-0", ++ "libasound2", ++ "libgbm1", ++ ], + recommends: ["libappindicator3-1", "policykit-1 | pkexec"], + packageCategory: "devel", ++ }; ++ buildConfig.rpm = { ++ depends: [ ++ "gtk3", ++ "libnotify", ++ "nss", ++ "libXScrnSaver", ++ "(libXtst or libXtst6)", ++ "xdg-utils", ++ "at-spi2-core", ++ "(libuuid or libuuid1)", ++ "(alsa-lib or libasound2)", ++ "(mesa-libgbm or libgbm1)", ++ ], + }; } if (platform === "win") { -@@ -3774,7 +3786,9 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( +@@ -3651,7 +3690,9 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( private: true, packageManager: rootPackageJson.packageManager, description: "T3 Code desktop build", diff --git a/mirror-patches/0003-linux-appstream-metainfo.patch b/mirror-patches/0003-linux-appstream-metainfo.patch index 3ad9608f8777..e2fad353f5ac 100644 --- a/mirror-patches/0003-linux-appstream-metainfo.patch +++ b/mirror-patches/0003-linux-appstream-metainfo.patch @@ -23,7 +23,7 @@ index 848c5806b..95e3f8deb 100644 + The open-source control plane for coding agents + +

-+ Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. Bring your own subscriptions. ++ Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. Bring your own subscriptions. This is an unofficial community build of T3 Code. +

+
+ diff --git a/mirror-patches/0003-linux-appstream-metainfo.wslprebuild.patch b/mirror-patches/0003-linux-appstream-metainfo.wslprebuild.patch index 143a3684141b..a02984b6151b 100644 --- a/mirror-patches/0003-linux-appstream-metainfo.wslprebuild.patch +++ b/mirror-patches/0003-linux-appstream-metainfo.wslprebuild.patch @@ -23,7 +23,7 @@ index 44d5f0ab1..7dc738f85 100644 + The open-source control plane for coding agents + +

-+ Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. Bring your own subscriptions. ++ Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. Bring your own subscriptions. This is an unofficial community build of T3 Code. +

+
+