Skip to content
Open
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
4 changes: 3 additions & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
platform: ${{ matrix.platform }}
target: ${{ matrix.target }}
build-args: ${{ matrix.args }}
sign-binaries: true
# Unsigned: run artifacts on this public repo are downloadable by anyone,
# so a signed DMG here would bypass the paid download.
sign-binaries: false
asset-prefix: "handy-test"
upload-artifacts: true
is-debug-build: ${{ contains(matrix.args, '--debug') }}
Expand Down
91 changes: 90 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ on:
required: false
type: boolean
default: false
publish-r2:
required: false
type: boolean
default: false
repository:
required: false
type: string
Expand All @@ -47,7 +51,7 @@ on:
jobs:
build:
permissions:
contents: write
contents: read
runs-on: ${{ inputs.platform }}
steps:
- name: Checkout repository
Expand Down Expand Up @@ -298,6 +302,91 @@ jobs:
src-tauri/target/${{ inputs.target }}/${{ steps.build-profile.outputs.profile }}/bundle/macos/*.app
retention-days: 30

- name: Determine R2 arch label
id: r2-arch
if: inputs.publish-r2 && contains(inputs.platform, 'macos')
shell: bash
run: |
case "${{ inputs.target }}" in
aarch64-apple-darwin) echo "arch=aarch64" >> "$GITHUB_OUTPUT" ;;
x86_64-apple-darwin) echo "arch=x64" >> "$GITHUB_OUTPUT" ;;
*)
echo "Unsupported target for R2 publish: ${{ inputs.target }}" >&2
exit 1
;;
esac

- name: Ensure AWS CLI is available
if: inputs.publish-r2 && contains(inputs.platform, 'macos')
shell: bash
run: |
set -euo pipefail
if command -v aws >/dev/null 2>&1; then
aws --version
exit 0
fi
# GitHub-hosted runner images ship the AWS CLI. Fail loudly instead of
# installing an unpinned copy at release time.
echo "::error::aws CLI not found on this runner image"
exit 1

- name: Locate macOS bundle outputs
id: bundle-paths
if: inputs.publish-r2 && contains(inputs.platform, 'macos')
shell: bash
run: |
set -euo pipefail
BUNDLE_ROOT="src-tauri/target/${{ inputs.target }}/${{ steps.build-profile.outputs.profile }}/bundle"
DMG_PATH=$(find "$BUNDLE_ROOT/dmg" -maxdepth 1 -name '*.dmg' | head -1)
APP_TARBALL=$(find "$BUNDLE_ROOT/macos" -maxdepth 1 -name '*.app.tar.gz' | head -1)
APP_SIG=$(find "$BUNDLE_ROOT/macos" -maxdepth 1 -name '*.app.tar.gz.sig' | head -1)

if [[ -z "$DMG_PATH" || -z "$APP_TARBALL" || -z "$APP_SIG" ]]; then
echo "Missing expected macOS bundle output(s) under $BUNDLE_ROOT" >&2
echo "dmg='$DMG_PATH' app_tarball='$APP_TARBALL' app_sig='$APP_SIG'" >&2
exit 1
fi

echo "dmg=$DMG_PATH" >> "$GITHUB_OUTPUT"
echo "app_tarball=$APP_TARBALL" >> "$GITHUB_OUTPUT"
echo "app_sig=$APP_SIG" >> "$GITHUB_OUTPUT"

- name: Publish macOS artifacts to R2
if: inputs.publish-r2 && contains(inputs.platform, 'macos')
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
R2_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
R2_PRIVATE_BUCKET: ${{ secrets.R2_PRIVATE_BUCKET }}
R2_PUBLIC_BUCKET: ${{ secrets.R2_PUBLIC_BUCKET }}
run: |
set -euo pipefail
VERSION="${{ steps.get-version.outputs.version }}"
ARCH="${{ steps.r2-arch.outputs.arch }}"

# Private bucket: versioned DMG only. `macos/latest/...` is refreshed
# by the release workflow's publish-feed job after both arches land,
# not here.
aws s3 cp "${{ steps.bundle-paths.outputs.dmg }}" \
"s3://${R2_PRIVATE_BUCKET}/macos/v${VERSION}/Dictx_${VERSION}_${ARCH}.dmg" \
--endpoint-url "$R2_ENDPOINT" \
--content-type application/x-apple-diskimage \
--cache-control "public, max-age=31536000, immutable"

# Public bucket: updater bundle + signature for this version.
aws s3 cp "${{ steps.bundle-paths.outputs.app_tarball }}" \
"s3://${R2_PUBLIC_BUCKET}/macos/v${VERSION}/Dictx_${ARCH}.app.tar.gz" \
--endpoint-url "$R2_ENDPOINT" \
--content-type application/gzip \
--cache-control "public, max-age=31536000, immutable"

aws s3 cp "${{ steps.bundle-paths.outputs.app_sig }}" \
"s3://${R2_PUBLIC_BUCKET}/macos/v${VERSION}/Dictx_${ARCH}.app.tar.gz.sig" \
--endpoint-url "$R2_ENDPOINT" \
--content-type text/plain \
--cache-control "public, max-age=31536000, immutable"

- name: Install FUSE for AppImage processing
if: contains(inputs.platform, 'ubuntu')
run: |
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/pr-test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
jobs:
build-test:
permissions:
contents: write
contents: read
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -43,12 +43,13 @@ jobs:
platform: ${{ matrix.platform }}
target: ${{ matrix.target }}
build-args: ${{ matrix.args }}
sign-binaries: true
# Unsigned: PR code must never see signing secrets, and signed DMGs in
# public run artifacts would bypass the paid download.
sign-binaries: false
asset-prefix: "handy-pr-${{ inputs.pr_number }}"
upload-artifacts: true
is-debug-build: ${{ contains(matrix.args, '--debug') }}
ref: ${{ format('refs/pull/{0}/merge', inputs.pr_number) }}
secrets: inherit

comment-on-pr:
needs: build-test
Expand Down
132 changes: 116 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,6 @@ jobs:
- platform: "macos-latest" # for Intel based macs.
args: "--target x86_64-apple-darwin"
target: "x86_64-apple-darwin"
- platform: "ubuntu-22.04" # Build .deb on 22.04
args: "--bundles deb"
target: "x86_64-unknown-linux-gnu"
- platform: "ubuntu-24.04" # Build AppImage and RPM on 24.04
args: "--bundles appimage,rpm"
target: "x86_64-unknown-linux-gnu"
- platform: "ubuntu-24.04-arm" # Build for ARM64 Linux
args: "--bundles appimage,deb,rpm"
target: "aarch64-unknown-linux-gnu"
- platform: "windows-latest"
args: ""
target: "x86_64-pc-windows-msvc"
- platform: "windows-11-arm" # for ARM64 Windows runner
args: "--target aarch64-pc-windows-msvc"
target: "aarch64-pc-windows-msvc"

uses: ./.github/workflows/build.yml
with:
Expand All @@ -76,5 +61,120 @@ jobs:
sign-binaries: true
asset-prefix: "Dictx"
upload-artifacts: false
release-id: ${{ needs.create-release.outputs.release-id }}
publish-r2: true
secrets: inherit

publish-feed:
permissions:
contents: read
needs: publish-tauri
runs-on: ubuntu-latest
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
R2_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
R2_PRIVATE_BUCKET: ${{ secrets.R2_PRIVATE_BUCKET }}
R2_PUBLIC_BUCKET: ${{ secrets.R2_PUBLIC_BUCKET }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Get version from tauri.conf.json
id: get-version
shell: bash
run: |
VERSION=$(grep -o '"version": "[^"]*"' src-tauri/tauri.conf.json | cut -d'"' -f4)
echo "Application version from tauri.conf.json: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Ensure AWS CLI is available
shell: bash
run: |
set -euo pipefail
if command -v aws >/dev/null 2>&1; then
aws --version
exit 0
fi
# GitHub-hosted runner images ship the AWS CLI. Fail loudly instead of
# installing an unpinned copy at release time.
echo "::error::aws CLI not found on this runner image"
exit 1

- name: Verify both macOS DMGs published
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.get-version.outputs.version }}"
for ARCH in aarch64 x64; do
aws s3api head-object \
--bucket "$R2_PRIVATE_BUCKET" \
--key "macos/v${VERSION}/Dictx_${VERSION}_${ARCH}.dmg" \
--endpoint-url "$R2_ENDPOINT" >/dev/null
done

- name: Fetch updater signatures and build latest.json
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.get-version.outputs.version }}"
PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
WORKDIR="$(mktemp -d)"
echo "WORKDIR=$WORKDIR" >> "$GITHUB_ENV"

fetch_sig() {
local arch="$1"
local out="$2"
aws s3 cp \
"s3://${R2_PUBLIC_BUCKET}/macos/v${VERSION}/Dictx_${arch}.app.tar.gz.sig" \
"$out" \
--endpoint-url "$R2_ENDPOINT"
}

fetch_sig aarch64 "$WORKDIR/aarch64.sig"
fetch_sig x64 "$WORKDIR/x64.sig"

SIG_AARCH64="$(cat "$WORKDIR/aarch64.sig")"
SIG_X64="$(cat "$WORKDIR/x64.sig")"

cat > "$WORKDIR/latest.json" <<JSON
{
"version": "${VERSION}",
"notes": "See the release notes: https://github.com/splitlabs/dictx/releases",
"pub_date": "${PUB_DATE}",
"platforms": {
"darwin-aarch64": {
"signature": "${SIG_AARCH64}",
"url": "https://updates.dictx.splitlabs.io/macos/v${VERSION}/Dictx_aarch64.app.tar.gz"
},
"darwin-x86_64": {
"signature": "${SIG_X64}",
"url": "https://updates.dictx.splitlabs.io/macos/v${VERSION}/Dictx_x64.app.tar.gz"
}
}
}
JSON

- name: Upload latest.json to the public update feed
shell: bash
run: |
set -euo pipefail
aws s3 cp "$WORKDIR/latest.json" \
"s3://${R2_PUBLIC_BUCKET}/latest.json" \
--endpoint-url "$R2_ENDPOINT" \
--content-type application/json \
--cache-control "no-cache"

- name: Promote versioned DMGs to macos/latest
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.get-version.outputs.version }}"
for ARCH in aarch64 x64; do
aws s3 cp \
"s3://${R2_PRIVATE_BUCKET}/macos/v${VERSION}/Dictx_${VERSION}_${ARCH}.dmg" \
"s3://${R2_PRIVATE_BUCKET}/macos/latest/Dictx_${ARCH}.dmg" \
--endpoint-url "$R2_ENDPOINT" \
--content-type application/x-apple-diskimage \
--cache-control "no-cache"
done
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,4 @@ blob-report/
.direnv
.envrc

# Gumroad
gumroad-delivery.txt
config.bat
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ Access debug features: `Cmd+Shift+D` (macOS) or `Ctrl+Shift+D` (Windows/Linux)

Dictx is licensed under **GPL-3.0-or-later**. See `LICENSE` and `NOTICE` for details.

**Monetization model**: Open source code + paid signed binary ($29 one-time via Gumroad). All features are available when building from source — no feature gating, no license key validation. The paid product is the convenience of a signed binary with auto-updates.
**Monetization model**: Open source code + paid signed macOS binary ($29 one-time via Stripe checkout at `https://dictx.splitlabs.io/buy`). All features are available when building from source — no feature gating, no license keys, no activation step. The paid product is a signed, notarized DMG downloaded from private Cloudflare R2 storage after the checkout is verified server-side; every install, free or paid, auto-updates from the same public Tauri update feed at `https://updates.dictx.splitlabs.io/latest.json`.

**Purchase links**: Gumroad product page at `https://0xnyk.gumroad.com/l/dictx`. This URL appears in `AboutSettings.tsx`, `Onboarding.tsx`, and `README.md`.
**Purchase links**: the About settings page and the landing site link to `https://dictx.splitlabs.io/buy`.

## Platform Notes

Expand Down
Loading
Loading