diff --git a/.gitignore b/.gitignore index b4a1f0d5..5e3549d0 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,6 @@ docs/superpowers/ /apps/web/.open-next /apps/web/.wrangler /apps/web/cloudflare-env.d.ts + +# Local Linux build output (published to GitHub releases, never committed) +dist-linux/ diff --git a/apps/desktop/Dockerfile.linux b/apps/desktop/Dockerfile.linux new file mode 100644 index 00000000..551859dc --- /dev/null +++ b/apps/desktop/Dockerfile.linux @@ -0,0 +1,25 @@ +# Linux build environment for the MyDevTools desktop app. +# +# Linux packages can't be cross-compiled from macOS (they link against +# webkit2gtk/GTK), so release-local.sh builds them in this container instead. +# Build for whichever architecture you want to ship: +# +# docker build --platform linux/amd64 -f Dockerfile.linux -t mydevtools-linux:amd64 . +# docker build --platform linux/arm64 -f Dockerfile.linux -t mydevtools-linux:arm64 . +FROM node:22-bookworm + +# libdbus-1-dev → required by dbus-secret-service, which backs the Linux keyring +# xdg-utils → tauri-plugin-opener embeds xdg-open; AppImage bundling fails without it +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libwebkit2gtk-4.1-dev build-essential curl wget file \ + libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev \ + patchelf libdbus-1-dev xdg-utils pkg-config ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:${PATH}" + +# Installed GLOBALLY on purpose: the repo is bind-mounted at build time, and a +# local node_modules resolve would pick up the host's (macOS) CLI binary. +RUN npm install -g @tauri-apps/cli@2 diff --git a/apps/desktop/RELEASING.md b/apps/desktop/RELEASING.md index adc943ac..e52e4298 100644 --- a/apps/desktop/RELEASING.md +++ b/apps/desktop/RELEASING.md @@ -3,10 +3,16 @@ The app updates itself in place via the Tauri updater — users never re-download or reinstall, and the local SQLCipher database is never touched by an update. -Every release is: **build → sign + notarize → sign the update payload → publish to -the public releases repo**. Both methods below produce the same result and publish to -[`mydevtools-tech/mydevtools-releases`](https://github.com/mydevtools-tech/mydevtools-releases) -(public), which the updater endpoint and the website download button point at. +Every release is: **build → sign + notarize → sign the update payload → publish**. +Releases go to this repo (`mydevtools-tech/mydevtools`), which the updater endpoint +and the website download button point at. The old `mydevtools-releases` mirror is +historical — it only still matters for installs from v0.1.11 and earlier. + +`scripts/release-local.sh` publishes **one release containing both macOS and Linux**, +so users find every platform under a single tag. macOS is built natively; Linux is +built in Docker (see `Dockerfile.linux`), because it can't be cross-compiled from +macOS. Set `SKIP_LINUX=1` for a macOS-only release, or `LINUX_PLATFORMS="linux/amd64"` +to skip the slower ARM build. - **Local** — run `scripts/release-local.sh` on your Mac. No CI minutes; needs one-time setup. - **CI** — push to the `release` branch; GitHub builds it. No local setup; uses Actions minutes. @@ -98,10 +104,18 @@ That one command: 2. notarizes + staples the app 3. tars the app and **signs it with your updater key** → `.app.tar.gz` + `.sig` 4. builds + signs + notarizes the `.dmg` -5. writes `latest.json` (version + signature + public URL) -6. uploads all four to the public repo release `v` +5. builds the Linux `.deb` + AppImage in Docker (amd64 and arm64) +6. writes `latest.json` (version + signature + public URL) +7. uploads everything to a single release `v` + +Takes ~20–40 min (macOS compile, two notarization round-trips, plus the Linux +builds). Docker must be running for the Linux half; if it isn't, the script says so +and publishes macOS only. -Takes ~10–20 min (Rust compile + two notarization round-trips). +> **Linux has no auto-update.** Only AppImage can self-update, and `latest.json` +> currently advertises the `darwin-*` platforms only. Linux users re-download. If you +> add a `linux-x86_64` entry later, every publisher of that manifest must **merge** +> into it rather than overwrite, or one platform will wipe the other's entries. ### Verify @@ -173,12 +187,19 @@ account, no certificate, no notarytool. You just build and ship the artifacts. sudo apt update && sudo apt install -y \ libwebkit2gtk-4.1-dev build-essential curl wget file \ libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev patchelf \ - libdbus-1-dev + libdbus-1-dev xdg-utils ``` `libdbus-1-dev` is required by the `dbus-secret-service` crate, which backs the Linux keyring (see the platform-split `keyring` dependency in `Cargo.toml`). +`xdg-utils` is required by the AppImage bundler — `tauri-plugin-opener` embeds +`xdg-open`, and bundling fails with "xdg-open binary not found" without it. + +> On a memory-constrained builder (e.g. Docker Desktop), the release profile's fat +> LTO can get the linker OOM-killed (`signal: 9, SIGKILL`). Build with +> `CARGO_PROFILE_RELEASE_LTO=false` if that happens. + ### Build ```bash diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a1f4d248..a3779da5 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@mydevtools/desktop", - "version": "0.1.14", + "version": "0.1.15", "private": true, "scripts": { "dev": "tauri dev", diff --git a/apps/desktop/scripts/release-local.sh b/apps/desktop/scripts/release-local.sh index d01b6363..e4f77231 100755 --- a/apps/desktop/scripts/release-local.sh +++ b/apps/desktop/scripts/release-local.sh @@ -2,6 +2,10 @@ # Build a release LOCALLY and publish it so installed apps auto-update — the # same end result as the CI workflow, without spending Actions minutes. # +# Publishes ONE release containing BOTH macOS and Linux, so users see every +# platform under a single tag. Linux packages are built in Docker (they cannot +# be cross-compiled from macOS); set SKIP_LINUX=1 to publish macOS only. +# # Produces + publishes the four artifacts the updater needs: # 1. .app.tar.gz (updater payload) # 2. .app.tar.gz.sig (signed with YOUR updater key -> installs trust it) @@ -27,9 +31,13 @@ set -euo pipefail # ── config ─────────────────────────────────────────────────────────────────── ROOT="$(cd "$(dirname "$0")/.." && pwd)" +REPO_ROOT="$(cd "$ROOT/../.." && pwd)" : "${PUBLIC_REPO:=mydevtools-tech/mydevtools}" : "${APPLE_SIGNING_IDENTITY:=Developer ID Application: Nishanth P V (STTF2NVQK8)}" : "${NOTARY_PROFILE:=mydevtools-notary}" +# Linux packaging (Docker). SKIP_LINUX=1 publishes a macOS-only release. +: "${SKIP_LINUX:=0}" +: "${LINUX_PLATFORMS:=linux/amd64 linux/arm64}" KEYFILE="$HOME/.tauri/mydevtools-updater.key" PWFILE="$HOME/.tauri/mydevtools-updater.password" export APPLE_SIGNING_IDENTITY @@ -112,7 +120,63 @@ codesign_retry "$DMG" xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait xcrun stapler staple "$DMG" +# ── 3b. Linux packages, built in Docker ────────────────────────────────────── +# Linux links against webkit2gtk/GTK so it can't be cross-compiled from macOS. +# The frontend is already built above, so the container only does the Rust half +# (beforeBuildCommand is disabled) and reuses apps/desktop-ui/out. +LINUX_ARTIFACTS=() +if [ "$SKIP_LINUX" = "1" ]; then + echo "▸ [3b/5] Skipping Linux (SKIP_LINUX=1)" +elif ! docker info >/dev/null 2>&1; then + echo "▸ [3b/5] Docker is not running — skipping Linux packages." >&2 + echo " Start Docker Desktop and re-run, or set SKIP_LINUX=1 to silence this." >&2 +else + for PLATFORM in $LINUX_PLATFORMS; do + ARCH="${PLATFORM#linux/}" + IMAGE="mydevtools-linux:$ARCH" + echo "▸ [3b/5] Linux $ARCH — preparing image" + docker build --quiet --platform "$PLATFORM" \ + -f "$ROOT/Dockerfile.linux" -t "$IMAGE" "$ROOT" >/dev/null + + echo "▸ [3b/5] Linux $ARCH — building packages" + # LTO off: the fat-LTO link peaks well above what a Docker VM usually has + # and gets OOM-killed (signal: 9). Slightly larger binary, reliable build. + docker run --rm --platform "$PLATFORM" \ + -v "$REPO_ROOT":/work \ + -v "mydevtools-cargo-reg-$ARCH":/root/.cargo/registry \ + -v "mydevtools-cargo-target-$ARCH":/build \ + -e CARGO_TARGET_DIR=/build/target \ + -e APPIMAGE_EXTRACT_AND_RUN=1 \ + -e CARGO_PROFILE_RELEASE_LTO=false \ + "$IMAGE" bash -c ' + set -e + cd /work/apps/desktop + tauri build --bundles deb,appimage \ + --config "{\"build\":{\"beforeBuildCommand\":\"\"},\"bundle\":{\"createUpdaterArtifacts\":false}}" + mkdir -p /work/dist-linux + find "$CARGO_TARGET_DIR/release/bundle" \( -name "*.deb" -o -name "*.AppImage" \) \ + -exec cp -f {} /work/dist-linux/ \; + ' + done + # Stable, arch-suffixed copies so the website can link to a URL that never + # changes: .../releases/latest/download/MyDevTools-amd64.deb + for f in "$REPO_ROOT"/dist-linux/*.deb "$REPO_ROOT"/dist-linux/*.AppImage; do + [ -e "$f" ] || continue + LINUX_ARTIFACTS+=("$f") + case "$f" in + *_amd64.deb) cp -f "$f" "$REPO_ROOT/dist-linux/MyDevTools-amd64.deb"; LINUX_ARTIFACTS+=("$REPO_ROOT/dist-linux/MyDevTools-amd64.deb") ;; + *_arm64.deb) cp -f "$f" "$REPO_ROOT/dist-linux/MyDevTools-arm64.deb"; LINUX_ARTIFACTS+=("$REPO_ROOT/dist-linux/MyDevTools-arm64.deb") ;; + *x86_64.AppImage) cp -f "$f" "$REPO_ROOT/dist-linux/MyDevTools-x86_64.AppImage"; LINUX_ARTIFACTS+=("$REPO_ROOT/dist-linux/MyDevTools-x86_64.AppImage") ;; + *aarch64.AppImage) cp -f "$f" "$REPO_ROOT/dist-linux/MyDevTools-aarch64.AppImage"; LINUX_ARTIFACTS+=("$REPO_ROOT/dist-linux/MyDevTools-aarch64.AppImage") ;; + esac + done + echo "▸ [3b/5] Linux artifacts: ${#LINUX_ARTIFACTS[@]}" +fi + # ── 4. latest.json: manifest the updater reads (signature + public URL) ─────── +# NOTE: darwin-* keys only. Linux packages ship without auto-update for now — +# only AppImage can self-update, and adding linux-x86_64 here means every +# publisher of this manifest must merge rather than overwrite it. echo "▸ [4/5] Writing latest.json…" LATEST="$OUT/latest.json" SIG_CONTENT="$(cat "$SIG")" \ @@ -146,6 +210,10 @@ gh release create "$TAG" --repo "$PUBLIC_REPO" \ cp "$DMG" "$OUT/MyDevTools.dmg" gh release upload "$TAG" --repo "$PUBLIC_REPO" --clobber \ "$DMG" "$OUT/MyDevTools.dmg" "$TARGZ" "$SIG" "$LATEST" +if [ "${#LINUX_ARTIFACTS[@]}" -gt 0 ]; then + echo "▸ [5/5] Uploading ${#LINUX_ARTIFACTS[@]} Linux artifacts…" + gh release upload "$TAG" --repo "$PUBLIC_REPO" --clobber "${LINUX_ARTIFACTS[@]}" +fi echo "" echo "✅ Published v$VERSION. Installed apps will offer the update on next launch." diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index bb69cbe1..6defc80e 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -2839,7 +2839,7 @@ dependencies = [ [[package]] name = "mydevtools-desktop" -version = "0.1.14" +version = "0.1.15" dependencies = [ "base64 0.22.1", "bytes", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 29c05e80..56ca6a43 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mydevtools-desktop" -version = "0.1.14" +version = "0.1.15" description = "MyDevTools desktop app" edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index ebe8717e..a2b51b45 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "MyDevTools", - "version": "0.1.14", + "version": "0.1.15", "identifier": "tech.mydevtools.desktop", "build": { "beforeDevCommand": "pnpm --filter @mydevtools/desktop-ui dev:tauri", diff --git a/apps/web/src/app/linux-builds/page.tsx b/apps/web/src/app/linux-builds/page.tsx new file mode 100644 index 00000000..8f4bee17 --- /dev/null +++ b/apps/web/src/app/linux-builds/page.tsx @@ -0,0 +1,131 @@ +import type { Metadata } from "next"; +import { Download, TerminalSquare } from "lucide-react"; + +import { Header } from "@/components/header"; +import { Footer } from "@/components/footer"; +import { Button } from "@/components/ui/button"; + +const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://mydevtools.tech"; + +/** + * Where the Linux artifacts live. Preview builds are published as a GitHub + * pre-release so the binaries stay out of git; the page links to the tag rather + * than to a fixed filename, which changes every version. + */ +const LINUX_RELEASE_URL = + "https://github.com/mydevtools-tech/mydevtools/releases/tag/v0.1.14-linux-preview"; + +export const metadata: Metadata = { + title: "MyDevTools for Linux — preview builds", + description: + "Preview builds of the MyDevTools desktop app for Linux — Intel/AMD (x86_64) and ARM64. Completely offline, no account required, free for everyone. AppImage and .deb packages.", + alternates: { canonical: `${baseUrl}/linux-builds` }, + openGraph: { + title: "MyDevTools for Linux — preview builds | MyDevTools", + description: + "Preview builds of the MyDevTools desktop app for Linux — Intel/AMD and ARM64. AppImage and .deb. Offline, no account, free for everyone.", + url: `${baseUrl}/linux-builds`, + siteName: "MyDevTools", + type: "website", + }, +}; + +export default function LinuxBuildsPage() { + return ( +
+
+
+
+
+ +
+ +
+
+

