Archived
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be22960c6f | ||
|
|
ce28c3ddb7 | ||
|
|
ef412e3d12 |
@@ -0,0 +1,65 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
bump:
|
||||||
|
description: "Semver bump (patch, minor, major)"
|
||||||
|
required: false
|
||||||
|
default: "patch"
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
if: github.actor != 'github-actions[bot]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: true
|
||||||
|
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Bump version and tag
|
||||||
|
id: bump
|
||||||
|
env:
|
||||||
|
BUMP: ${{ inputs.bump }}
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
npm version "${BUMP:-patch}" -m "chore: release %s"
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
git push
|
||||||
|
git push --tags
|
||||||
|
|
||||||
|
- name: Build artifacts
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{ steps.bump.outputs.version }}
|
||||||
|
name: Linear v${{ steps.bump.outputs.version }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
files: |
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.rpm
|
||||||
@@ -11,9 +11,20 @@ A linux port of <a href="https://linear.app/">linear.app</a>
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
|
Requires `sudo` (for sandbox helper, desktop entry, and icons).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget -qO- https://raw.githubusercontent.com/kleo-dev/linear-linux/refs/heads/master/installer.sh | bash
|
wget -qO- https://raw.githubusercontent.com/kleo-dev/linear-linux/refs/heads/master/installer.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The installer:
|
||||||
|
- Downloads the AppImage, extracts it under `/opt/linear-linux-<version>`, and wires up the `chrome-sandbox` helper correctly.
|
||||||
|
- Installs a wrapper at `/usr/local/bin/linear`, a desktop entry, and the Linear icon into the system icon cache.
|
||||||
|
- Accepts overrides: `VERSION=0.2.1 APPIMAGE_URL=<url> INSTALL_ROOT=/opt ./installer.sh`
|
||||||
|
|
||||||
|
Development:
|
||||||
|
- `npm start` launches Electron with sandbox disabled for local runs (packaged builds use the proper setuid helper).
|
||||||
|
- `npm run build` produces an AppImage that bundles the Linear brand assets for the desktop icon.
|
||||||
|
|
||||||
# Having an issue?
|
# Having an issue?
|
||||||
Describe your issue [here](https://github.com/kleo-dev/linear-linux/issues)
|
Describe your issue [here](https://github.com/kleo-dev/linear-linux/issues)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 639 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.4 KiB |
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
chown root:root /opt/Linear/chrome-sandbox || true
|
||||||
|
chmod 4755 /opt/Linear/chrome-sandbox || true
|
||||||
@@ -1,24 +1,109 @@
|
|||||||
const { app, BrowserWindow } = require('electron');
|
const { app, BrowserWindow, session, globalShortcut } = require('electron');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
let mainWindow;
|
let stateFilePath;
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
const loadWindowState = () => {
|
||||||
mainWindow = new BrowserWindow({
|
if (!stateFilePath) return {};
|
||||||
width: 800,
|
try {
|
||||||
height: 600,
|
const raw = fs.readFileSync(stateFilePath, 'utf8');
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
const { width, height, x, y } = parsed;
|
||||||
|
if (Number.isFinite(width) && Number.isFinite(height)) {
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
x: Number.isFinite(x) ? x : undefined,
|
||||||
|
y: Number.isFinite(y) ? y : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore malformed or missing state and fall back to defaults.
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveWindowState = (bounds) => {
|
||||||
|
if (!stateFilePath || !bounds) return;
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(stateFilePath), { recursive: true });
|
||||||
|
fs.writeFileSync(stateFilePath, JSON.stringify(bounds), 'utf8');
|
||||||
|
} catch (_) {
|
||||||
|
// Persisting state is best-effort; ignore write failures.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createWindow = () => {
|
||||||
|
let state = {};
|
||||||
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
|
||||||
|
if (focusedWindow) {
|
||||||
|
const bounds = focusedWindow.getBounds();
|
||||||
|
state = {
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
x: bounds.x + 30,
|
||||||
|
y: bounds.y + 30
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
state = loadWindowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowOptions = {
|
||||||
|
width: state.width || 800,
|
||||||
|
height: state.height || 600,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Number.isFinite(state.x) && Number.isFinite(state.y)) {
|
||||||
|
windowOptions.x = state.x;
|
||||||
|
windowOptions.y = state.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const win = new BrowserWindow(windowOptions);
|
||||||
|
win.setMenu(null);
|
||||||
|
win.loadURL('https://linear.app/login');
|
||||||
|
|
||||||
|
win.on('close', () => {
|
||||||
|
saveWindowState(win.getBounds());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
stateFilePath = path.join(app.getPath('userData'), 'window-state.json');
|
||||||
|
|
||||||
|
session.defaultSession.setPermissionRequestHandler((_, permission, callback, details) => {
|
||||||
|
const origin = details?.requestingUrl || '';
|
||||||
|
const isLinear = origin.startsWith('https://linear.app');
|
||||||
|
if (permission === 'notifications' && isLinear) {
|
||||||
|
return callback(true);
|
||||||
|
}
|
||||||
|
return callback(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.setMenu(null);
|
createWindow();
|
||||||
|
|
||||||
mainWindow.loadURL('https://linear.app/login');
|
app.on('browser-window-focus', () => {
|
||||||
|
globalShortcut.register('CommandOrControl+Shift+N', createWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('browser-window-blur', () => {
|
||||||
|
globalShortcut.unregister('CommandOrControl+Shift+N');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|||||||
+66
-4
@@ -1,4 +1,66 @@
|
|||||||
#/bin/bash
|
#!/usr/bin/env bash
|
||||||
wget -O ~/.linear.AppImage https://github.com/kleo-dev/linear-linux/releases/download/v0.2.0/linear-linux-0.2.0.AppImage
|
|
||||||
sudo chmod +x ~/.linear.AppImage
|
set -euo pipefail
|
||||||
sudo mv ~/.linear.AppImage /bin/linear
|
|
||||||
|
VERSION="${VERSION:-0.2.1}"
|
||||||
|
APPIMAGE_URL="${APPIMAGE_URL:-https://github.com/kleo-dev/linear-linux/releases/download/v${VERSION}/linear-linux-${VERSION}.AppImage}"
|
||||||
|
INSTALL_ROOT="${INSTALL_ROOT:-/opt}"
|
||||||
|
INSTALL_DIR="${INSTALL_ROOT}/linear-linux-${VERSION}"
|
||||||
|
WRAPPER_PATH="/usr/local/bin/linear"
|
||||||
|
DESKTOP_PATH="/usr/share/applications/linear.desktop"
|
||||||
|
APP_NAME="Linear"
|
||||||
|
APP_DIR="${INSTALL_DIR}/squashfs-root"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ICON_SVG="${SCRIPT_DIR}/assets/linear-icon.svg"
|
||||||
|
ICON_PNG="${SCRIPT_DIR}/assets/linear-app-icon.png"
|
||||||
|
|
||||||
|
if [[ ! -f "${ICON_SVG}" || ! -f "${ICON_PNG}" ]]; then
|
||||||
|
echo "Icon assets missing. Expected ${ICON_SVG} and ${ICON_PNG}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "${tmpdir}"' EXIT
|
||||||
|
|
||||||
|
echo "Downloading Linear AppImage ${VERSION}..."
|
||||||
|
curl -fsSL "${APPIMAGE_URL}" -o "${tmpdir}/linear.AppImage"
|
||||||
|
chmod +x "${tmpdir}/linear.AppImage"
|
||||||
|
|
||||||
|
echo "Installing to ${INSTALL_DIR}..."
|
||||||
|
sudo mkdir -p "${INSTALL_DIR}"
|
||||||
|
sudo cp "${tmpdir}/linear.AppImage" "${INSTALL_DIR}/linear.AppImage"
|
||||||
|
sudo chmod +x "${INSTALL_DIR}/linear.AppImage"
|
||||||
|
sudo bash -lc "cd \"${INSTALL_DIR}\" && ./linear.AppImage --appimage-extract >/dev/null"
|
||||||
|
sudo chmod -R a+rX "${APP_DIR}"
|
||||||
|
|
||||||
|
echo "Configuring sandbox helper..."
|
||||||
|
sudo chown root:root "${APP_DIR}/chrome-sandbox"
|
||||||
|
sudo chmod 4755 "${APP_DIR}/chrome-sandbox"
|
||||||
|
|
||||||
|
echo "Creating launch wrapper at ${WRAPPER_PATH}..."
|
||||||
|
sudo tee "${WRAPPER_PATH}" >/dev/null <<EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
export CHROME_DEVEL_SANDBOX="${APP_DIR}/chrome-sandbox"
|
||||||
|
exec "${APP_DIR}/AppRun" "\$@"
|
||||||
|
EOF
|
||||||
|
sudo chmod 755 "${WRAPPER_PATH}"
|
||||||
|
|
||||||
|
echo "Installing desktop entry..."
|
||||||
|
sudo tee "${DESKTOP_PATH}" >/dev/null <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=${APP_NAME}
|
||||||
|
Exec=${WRAPPER_PATH} %U
|
||||||
|
Icon=linear
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;
|
||||||
|
StartupWMClass=linear-linux
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Installing icons..."
|
||||||
|
sudo install -Dm644 "${ICON_SVG}" /usr/share/icons/hicolor/scalable/apps/linear.svg
|
||||||
|
sudo install -Dm644 "${ICON_PNG}" /usr/share/icons/hicolor/512x512/apps/linear.png
|
||||||
|
sudo gtk-update-icon-cache -f /usr/share/icons/hicolor || true
|
||||||
|
|
||||||
|
echo "Linear ${VERSION} installed. Launch with: ${WRAPPER_PATH}"
|
||||||
|
|||||||
Generated
+112
-812
File diff suppressed because it is too large
Load Diff
+28
-4
@@ -1,23 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "linear-linux",
|
"name": "linear-linux",
|
||||||
"version": "0.2.1",
|
"version": "0.2.3",
|
||||||
"description": "Linux support for linear.app",
|
"description": "Linux support for linear.app",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npx electron index.js",
|
"start": "CHROME_DEVEL_SANDBOX=${CHROME_DEVEL_SANDBOX:-/opt/linear-linux-0.2.1/squashfs-root/chrome-sandbox} ELECTRON_DISABLE_SANDBOX=1 npx electron index.js",
|
||||||
"build": "electron-builder --linux AppImage"
|
"build": "electron-builder --linux AppImage deb"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/kleo-dev/linear-linux.git"
|
"url": "git+https://github.com/kleo-dev/linear-linux.git"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "Linear Linux <[email protected]>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/kleo-dev/linear-linux/issues"
|
"url": "https://github.com/kleo-dev/linear-linux/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/kleo-dev/linear-linux#readme",
|
"homepage": "https://github.com/kleo-dev/linear-linux#readme",
|
||||||
|
"build": {
|
||||||
|
"appId": "app.linear.linux",
|
||||||
|
"productName": "Linear",
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"package.json",
|
||||||
|
"assets/**/*"
|
||||||
|
],
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"AppImage",
|
||||||
|
"deb"
|
||||||
|
],
|
||||||
|
"category": "Utility",
|
||||||
|
"icon": "assets/linear-app-icon.png",
|
||||||
|
"afterInstall": "build/after-install.sh"
|
||||||
|
},
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "assets",
|
||||||
|
"to": "assets"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^33.2.1",
|
"electron": "^33.2.1",
|
||||||
"electron-builder": "^25.1.8"
|
"electron-builder": "^25.1.8"
|
||||||
|
|||||||
Reference in New Issue
Block a user