Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
25 changes: 25 additions & 0 deletions apps/desktop/Dockerfile.linux
Original file line number Diff line number Diff line change
@@ -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
37 changes: 29 additions & 8 deletions apps/desktop/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<version>`
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<version>`

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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mydevtools/desktop",
"version": "0.1.14",
"version": "0.1.15",
"private": true,
"scripts": {
"dev": "tauri dev",
Expand Down
68 changes: 68 additions & 0 deletions apps/desktop/scripts/release-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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>.app.tar.gz (updater payload)
# 2. <app>.app.tar.gz.sig (signed with YOUR updater key -> installs trust it)
Expand All @@ -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
Expand Down Expand Up @@ -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")" \
Expand Down Expand Up @@ -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."
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mydevtools-desktop"
version = "0.1.14"
version = "0.1.15"
description = "MyDevTools desktop app"
edition = "2021"

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
131 changes: 131 additions & 0 deletions apps/web/src/app/linux-builds/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="dark mdt-deck flex flex-col min-h-screen bg-background text-foreground font-sans">
<div aria-hidden style={{ position: "fixed", inset: 0, zIndex: -1, pointerEvents: "none" }}>
<div className="mdt-grid" />
<div className="mdt-noise" />
</div>

<Header />

<main className="flex-1">
<section className="mx-auto max-w-3xl px-6 pt-32 pb-24 text-center">
<p className="mdt-kicker">Desktop app · Linux</p>
<h1 className="mt-4 text-4xl sm:text-5xl font-semibold tracking-tight">
Your entire dev toolkit,{" "}
<span className="mdt-grad-text">on Linux.</span>
</h1>
<p className="mt-5 text-lg text-muted-foreground">
The same offline, local-first toolkit that runs on macOS — packaged as an
AppImage and a <code>.deb</code>. No account, no sign-in, free for everyone.
</p>

{/* Preview status — set expectations honestly */}
<div className="mt-8 mx-auto max-w-xl rounded-xl border border-amber-500/30 bg-amber-500/[0.06] px-5 py-4 text-left">
<p className="text-sm font-semibold text-amber-200">Preview build</p>
<p className="mt-1.5 text-sm text-muted-foreground">
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 <code>libwebkit2gtk-4.1-0</code>).
</p>
</div>

<div className="mt-10 flex flex-col items-center">
<Button asChild size="lg" className="gap-2">
<a href={LINUX_RELEASE_URL} target="_blank" rel="noreferrer">
<TerminalSquare className="h-4 w-4" />
Get the Linux build
<Download className="h-4 w-4 opacity-70" />
</a>
</Button>
<p className="mt-2 text-sm text-muted-foreground">
Intel/AMD &amp; ARM64 · from the GitHub releases page
</p>
</div>

{/* Install instructions */}
<div className="mt-16 mx-auto max-w-xl text-left mdt-surface rounded-xl p-6">
<h2 className="text-sm font-semibold">Installing</h2>

<p className="mt-3 text-sm text-muted-foreground">
<strong className="text-foreground">Intel or AMD</strong> — both are the same
architecture (<code>amd64</code>), so one package covers every normal PC:
</p>
<pre className="mt-2 overflow-x-auto rounded-lg bg-black/40 p-3 text-xs text-muted-foreground">
<code>sudo apt install ./MyDevTools_*_amd64.deb</code>
</pre>

<p className="mt-4 text-xs text-muted-foreground">
On ARM64 (Raspberry Pi, ARM servers) use the <code>arm64.deb</code> or the{" "}
<code>aarch64.AppImage</code>. Unsure? Run <code>dpkg --print-architecture</code>.
</p>

<p className="mt-3 text-sm text-muted-foreground">
<strong className="text-foreground">AppImage</strong> — runs anywhere, no
install:
</p>
<pre className="mt-2 overflow-x-auto rounded-lg bg-black/40 p-3 text-xs text-muted-foreground">
<code>{`chmod +x MyDevTools_*.AppImage
./MyDevTools_*.AppImage`}</code>
</pre>

<p className="mt-4 text-sm text-muted-foreground">
<strong className="text-foreground">Debian / Ubuntu</strong>:
</p>
<pre className="mt-2 overflow-x-auto rounded-lg bg-black/40 p-3 text-xs text-muted-foreground">
<code>sudo apt install ./MyDevTools_*.deb</code>
</pre>

<p className="mt-4 text-xs text-muted-foreground">
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.
</p>
</div>

<p className="mt-10 text-sm text-muted-foreground">
Looking for macOS?{" "}
<a className="underline underline-offset-2" href="/download">
Download for Mac
</a>
</p>
</section>
</main>

<Footer />
</div>
);
}
14 changes: 14 additions & 0 deletions apps/web/src/lib/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ export const changeTypeLabels: Record<ChangeType, string> = {
* 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',
Expand Down
Loading