diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a5fb3bd..ff81176 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -2,38 +2,36 @@ name: Build Linux on: push: - branches: - - release/production + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: 'recursive' - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1 - - name: Use Node.js 20.x - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 20.x - name: install dependencies run: npm i - - name: build + - name: build deb env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - run: npm run build:linux -- --publish never + run: npm run build:linux -- --linux deb --publish always - - name: publish amd64 - uses: snapcore/action-publish@v1 - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_EXPORT }} + - name: upload deb artifact + uses: actions/upload-artifact@v4 with: - snap: "${{ github.workspace }}/dist/Raindrop-amd64.snap" - release: stable \ No newline at end of file + name: Raindrop-linux-deb + path: dist/*.deb diff --git a/README.md b/README.md index 0100ce4..afd4019 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,37 @@ # Build local copy + +## Ubuntu / Linux + +### Prerequisites +- Node.js 18 or 20 (`node --version`) +- npm 9+ (`npm --version`) +- git + +### Build +```bash +git clone --recurse-submodules https://github.com/0xTriboulet/desktop.git +cd desktop +./build.sh +``` + +### Install +```bash +sudo dpkg -i dist/Raindrop.io_*.deb +raindrop +``` + +> **Note:** The `.deb` is the recommended artifact on Ubuntu. The AppImage requires +> `libfuse2` which is not installed by default on Ubuntu 22.04+. + +### Force a fresh webapp build +```bash +rm -rf webapp/dist/electron/prod/ +npm run build:linux +``` + +--- + +## Windows ``` git submodule update --init --recursive git submodule update --remote --merge @@ -7,10 +40,12 @@ npm run build:win ``` Then check `dist` folder +--- + # Deployment Run `npm run deploy:prod` # Local testing tips ## Windows Uncomment special `identityName` and `publisher` fields in `build/config.js` -Run `add-appxpackage -path appx.appx -AllowUnsigned` \ No newline at end of file +Run `add-appxpackage -path appx.appx -AllowUnsigned` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6ccbcf3 --- /dev/null +++ b/build.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# build.sh – Build Raindrop Desktop for Linux +# Usage: ./build.sh +# +# Produces in ./dist/: +# Raindrop.io__amd64.deb (recommended for Debian/Ubuntu) +# Raindrop.io-.AppImage (portable; requires libfuse2 to run) +# Raindrop-.snap (Snap Store package) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "==> Checking prerequisites..." +command -v node >/dev/null 2>&1 || { echo "ERROR: node is required but not found."; exit 1; } +command -v npm >/dev/null 2>&1 || { echo "ERROR: npm is required but not found."; exit 1; } + +NODE_VER=$(node --version | sed 's/v//') +NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1) +if [ "$NODE_MAJOR" -lt 18 ]; then + echo "ERROR: Node.js >= 18 required (found $NODE_VER)" + exit 1 +fi +echo " Node $(node --version), npm $(npm --version)" + +echo "==> Initialising git submodules..." +git submodule update --init --recursive + +echo "==> Installing npm dependencies..." +npm install + +echo "==> Building Linux targets (AppImage, deb, snap)..." +npm run build:linux + +echo "" +echo "==> Build complete. Artifacts in ./dist/:" +ls -lh dist/*.AppImage dist/*.deb dist/*.snap 2>/dev/null || ls dist/ + +echo "" +echo "To install on Debian/Ubuntu (recommended):" +DEB=$(ls dist/*.deb 2>/dev/null | head -1) +if [ -n "$DEB" ]; then + echo " sudo dpkg -i $DEB" + echo " Then launch: raindrop" +fi + +echo "" +echo "NOTE: The AppImage requires libfuse2 (not present by default on Ubuntu 22.04+)." +echo " Use the .deb package above instead." diff --git a/build/config.js b/build/config.js index cdec7f2..046121a 100644 --- a/build/config.js +++ b/build/config.js @@ -91,7 +91,9 @@ exports.default = ()=>({ executableName: 'raindrop', icon: 'build/linux', category: 'GNOME;GTK;Network;Education;Science', - target: ['snap'], + // AppImage and deb run outside snap confinement so GPU/display issues don't apply. + // snap kept for Snap Store publishing. + target: ['AppImage', 'deb', 'snap'], desktop: { entry: { StartupWMClass: 'Raindrop.io', @@ -101,6 +103,8 @@ exports.default = ()=>({ }, snap: { - artifactName: 'Raindrop-${arch}.${ext}' + artifactName: 'Raindrop-${arch}.${ext}', + // Grant display and GPU access so the snap version works without manual plug connections. + plugs: ['x11', 'wayland', 'desktop', 'desktop-legacy', 'opengl', 'home', 'network', 'browser-support'] } }) \ No newline at end of file diff --git a/build/version.mjs b/build/version.mjs index b3315ec..8bbeaf8 100644 --- a/build/version.mjs +++ b/build/version.mjs @@ -1,6 +1,9 @@ import fs from 'fs' -import { resolve } from 'path' +import { resolve, dirname } from 'path' +import { fileURLToPath } from 'url' -const version = JSON.parse(fs.readFileSync(resolve(import.meta.dirname, '../webapp/package.json'), 'utf-8')).version; -const packageContent = fs.readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf-8'); -fs.writeFileSync(resolve(import.meta.dirname, '../package.json'), packageContent.replace(/version": "(.*)"/i, `version": "${version}"`), 'utf-8'); \ No newline at end of file +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const version = JSON.parse(fs.readFileSync(resolve(__dirname, '../webapp/package.json'), 'utf-8')).version; +const packageContent = fs.readFileSync(resolve(__dirname, '../package.json'), 'utf-8'); +fs.writeFileSync(resolve(__dirname, '../package.json'), packageContent.replace(/version": "(.*)"/i, `version": "${version}"`), 'utf-8'); \ No newline at end of file diff --git a/build/webapp.js b/build/webapp.js index 0076f8b..3efed64 100644 --- a/build/webapp.js +++ b/build/webapp.js @@ -1,6 +1,31 @@ -const util = require('util') -const exec = util.promisify(require('child_process').exec) +const { execSync } = require('child_process') +const fs = require('fs') +const path = require('path') exports.default = async function(context) { - await exec('cd webapp && npm i && npm run build:electron') + const distIndex = path.resolve(__dirname, '../webapp/dist/electron/prod/index.html') + + // Skip webapp build if already built (avoids Sentry CLI auth failure in local/offline builds). + // To force a rebuild, delete webapp/dist/electron/prod/ and re-run. + if (fs.existsSync(distIndex)) { + console.log(' • webapp dist already present, skipping webapp build') + return + } + + // The webapp's webpack config includes a Sentry CLI plugin that exits non-zero when + // SENTRY_AUTH_TOKEN is missing. The dist files are still produced, so we tolerate the + // error and verify the output exists afterwards. + const env = { ...process.env } + if (!env.SENTRY_AUTH_TOKEN) env.SENTRY_AUTH_TOKEN = '' + + try { + execSync('cd webapp && npm i && npm run build:electron', { env, stdio: 'inherit' }) + } catch (e) { + // Check if dist was created despite the error (Sentry CLI failure is non-fatal) + if (fs.existsSync(distIndex)) { + console.log(' • webapp build exited with error (likely Sentry CLI) but dist was produced — continuing') + return + } + throw new Error('webapp build failed and no dist was produced: ' + e.message) + } } \ No newline at end of file diff --git a/src/index.mjs b/src/index.mjs index dc88d8c..5e0cf3e 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -11,6 +11,12 @@ import webview from './webview/index.mjs' // Fix webview fail for Twitter app.commandLine.appendSwitch('disable-features', 'CrossOriginOpenerPolicy') +// Linux display compatibility: auto-select Wayland when available, fall back to X11. +// This must be set before app.whenReady() so it also takes effect when a second +// instance is spawned by the OS to handle rnio:// deep-links (OAuth callback). +if (process.platform === 'linux') + app.commandLine.appendSwitch('ozone-platform-hint', 'auto') + // Security (snap doesn't work properly with sandbox) if (process.platform !== 'linux') app.enableSandbox()