Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
167 changes: 167 additions & 0 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: Desktop Release

on:
push:
tags:
- "desktop-v*"

permissions:
contents: write

env:
DOWNLOAD_HOST: dl.shelldeck.app
DOWNLOAD_BASE_URL: https://dl.shelldeck.app/desktop
DESKTOP_ARCH: x86_64

jobs:
linux:
name: Linux desktop bundles
runs-on: ubuntu-latest
defaults:
run:
working-directory: desktop
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
cache-dependency-path: desktop/package-lock.json

- uses: dtolnay/rust-toolchain@stable

- name: Install Tauri Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
curl \
file \
libayatana-appindicator3-dev \
librsvg2-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
wget

- name: Install desktop dependencies
run: npm ci

- name: Build AppImage and deb
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: npm run tauri build -- --bundles appimage,deb

- name: Prepare Linux updater manifest
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#desktop-v}"
APPIMAGE="$(find src-tauri/target/release/bundle/appimage -type f -name '*.AppImage' | head -n 1)"
DEB="$(find src-tauri/target/release/bundle/deb -type f -name '*.deb' | head -n 1)"
SIG="${APPIMAGE}.sig"

test -f "$APPIMAGE"
test -f "$DEB"
test -f "$SIG"

mkdir -p release/upload release/linux
cp "$APPIMAGE" "$SIG" "$DEB" release/upload/

npm run latest-json -- \
--version "$VERSION" \
--target linux \
--arch "$DESKTOP_ARCH" \
--artifact "$APPIMAGE" \
--signature "$SIG" \
--url "$DOWNLOAD_BASE_URL/linux/$DESKTOP_ARCH/$(basename "$APPIMAGE")" \
--out release/linux/latest.json