Desktop app · Linux

+

+ Your entire dev toolkit,{" "} + on Linux. +

+

+ The same offline, local-first toolkit that runs on macOS — packaged as an + AppImage and a .deb. No account, no sign-in, free for everyone. +

+ + {/* Preview status — set expectations honestly */} +
+

Preview build

+

+ Linux support is new and still being validated. Unlike the macOS build, + these packages are not yet covered by automatic updates — grab a newer + build from this page when one lands. Requires Ubuntu 22.04+ or Debian 12+ + (older releases don't ship libwebkit2gtk-4.1-0). +

+
+ +
+ +

+ Intel/AMD & ARM64 · from the GitHub releases page +

+
+ + {/* Install instructions */} +
+

Installing

+ +

+ Intel or AMD — both are the same + architecture (amd64), so one package covers every normal PC: +

+
+              sudo apt install ./MyDevTools_*_amd64.deb
+            
+ +

+ On ARM64 (Raspberry Pi, ARM servers) use the arm64.deb or the{" "} + aarch64.AppImage. Unsure? Run dpkg --print-architecture. +

+ +

+ AppImage — runs anywhere, no + install: +

+
+              {`chmod +x MyDevTools_*.AppImage
+./MyDevTools_*.AppImage`}
+            
+ +

+ Debian / Ubuntu: +

+
+              sudo apt install ./MyDevTools_*.deb
+            
+ +

+ MyDevTools stores its database encryption key in your desktop keyring + (GNOME Keyring or KWallet), so a desktop session with a running keyring + service is required. +

+
+ +

+ Looking for macOS?{" "} + + Download for Mac + +

+
+
+ +
+
+ ); +} diff --git a/apps/web/src/lib/changelog.ts b/apps/web/src/lib/changelog.ts index 6d0a111d..c2a938ef 100644 --- a/apps/web/src/lib/changelog.ts +++ b/apps/web/src/lib/changelog.ts @@ -25,6 +25,20 @@ export const changeTypeLabels: Record = { * comparator is code nobody needs when the author controls the order. */ export const changelog: ChangelogEntry[] = [ + { + version: '0.1.15', + date: '2026-08-21', + title: 'MyDevTools on Linux', + summary: + 'The desktop app now runs on Linux. Every release from here ships macOS and Linux together under one version, so you can grab the build for your machine in one place.', + changes: [ + { type: 'added', text: 'Linux support: install with a .deb on Debian and Ubuntu (22.04 or newer), or run the AppImage without installing anything.' }, + { type: 'added', text: 'Builds for Intel/AMD and for ARM64 machines.' }, + { type: 'improved', text: 'On Linux your database key is kept in the system keyring (GNOME Keyring or KWallet), the same way the macOS build uses the Keychain.' }, + { type: 'improved', text: 'macOS and Linux are now published together in a single release instead of separate ones.' }, + { type: 'fixed', text: 'The window title bar no longer leaves an empty gap on Linux, where it reserved room for the macOS window buttons.' }, + ], + }, { version: '0.1.14', date: '2026-08-14',