Merge pull request #77 from Assem-ElQersh/main

feat: add Linux (Ubuntu) desktop build support
This commit is contained in:
Anil Chandra Naidu Matcha 2026-04-15 18:40:00 +05:30 committed by GitHub
commit 57cb24bcbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 239 additions and 113 deletions

14
.gitignore vendored
View file

@ -22,3 +22,17 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Electron build output
release/
.webpack/
# Next.js
.next/
out/
# Misc
*.pem
.env
.env.*
!.env.example

View file

@ -23,6 +23,7 @@ One-click installers — no Node.js or terminal required.
| macOS Apple Silicon (M1/M2/M3/M4) | [Open Generative AI-1.0.0-arm64.dmg](https://github.com/Anil-matcha/Open-Generative-AI/releases/download/v1.0.0/Open.Generative.AI-1.0.0-arm64.dmg) |
| macOS Intel (x64) | [Open Generative AI-1.0.0.dmg](https://github.com/Anil-matcha/Open-Generative-AI/releases/download/v1.0.0/Open.Generative.AI-1.0.0.dmg) |
| Windows (x64 + ARM64) | [Open Generative AI Setup 1.0.0.exe](https://github.com/Anil-matcha/Open-Generative-AI/releases/download/v1.0.0/Open.Generative.AI.Setup.1.0.0.exe) |
| Linux (Ubuntu x64) | Build locally with `npm run electron:build:linux` |
All releases: [github.com/Anil-matcha/Open-Generative-AI/releases](https://github.com/Anil-matcha/Open-Generative-AI/releases)
@ -56,6 +57,48 @@ Windows SmartScreen may show a warning because the installer is not code-signed:
The app will install silently to `%LocalAppData%` with a Start Menu shortcut.
### Ubuntu / Linux Installation
Linux artifacts are available when building with Electron Builder:
```bash
# Build Linux installers (AppImage + .deb)
npm run electron:build:linux
```
Generated files are written to the `release/` folder:
- **AppImage** — portable, run directly after making executable:
```bash
chmod +x "release/Open Generative AI-*.AppImage"
./release/Open\ Generative\ AI-*.AppImage
```
- **.deb** — install on Debian/Ubuntu:
```bash
sudo apt install ./release/open-generative-ai_*_amd64.deb
```
If AppImage fails to start on older systems, install `libfuse2`:
```bash
sudo apt install libfuse2
```
#### Ubuntu 24.04+ / AppArmor sandbox restriction
Ubuntu 24.04 and later enable a kernel security policy (`apparmor_restrict_unprivileged_userns`) that blocks Chromium's user-namespace sandbox. If the app fails to start silently or crashes immediately, you have two options:
**Option A — Recommended: install the `.deb` instead.**
The `.deb` package ships an AppArmor profile that grants the required permission automatically on install with no system-wide changes.
**Option B — Temporary system fix (AppImage users):**
```bash
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
```
This lasts until next reboot. To make it permanent:
```bash
echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/99-userns.conf
```
---
Open Generative AI is an open-source AI image, video, cinema, and lip sync studio that brings creative workflows to everyone. Powered by [Muapi.ai](https://muapi.ai), it supports text-to-image, image-to-image, text-to-video, image-to-video, and audio-driven lip sync generation across models like Flux, Nano Banana, Midjourney, Kling, Sora, Veo, Seedream, Infinite Talk, LTX Lipsync, Wan 2.2, and more — all from a sleek, modern interface you can self-host and customize.
@ -251,6 +294,9 @@ npm run electron:build
# Windows (NSIS installer — x64 + ARM64)
npm run electron:build:win
# Linux (AppImage + DEB — x64)
npm run electron:build:linux
# Both platforms in one pass
npm run electron:build:all
```

View file

@ -0,0 +1,7 @@
abi <abi/4.0>,
include <tunables/global>
profile open-generative-ai /opt/Open\ Generative\ AI/open-generative-ai flags=(unconfined) {
userns,
include if exists <local/open-generative-ai>
}

View file

@ -1,33 +1,46 @@
import { app, BrowserWindow, shell } from 'electron';
import { fileURLToPath } from 'url';
import path from 'path';
const { app, BrowserWindow, shell } = require('electron');
const path = require('path');
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Ubuntu 24.04+ sets kernel.apparmor_restrict_unprivileged_userns=1 which
// blocks Chromium's user namespace sandbox. The .deb package ships an AppArmor
// profile that grants the permission cleanly. When running the AppImage on an
// affected system, run once: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
// or pass --no-sandbox on the command line.
if (process.platform === 'linux') {
app.commandLine.appendSwitch('disable-dev-shm-usage');
}
let mainWindow;
function createWindow() {
const isMac = process.platform === 'darwin';
mainWindow = new BrowserWindow({
width: 1440,
height: 900,
minWidth: 1024,
minHeight: 640,
webPreferences: {
webSecurity: false, // Allow file:// origin to call external APIs
webSecurity: false,
contextIsolation: true,
nodeIntegration: false,
},
titleBarStyle: 'hiddenInset',
...(isMac ? { titleBarStyle: 'hiddenInset' } : {}),
backgroundColor: '#0d0d0d',
show: false,
title: 'Open Generative AI',
});
const indexPath = path.join(__dirname, '../dist/index.html');
mainWindow.loadFile(indexPath);
mainWindow.loadFile(indexPath).catch((err) => {
console.error('Failed to load index.html:', err);
mainWindow.show();
});
mainWindow.webContents.on('did-fail-load', (event, code, desc) => {
console.error('did-fail-load:', code, desc);
});
// Open external links in the system browser
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };

163
package-lock.json generated
View file

@ -27,7 +27,7 @@
"eslint": "^9",
"eslint-config-next": "^15.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.0",
"tailwindcss": "^4.2.2",
"vite": "^5.4.0"
}
},
@ -36,6 +36,7 @@
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
@ -10062,7 +10063,8 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
"version": "6.0.0",
@ -12778,82 +12780,11 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.6.0",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.21.7",
"lilconfig": "^3.1.3",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.1.1",
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
"postcss-nested": "^6.2.0",
"postcss-selector-parser": "^6.1.2",
"resolve": "^1.22.8",
"sucrase": "^3.35.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss/node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/tailwindcss/node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
}
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@ -13791,6 +13722,84 @@
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"packages/studio/node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"packages/studio/node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"packages/studio/node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
}
},
"packages/studio/node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.6.0",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.21.7",
"lilconfig": "^3.1.3",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.1.1",
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
"postcss-nested": "^6.2.0",
"postcss-selector-parser": "^6.1.2",
"resolve": "^1.22.8",
"sucrase": "^3.35.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
}
}
}

View file

@ -17,7 +17,8 @@
"vite:build": "vite build",
"electron:build": "vite build && electron-builder --mac",
"electron:build:win": "vite build && electron-builder --win",
"electron:build:all": "vite build && electron-builder --mac --win"
"electron:build:linux": "vite build && electron-builder --linux",
"electron:build:all": "vite build && electron-builder --mac --win --linux"
},
"build": {
"appId": "ai.generative.open",
@ -56,27 +57,52 @@
]
}
]
},
"linux": {
"icon": "public/banner.png",
"category": "Utility",
"maintainer": "Open Generative AI Team",
"extraFiles": [
{
"from": "build/linux/apparmor.profile",
"to": "resources/apparmor.profile"
}
],
"target": [
{
"target": "AppImage",
"arch": [
"x64"
]
},
{
"target": "deb",
"arch": [
"x64"
]
}
]
}
},
"dependencies": {
"axios": "^1.7.0",
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"studio": "*",
"axios": "^1.7.0",
"react-hot-toast": "^2.4.1"
"react-hot-toast": "^2.4.1",
"studio": "*"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/vite": "^4.1.18",
"autoprefixer": "^10.4.24",
"electron": "^33.4.11",
"electron-builder": "^25.1.8",
"eslint": "^9",
"eslint-config-next": "^15.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.0",
"vite": "^5.4.0",
"@tailwindcss/vite": "^4.1.18"
"tailwindcss": "^4.2.2",
"vite": "^5.4.0"
},
"main": "electron/main.js"
}

View file

@ -3,30 +3,30 @@ import { CAMERA_MAP, LENS_MAP, FOCAL_PERSPECTIVE, APERTURE_EFFECT } from '../lib
const ASSET_URLS = {
// CAMERA
"Modular 8K Digital": "/assets/cinema/modular_8k_digital.webp",
"Full-Frame Cine Digital": "/assets/cinema/full_frame_cine_digital.webp",
"Grand Format 70mm Film": "/assets/cinema/grand_format_70mm_film.webp",
"Studio Digital S35": "/assets/cinema/studio_digital_s35.webp",
"Classic 16mm Film": "/assets/cinema/classic_16mm_film.webp",
"Premium Large Format Digital": "/assets/cinema/premium_large_format_digital.webp",
"Modular 8K Digital": "./assets/cinema/modular_8k_digital.webp",
"Full-Frame Cine Digital": "./assets/cinema/full_frame_cine_digital.webp",
"Grand Format 70mm Film": "./assets/cinema/grand_format_70mm_film.webp",
"Studio Digital S35": "./assets/cinema/studio_digital_s35.webp",
"Classic 16mm Film": "./assets/cinema/classic_16mm_film.webp",
"Premium Large Format Digital": "./assets/cinema/premium_large_format_digital.webp",
// LENS
"Creative Tilt Lens": "/assets/cinema/creative_tilt_lens.webp",
"Compact Anamorphic": "/assets/cinema/compact_anamorphic.webp",
"Extreme Macro": "/assets/cinema/extreme_macro.webp",
"70s Cinema Prime": "/assets/cinema/70s_cinema_prime.webp",
"Classic Anamorphic": "/assets/cinema/classic_anamorphic.webp",
"Premium Modern Prime": "/assets/cinema/premium_modern_prime.webp",
"Warm Cinema Prime": "/assets/cinema/warm_cinema_prime.webp",
"Swirl Bokeh Portrait": "/assets/cinema/swirl_bokeh_portrait.webp",
"Vintage Prime": "/assets/cinema/vintage_prime.webp",
"Halation Diffusion": "/assets/cinema/halation_diffusion.webp",
"Clinical Sharp Prime": "/assets/cinema/clinical_sharp_prime.webp",
"Creative Tilt Lens": "./assets/cinema/creative_tilt_lens.webp",
"Compact Anamorphic": "./assets/cinema/compact_anamorphic.webp",
"Extreme Macro": "./assets/cinema/extreme_macro.webp",
"70s Cinema Prime": "./assets/cinema/70s_cinema_prime.webp",
"Classic Anamorphic": "./assets/cinema/classic_anamorphic.webp",
"Premium Modern Prime": "./assets/cinema/premium_modern_prime.webp",
"Warm Cinema Prime": "./assets/cinema/warm_cinema_prime.webp",
"Swirl Bokeh Portrait": "./assets/cinema/swirl_bokeh_portrait.webp",
"Vintage Prime": "./assets/cinema/vintage_prime.webp",
"Halation Diffusion": "./assets/cinema/halation_diffusion.webp",
"Clinical Sharp Prime": "./assets/cinema/clinical_sharp_prime.webp",
// APERTURE
"f/1.4": "/assets/cinema/f_1_4.webp",
"f/4": "/assets/cinema/f_4.webp",
"f/11": "/assets/cinema/f_11.webp"
"f/1.4": "./assets/cinema/f_1_4.webp",
"f/4": "./assets/cinema/f_4.webp",
"f/11": "./assets/cinema/f_11.webp"
};
export function CameraControls(onChange) {

View file

@ -9,6 +9,17 @@ import { AuthModal } from './AuthModal.js';
import { createUploadPicker } from './UploadPicker.js';
import { savePendingJob, removePendingJob, getPendingJobs } from '../lib/pendingJobs.js';
function createInlineInstructions(type) {
const el = document.createElement('div');
el.className = 'w-full text-center text-white/30 text-sm flex flex-col items-center gap-2 py-2';
const icon = type === 'image' ? '🖼️' : '🎬';
el.innerHTML = `
<p>${icon} Enter a prompt above and click <span class="text-primary font-semibold">Generate</span> to create your ${type}.</p>
<p class="text-xs text-white/20">Tip: Be descriptive include style, lighting, mood, and subject for best results.</p>
`;
return el;
}
export function ImageStudio() {
const container = document.createElement('div');
container.className = 'w-full h-full flex flex-col items-center justify-center bg-app-bg relative p-4 md:p-6 overflow-y-auto custom-scrollbar overflow-x-hidden';