license: relicense to AGPL-3.0-only with commercial dual-license

- LICENSE -> verbatim GNU AGPL-3.0; add LICENSE-COMMERCIAL.md + SECURITY.md
- CLA -> exclusive-license grant (contributors keep copyright); add README
  anti-rugpull covenant + relicense record
- NOTICE / README / CONTRIBUTING / CLAUDE.md / package.json x5 updated;
  contact routed through GitHub (no email placeholders)
- AGPL section 13 source offer: operator-configurable BACKSPACE_SOURCE_URL +
  build-injected commit; sourceCodeUrl+commit on /api/instance/info;
  SourceCodeLink on login/register/settings/desktop; docs + .env.example updated
This commit is contained in:
Jannis Braun
2026-07-01 16:38:22 +02:00
parent 16d75f2806
commit f481e1fe9e
34 changed files with 1084 additions and 165 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@backspace/desktop",
"version": "1.0.0",
"private": true,
"license": "Elastic-2.0",
"license": "AGPL-3.0-only",
"description": "Backspace",
"author": {
"name": "Jannis Braun",
+48
View File
@@ -69,6 +69,45 @@ let pendingDeepLink: string | null = null;
const knownInstanceOrigins = new Set<string>();
// ─── AGPL-3.0 § 13 source offer ─────────────────────────────────────────────
// Upstream fallback for the "Source code" menu items and the About panel.
// Used when the connected instance can't be reached or advertises no source URL.
const UPSTREAM_SOURCE_URL = 'https://github.com/TheZwiss/backspace';
/**
* Resolve the Corresponding Source URL for the instance the desktop app is
* pointed at, honouring an operator's modified fork via GET /api/instance/info.
* Falls back to the upstream repo when no instance is loaded or the probe fails.
*/
async function resolveSourceUrl(): Promise<string> {
let base: string | null = process.env.BACKSPACE_URL ?? loadInstanceUrl();
if (!base && mainWindow && !mainWindow.isDestroyed()) {
const current = mainWindow.webContents.getURL();
if (current.startsWith('http://') || current.startsWith('https://')) base = current;
}
if (!base) return UPSTREAM_SOURCE_URL;
try {
const origin = new URL(base).origin;
const res = await fetch(`${origin}/api/instance/info`, { signal: AbortSignal.timeout(5000) });
if (!res.ok) return UPSTREAM_SOURCE_URL;
const info = (await res.json()) as { sourceCodeUrl?: unknown };
if (typeof info.sourceCodeUrl === 'string' && /^https?:\/\//i.test(info.sourceCodeUrl)) {
return info.sourceCodeUrl;
}
} catch {
// Unreachable / malformed — fall back to upstream.
}
return UPSTREAM_SOURCE_URL;
}
/** Open the resolved source URL externally; upstream fallback on any failure. */
function openSourceCode(): void {
resolveSourceUrl()
.then((url) => shell.openExternal(url))
.catch(() => { void shell.openExternal(UPSTREAM_SOURCE_URL); });
}
// ─── Window State Persistence ───────────────────────────────────────────────
interface WindowState {
@@ -930,6 +969,14 @@ if (!gotTheLock) {
createWindow();
createTray();
// AGPL-3.0 § 13: native About panel advertises the version + source repo.
app.setAboutPanelOptions({
applicationName: 'Backspace',
applicationVersion: app.getVersion(),
copyright: `AGPL-3.0-only · Source: ${UPSTREAM_SOURCE_URL}`,
website: UPSTREAM_SOURCE_URL,
});
// Tray + macOS app-menu actions. Defined once so the subscriber and the
// initial-fire share one implementation (no drift on future menu changes).
const trayActions = {
@@ -941,6 +988,7 @@ if (!gotTheLock) {
onChangeInstance: () => handleRecoveryAction('change-instance'),
onCheckForUpdates: () => handleRecoveryAction('check-update'),
onRestartToInstall: () => handleRecoveryAction('install-update'),
onOpenSource: () => openSourceCode(),
onQuit: () => requestQuit(),
};
+4
View File
@@ -96,6 +96,8 @@ interface MenuActions {
onChangeInstance: () => void;
onCheckForUpdates: () => void;
onRestartToInstall: () => void;
// AGPL-3.0 § 13: open the Corresponding Source of the running instance.
onOpenSource: () => void;
onQuit: () => void;
}
@@ -138,6 +140,7 @@ export function buildTrayMenuTemplate(
items.push(
{ type: 'separator' },
{ label: 'Change Instance', click: actions?.onChangeInstance },
{ label: 'Source code (AGPL)', click: actions?.onOpenSource },
{ type: 'separator' },
{ label: 'Quit', click: actions?.onQuit },
);
@@ -152,6 +155,7 @@ export function buildAppMenuTemplate(
): MenuItemConstructorOptions[] {
const appSubmenu: MenuItemConstructorOptions[] = [
{ role: 'about' },
{ label: 'Source code (AGPL)', click: () => actions?.onOpenSource?.() },
{ type: 'separator' },
checkForUpdatesItem(state, () => actions?.onCheckForUpdates?.()),
];