diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml new file mode 100644 index 0000000..1c0a2a1 --- /dev/null +++ b/.github/workflows/release-linux.yml @@ -0,0 +1,100 @@ +name: Release Linux + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: Existing release tag, for example v0.1.5 + required: true + type: string + +permissions: + contents: write + +concurrency: + group: release-linux-${{ github.event.inputs.tag || github.ref_name }} + cancel-in-progress: false + +jobs: + build: + name: Build Linux x86_64 + runs-on: ubuntu-22.04 + timeout-minutes: 30 + env: + RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + GH_TOKEN: ${{ github.token }} + + steps: + - name: Check out release source + uses: actions/checkout@v6 + + - name: Install Tauri system dependencies + run: | + sudo apt-get update + sudo apt-get install --yes \ + libwebkit2gtk-4.1-dev \ + libxdo-dev \ + libssl-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.0 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Validate release inputs + run: | + : "${TAURI_SIGNING_PRIVATE_KEY:?TAURI_SIGNING_PRIVATE_KEY is not configured}" + VERSION="${RELEASE_TAG#v}" + CONFIG_VERSION="$(node -p "require('./src-tauri/tauri.conf.json').version")" + test "$VERSION" = "$CONFIG_VERSION" || { + echo "Tag $RELEASE_TAG does not match app version $CONFIG_VERSION" + exit 1 + } + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + + - name: Build AppImage and Debian package + run: bunx tauri build --bundles appimage,deb + + - name: Prepare release artifacts + run: | + APPIMAGE="$(find src-tauri/target/release/bundle/appimage -maxdepth 1 -name '*.AppImage' -print -quit)" + DEB="$(find src-tauri/target/release/bundle/deb -maxdepth 1 -name '*.deb' -print -quit)" + test -n "$APPIMAGE" && test -f "$APPIMAGE.sig" + test -n "$DEB" + + mkdir -p release-artifacts + install -m 755 "$APPIMAGE" "release-artifacts/Markd_${VERSION}_amd64.AppImage" + cp "$APPIMAGE.sig" "release-artifacts/Markd_${VERSION}_amd64.AppImage.sig" + cp "$DEB" "release-artifacts/Markd_${VERSION}_amd64.deb" + + bun scripts/generate-linux-update-manifest.js \ + "$VERSION" \ + "release-artifacts/Markd_${VERSION}_amd64.AppImage.sig" \ + "release-artifacts/latest-linux.json" \ + "Markd ${VERSION} for Linux" + + - name: Keep a workflow artifact + uses: actions/upload-artifact@v4 + with: + name: markd-linux-${{ env.VERSION }} + path: release-artifacts/ + if-no-files-found: error + + - name: Upload to GitHub release + run: | + gh release view "$RELEASE_TAG" >/dev/null 2>&1 || \ + gh release create "$RELEASE_TAG" --verify-tag --generate-notes --title "Markd ${VERSION}" + gh release upload "$RELEASE_TAG" release-artifacts/* --clobber diff --git a/AGENTS.md b/AGENTS.md index 90462ef..49b516e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Markd — agent guide -Local-first markdown notes app for macOS. Tauri 2 (Rust) + React 19 + Vite + Tailwind v4 + Tiptap 3 + Zustand. +Local-first markdown notes app for macOS and GNU/Linux. Tauri 2 (Rust) + React 19 + Vite + Tailwind v4 + Tiptap 3 + Zustand. ## Commands @@ -8,7 +8,8 @@ Local-first markdown notes app for macOS. Tauri 2 (Rust) + React 19 + Vite + Tai - `bun run build` — typecheck (tsc) + vite production build - `bunx tsc --noEmit` — typecheck only - `cd src-tauri && cargo test` — Rust unit tests -- `bun run release` — full signed release (scripts/release.sh) +- `bun run release`: full signed and notarized macOS release (scripts/release.sh) +- `.github/workflows/release-linux.yml`: signed x86_64 AppImage and Debian release ## Architecture diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee7bf13..214c2e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,4 +41,4 @@ See [AGENTS.md](./AGENTS.md) for the full architecture guide. The short version: ## Reporting bugs -Open an issue with: macOS version, Markd version, and repro steps. If it's a vault/data issue, mention whether it reproduces with a fresh vault folder. +Open an issue with your operating system, Markd version, and reproduction steps. If it is a vault or data issue, mention whether it reproduces with a fresh vault folder. diff --git a/README.md b/README.md index 7f5ebad..9c2687b 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ **Local-first notes for people who write.** -Markd is a macOS-first notes app built for developers and content creators who care about speed, privacy, and ownership. +Markd is a fast notes app for macOS and Linux, built for people who care about speed, privacy, and ownership. No accounts. No cloud. No sync (for now). -Your notes live on your Mac as plain `.md` files. Markd simply makes writing and finding them fast. +Your notes live on your disk as plain `.md` files. Markd simply makes writing and finding them fast. --- @@ -20,16 +20,35 @@ Markd releases are Developer ID signed and notarized by Apple before distributio --- +## Installing on Linux + +Download the latest AppImage or Debian package from the [GitHub releases](https://github.com/starc007/markd/releases/latest) page. + +Run the AppImage: + +```bash +chmod +x Markd_*_amd64.AppImage +./Markd_*_amd64.AppImage +``` + +Or install the Debian package: + +```bash +sudo apt install ./Markd_*_amd64.deb +``` + +--- + ## Features -- **WYSIWYG markdown editor** — write in a rich editor, saved as clean markdown on disk -- **Folders & subfolders** — organize notes in real, Finder-visible folders -- **Todos** — a standalone task list with tags and filtering -- **Bookmarks** — save links with auto-fetched title, image, and favicon; tag and filter them too -- **⌘K command palette** — jump to any note, folder, or page instantly -- **Instant search** — title + content, ranked, in milliseconds -- **Monochrome UI** — light, dark, or system theme; no color noise -- **Portable vault** — plain files, no IDs, no required metadata, no lock-in +- **WYSIWYG markdown editor:** write in a rich editor, saved as clean markdown on disk +- **Folders and subfolders:** organize notes in real, file-manager-visible folders +- **Todos:** a standalone task list with tags and filtering +- **Bookmarks:** save links with an auto-fetched title, image, and favicon +- **Command palette:** press Ctrl/Cmd+K to jump to any note, folder, or page instantly +- **Instant search:** title and content, ranked in milliseconds +- **Monochrome UI:** light, dark, or system theme with no color noise +- **Portable vault:** plain files, no IDs, no required metadata, no lock-in --- @@ -39,17 +58,17 @@ Pick any folder on disk as your vault: ``` / -├── notes/ plain .md files — filename is the title, folders are real folders +├── notes/ plain .md files, filename is the title, folders are real folders └── .markd/ app data: todos, bookmarks, tags, pasted images ``` -Notes are addressed by path, never by ID. Deletes go to the OS trash. Edit notes externally (vim, VS Code, whatever) — Markd picks up changes on window focus. +Notes are addressed by path, never by ID. Deletes go to the OS trash. Edit notes externally with vim, VS Code, or another editor. Markd picks up changes on window focus. --- ## Getting started -Requirements: [Bun](https://bun.sh), [Rust](https://rustup.rs), and Xcode Command Line Tools. +Requirements: [Bun](https://bun.sh), [Rust](https://rustup.rs), and the platform dependencies listed in the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/). ```bash bun install @@ -59,7 +78,7 @@ bun tauri dev # run the app Build a release bundle: ```bash -bun tauri build # produces a .app and .dmg under src-tauri/target/release/bundle +bun tauri build ``` Maintainers can follow [NOTARIZATION.md](./NOTARIZATION.md) to configure Developer ID signing and Apple notarization for releases. @@ -72,16 +91,16 @@ See [AGENTS.md](./AGENTS.md) for architecture details, or [CONTRIBUTING.md](./CO - Notes are stored locally as user-owned files - No analytics, tracking, accounts, or note-content uploads -- Markd connects to `usemarkd.app` to check for application updates +- Markd connects to `usemarkd.app` on macOS or GitHub Releases on Linux to check for application updates - Saving a bookmark fetches that page's title, preview image, and favicon -- Export your notes anytime — they're already just files +- Export your notes anytime because they are already just files --- ## Status Markd is under active development. -Sync, encryption, and publishing may be added later — without compromising local-first performance. +Sync, encryption, and publishing may be added later without compromising local-first performance. ## License diff --git a/scripts/generate-latest-json.js b/scripts/generate-latest-json.js index b72f116..e94dc67 100644 --- a/scripts/generate-latest-json.js +++ b/scripts/generate-latest-json.js @@ -67,7 +67,7 @@ const windowsSigPath = join( const linuxSigPath = join( buildDir, "appimage", - `${APP_NAME}_${version}_amd64.AppImage.tar.gz.sig` + `${APP_NAME}_${version}_amd64.AppImage.sig` ); // Also try alternative paths (Tauri v2 might use different structure) @@ -132,7 +132,7 @@ if (windowsSig) { if (linuxSig) { latestJson.platforms["linux-x86_64"] = { signature: linuxSig, - url: `${WEBSITE_URL}/${APP_NAME}_${version}_amd64.AppImage.tar.gz`, + url: `${WEBSITE_URL}/${APP_NAME}_${version}_amd64.AppImage`, }; console.log("✓ Added Linux platform"); } else { diff --git a/scripts/generate-linux-update-manifest.js b/scripts/generate-linux-update-manifest.js new file mode 100644 index 0000000..5b87295 --- /dev/null +++ b/scripts/generate-linux-update-manifest.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; + +const [version, signaturePath, outputPath, notes = `Markd ${version}`] = + process.argv.slice(2); + +if (!version || !signaturePath || !outputPath) { + console.error( + "Usage: bun scripts/generate-linux-update-manifest.js [notes]", + ); + process.exit(1); +} + +const normalizedVersion = version.replace(/^v/, ""); +const fileName = `Markd_${normalizedVersion}_amd64.AppImage`; +const manifest = { + version: normalizedVersion, + notes, + pub_date: new Date().toISOString(), + platforms: { + "linux-x86_64": { + signature: readFileSync(resolve(signaturePath), "utf8").trim(), + url: `https://github.com/starc007/markd/releases/download/v${normalizedVersion}/${fileName}`, + }, + }, +}; + +const resolvedOutput = resolve(outputPath); +mkdirSync(dirname(resolvedOutput), { recursive: true }); +writeFileSync(resolvedOutput, `${JSON.stringify(manifest, null, 2)}\n`); +console.log(`Generated ${resolvedOutput}`); diff --git a/site/.gitignore b/site/.gitignore index b370bf8..0a7f63f 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -15,6 +15,7 @@ cloudflare-env.d.ts # Only latest.json (tiny, versioned) is committed; the bundles are not. public/downloads/* public/updates/*.tar.gz +public/updates/*.AppImage !public/downloads/.gitkeep # TS build cache diff --git a/site/app/changelog/page.tsx b/site/app/changelog/page.tsx index 96ce206..e43a2dc 100644 --- a/site/app/changelog/page.tsx +++ b/site/app/changelog/page.tsx @@ -9,7 +9,7 @@ import { GITHUB } from "@/lib/config"; export const metadata: Metadata = { title: "Changelog | Markd", description: - "See what is new in Markd, the local-first Markdown notes app for macOS.", + "See what is new in Markd, the local-first Markdown notes app for macOS and Linux.", alternates: { canonical: "/changelog", }, diff --git a/site/app/download/page.tsx b/site/app/download/page.tsx new file mode 100644 index 0000000..55e1bbd --- /dev/null +++ b/site/app/download/page.tsx @@ -0,0 +1,97 @@ +import type { Metadata } from "next"; +import { Apple } from "lucide-react"; +import { Footer } from "@/components/Footer"; +import { Nav } from "@/components/Nav"; +import { DownloadLink } from "@/components/download/DownloadLink"; +import { PlatformCard } from "@/components/download/PlatformCard"; +import { LinuxIcon } from "@/components/download/PlatformIcons"; +import { DMG, LINUX_APPIMAGE, LINUX_DEB, VERSION } from "@/lib/config"; + +export const metadata: Metadata = { + title: "Download Markd", + description: + "Download Markd for Apple Silicon Macs or x86_64 GNU/Linux systems.", + alternates: { + canonical: "/download", + }, +}; + +export default function DownloadPage() { + return ( + <> +