- name: Upload Linux bundles to R2
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
R2_BUCKET: ${{ secrets.SHELLDECK_R2_BUCKET }}
run: |
set -euo pipefail
for file in release/upload/*; do
npx wrangler@latest r2 object put "$R2_BUCKET/desktop/linux/$DESKTOP_ARCH/$(basename "$file")" --file "$file"
done
npx wrangler@latest r2 object put "$R2_BUCKET/desktop/linux/$DESKTOP_ARCH/latest.json" --file release/linux/latest.json

- name: Attach Linux artifacts to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
desktop/release/upload/*
desktop/release/linux/latest.json

windows:
name: Windows desktop bundle
runs-on: windows-latest
defaults:
run:
working-directory: desktop
shell: bash
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
cache-dependency-path: desktop/package-lock.json

- uses: dtolnay/rust-toolchain@stable

- name: Install desktop dependencies
run: npm ci

- name: Build NSIS installer
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: npm run tauri build -- --bundles nsis

- name: Prepare Windows updater manifest
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#desktop-v}"
INSTALLER="$(find src-tauri/target/release/bundle/nsis -type f -name '*.exe' | head -n 1)"
SIG="${INSTALLER}.sig"

test -f "$INSTALLER"
test -f "$SIG"

mkdir -p release/upload release/windows
cp "$INSTALLER" "$SIG" release/upload/

npm run latest-json -- \
--version "$VERSION" \
--target windows \
--arch "$DESKTOP_ARCH" \
--artifact "$INSTALLER" \
--signature "$SIG" \
--url "$DOWNLOAD_BASE_URL/windows/$DESKTOP_ARCH/$(basename "$INSTALLER")" \
--out release/windows/latest.json

- name: Upload Windows bundle to R2
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
R2_BUCKET: ${{ secrets.SHELLDECK_R2_BUCKET }}
run: |
set -euo pipefail
for file in release/upload/*; do
npx wrangler@latest r2 object put "$R2_BUCKET/desktop/windows/$DESKTOP_ARCH/$(basename "$file")" --file "$file"
done
npx wrangler@latest r2 object put "$R2_BUCKET/desktop/windows/$DESKTOP_ARCH/latest.json" --file release/windows/latest.json

- name: Attach Windows artifacts to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
desktop/release/upload/*
desktop/release/windows/latest.json
5 changes: 5 additions & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
src-tauri/target
*.log

76 changes: 76 additions & 0 deletions desktop/BUILD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# ShellDeck Desktop Build Notes

ShellDeck Desktop is a Tauri v2 thin client. It does not bundle or start the ShellDeck server; it opens a user-configured server URL in a persistent desktop webview.

## Prerequisites

- Node.js 22+ and npm.
- Rust stable.
- Linux GUI build packages when building locally: `libwebkit2gtk-4.1-dev`, `build-essential`, `libssl-dev`, `libayatana-appindicator3-dev`, `librsvg2-dev`, `curl`, `wget`, and `file`.
- No local secret files are required for unsigned local checks.

## Install and Check Locally

```sh
cd desktop
npm ci
npm run build
```

If the Linux WebKitGTK development package is installed, also run:

```sh
cd desktop/src-tauri
cargo check
```

To build local Linux bundles:

```sh
cd desktop
npm run tauri build -- --bundles appimage,deb
```

## Updater Keypair

Generate the Tauri updater keypair once:

```sh
cd desktop
npm run tauri signer generate
```

Keep the private key out of the repo. Put only the public key in `src-tauri/tauri.conf.json`, replacing `REPLACE_WITH_TAURI_UPDATER_PUBKEY`.

Store the private key and password in GitHub Actions secrets:

- `TAURI_SIGNING_PRIVATE_KEY`
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`

## Release Secrets

The release workflow also expects Cloudflare R2 credentials by secret name:

- `CLOUDFLARE_ACCOUNT_ID`
- `CLOUDFLARE_API_TOKEN`
- `SHELLDECK_R2_BUCKET`

The R2 bucket must serve `https://dl.shelldeck.app/desktop/...` through its custom domain. No R2 credential value belongs in this repo.

## Cut a Release

1. Update the desktop version in `desktop/package.json`, `desktop/src-tauri/Cargo.toml`, and `desktop/src-tauri/tauri.conf.json`.
2. Make sure the updater public key placeholder has been replaced with the generated public key.
3. Commit the version change.
4. Tag the release:

```sh
git tag desktop-vX.Y.Z
git push origin feat/shelldeck-desktop
git push origin desktop-vX.Y.Z
Comment on lines +69 to +70
```

The tag triggers `.github/workflows/desktop-release.yml`. It builds Linux AppImage and `.deb` bundles on `ubuntu-latest`, builds the Windows NSIS installer on `windows-latest`, signs updater artifacts with the Tauri signing secrets, writes per-target `latest.json`, uploads artifacts to R2, and attaches them to the GitHub Release.

Windows code signing is intentionally skipped in v1, so Windows may show a SmartScreen warning until Authenticode signing is added.

32 changes: 32 additions & 0 deletions desktop/STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ShellDeck Desktop Status

## Built

- Added `desktop/`, a Tauri v2 thin client named ShellDeck.
- Added a first-run/settings screen that stores a ShellDeck server URL in localStorage.
- Added a Rust reachability check for the configured server URL before navigation.
- Added a connection-error screen with Retry and Edit server URL actions.
- Added native window state persistence, tray actions, single-instance focus behavior, and updater wiring.
- Added `.github/workflows/desktop-release.yml` for tag-based Linux and Windows desktop releases.
- Added `site/`, a static Cloudflare Pages landing page with current-version download buttons backed by `latest.json`.

## Verified

- `npm install` in `desktop/` completed with no vulnerabilities.
- `npm run build` in `desktop/` completed successfully.
- `npm run latest-json` generated a valid test updater manifest from a throwaway signature file.
- `git diff --check` completed with no whitespace errors.
- `cargo fmt --check` completed successfully.
- `npm run tauri info` parsed the Tauri app and confirmed WebKitGTK 4.1 is the missing local prerequisite.
- Playwright loaded the static site at desktop and 390px widths: no console warnings/errors after localhost manifest fetches were disabled, and no horizontal overflow was detected.
- Playwright loaded the desktop settings screen through Vite at desktop and 390px widths: no console warnings/errors and no horizontal overflow was detected.
- `cargo check` in `desktop/src-tauri` could not complete on this machine because WebKitGTK 4.1 development packages are not installed locally.

## Remaining Manual Steps

- Generate the Tauri updater keypair with `npm run tauri signer generate`.
- Replace `REPLACE_WITH_TAURI_UPDATER_PUBKEY` in `desktop/src-tauri/tauri.conf.json` with the generated public key.
- Add GitHub secrets: `TAURI_SIGNING_PRIVATE_KEY`, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`, `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`, and `SHELLDECK_R2_BUCKET`.
- Confirm `dl.shelldeck.app` serves the R2 bucket path used by the workflow.
- Replace `site/screenshot-*.png` placeholders with real screenshots.
- Cut the first release with `git tag desktop-vX.Y.Z` and push the tag.
13 changes: 13 additions & 0 deletions desktop/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ShellDeck</title>
<link rel="icon" href="/favicon.png" />
<script type="module" src="/src/main.ts"></script>
</head>
<body>
<main id="app" class="shell"></main>
</body>
</html>
Loading