<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged electron</title>
<link>https://coderlegion.com/tag/electron</link>
<description>Powered by Question2Answer</description>
<item>
<title>Build an Electron app with local data storage — no SQLite bindings, no server</title>
<link>https://coderlegion.com/24329/build-an-electron-app-with-local-data-storage-no-sqlite-bindings-no-server</link>
<description>&lt;p&gt;If you've shipped an Electron app that persists structured data, you've probably hit the wall at some point. You need something more than a flat JSON file, but a full database server is absurd for a desktop app. So you reach for better-sqlite3 — and then spend the next hour fighting native bindings.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-electron-storage-problem&quot;&gt;The Electron storage problem&lt;/h3&gt;
&lt;p&gt;Electron bundles its own version of Node.js. That version almost never matches the one on your machine. Native Node modules — the ones that compile C++ bindings — need to be recompiled specifically for the Electron runtime, using &lt;code&gt;electron-rebuild&lt;/code&gt; or equivalent tooling.&lt;/p&gt;
&lt;p&gt;In practice this means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every Electron update potentially breaks your native dependencies&lt;/li&gt;
&lt;li&gt;CI pipelines need platform-specific build steps for Windows, macOS and Linux&lt;/li&gt;
&lt;li&gt;Code signing and notarization on macOS gets complicated by native binaries&lt;/li&gt;
&lt;li&gt;End users on some machines encounter install failures you can't reproduce locally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;better-sqlite3 is an excellent library. But it is a native module, and native modules in Electron are a tax you pay forever.&lt;/p&gt;
&lt;p&gt;The alternatives people reach for have their own problems. lowdb and lokijs keep everything in memory and flush to disk — fine for small datasets, but you lose durability on crash and memory usage grows with your data. nedb hasn't been maintained in years. A remote database is obviously wrong for a local desktop app.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-you-actually-need&quot;&gt;What you actually need&lt;/h3&gt;
&lt;p&gt;For most Electron apps, the storage requirements are modest but specific:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Structured documents, not just key-value pairs&lt;/li&gt;
&lt;li&gt;Queries that go beyond &quot;give me everything&quot;&lt;/li&gt;
&lt;li&gt;Writes that survive a crash&lt;/li&gt;
&lt;li&gt;Zero native binaries — pure JavaScript, works everywhere Electron runs&lt;/li&gt;
&lt;li&gt;No server process running alongside your app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's exactly the gap pocket-db is designed to fill. It's a single-file embedded document store — the SQLite model applied to JSON documents, with a MongoDB-style API. Pure TypeScript, shipped as both ESM and CommonJS. No native bindings. No optional dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;building-the-example-app&quot;&gt;Building the example app&lt;/h3&gt;
&lt;p&gt;Let's build a small note-taking app: create, list, search and delete notes. Simple enough to follow in full, realistic enough to show the pattern.&lt;/p&gt;
&lt;h4 id=&quot;setup&quot;&gt;Setup&lt;/h4&gt;
&lt;p&gt;We'll use &lt;a rel=&quot;nofollow&quot; href=&quot;https://electron-vite.org&quot;&gt;electron-vite&lt;/a&gt;, which scaffolds the &lt;code&gt;src/main&lt;/code&gt; / &lt;code&gt;src/preload&lt;/code&gt; / &lt;code&gt;src/renderer&lt;/code&gt; layout used below, with TypeScript and React ready to go:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm create @quick-start/electron@latest my-notes -- --template react-ts
cd my-notes
npm install @axfab/pocket-db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;electron-rebuild&lt;/code&gt;. No postinstall scripts. That's the point.&lt;/p&gt;
&lt;h4 id=&quot;initialising-the-database&quot;&gt;Initialising the database&lt;/h4&gt;
&lt;p&gt;In Electron, database access belongs in the main process. You have access to the filesystem there, and it keeps your renderer process clean. Use &lt;code&gt;app.getPath('userData')&lt;/code&gt; — Electron's standard location for app data, persisted across updates.&lt;/p&gt;
&lt;p&gt;One thing to know: Electron creates the &lt;code&gt;userData&lt;/code&gt; directory lazily, so on a first launch it may not exist yet — and &lt;code&gt;open()&lt;/code&gt; does not create parent directories. Create it before opening:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/main/db.ts
import { app } from 'electron'
import { open } from '@axfab/pocket-db'
import { mkdirSync } from 'node:fs'
import path from 'node:path'

const dataDir = app.getPath('userData')
mkdirSync(dataDir, { recursive: true }) // may not exist on first launch

export const db = open({ path: path.join(dataDir, 'notes.pdb') })
export const notes = db.collection('notes')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One file, one call. The database is created if it doesn't exist. No migrations, no schema definition.&lt;/p&gt;
&lt;h4 id=&quot;exposing-database-operations-via-ipc&quot;&gt;Exposing database operations via IPC&lt;/h4&gt;
&lt;p&gt;Electron's security model means the renderer can't touch Node APIs directly. You expose operations through IPC handlers in the main process and call them via &lt;code&gt;contextBridge&lt;/code&gt; in the preload script.&lt;/p&gt;
&lt;p&gt;Search deserves a moment of care: the search term comes from user input, and &lt;code&gt;$regex&lt;/code&gt; compiles it as a regular expression. A term containing &lt;code&gt;(&lt;/code&gt; or &lt;code&gt;[&lt;/code&gt; would throw, and a raw pattern is case-sensitive. Escape the input and pass &lt;code&gt;$options: 'i'&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/main/index.ts
import { app, BrowserWindow, ipcMain } from 'electron'
import { db, notes } from './db'

/** Escapes regex metacharacters so user input is matched literally. */
const escapeRegex = (term: string) =&amp;gt;
  term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&amp;amp;')

ipcMain.handle('notes:create', (_event, title: string, body: string) =&amp;gt; {
  return notes.insertOne({ title, body, createdAt: Date.now() })
})

ipcMain.handle('notes:list', () =&amp;gt; {
  return notes.find().sort({ createdAt: -1 }).toArray()
})

ipcMain.handle('notes:search', (_event, term: string) =&amp;gt; {
  const pattern = escapeRegex(term)
  return notes.find({
    $or: [
      { title: { $regex: pattern, $options: 'i' } },
      { body:  { $regex: pattern, $options: 'i' } }
    ]
  }).toArray()
})

ipcMain.handle('notes:delete', (_event, id: string) =&amp;gt; {
  return notes.deleteOne(id)
})

app.on('before-quit', () =&amp;gt; db.close())
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/preload/index.ts
// Note: keep the preload compiled to CommonJS (electron-vite's default) —
// sandboxed renderers don't load ESM preload scripts.
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('notes', {
  create:  (title: string, body: string) =&amp;gt; ipcRenderer.invoke('notes:create', title, body),
  list:    ()           =&amp;gt; ipcRenderer.invoke('notes:list'),
  search:  (term: string) =&amp;gt; ipcRenderer.invoke('notes:search', term),
  delete:  (id: string) =&amp;gt; ipcRenderer.invoke('notes:delete', id),
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;using-it-in-the-renderer&quot;&gt;Using it in the renderer&lt;/h4&gt;
&lt;p&gt;From the renderer, the API is just async function calls:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/renderer/src/App.tsx
const handleCreate = async () =&amp;gt; {
  await window.notes.create(title, body)
  setNotes(await window.notes.list())
}

const handleSearch = async (term: string) =&amp;gt; {
  const results = await window.notes.search(term)
  setNotes(results)
}

const handleDelete = async (id: string) =&amp;gt; {
  await window.notes.delete(id)
  setNotes(await window.notes.list())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No database logic in the renderer. No connection strings. No async initialisation to wait for. The main process owns the database, the renderer just calls handlers.&lt;/p&gt;
&lt;h4 id=&quot;where-indexes-help-and-where-they-don-t&quot;&gt;Where indexes help — and where they don't&lt;/h4&gt;
&lt;p&gt;Two things to know about pocket-db's query planner in V1: &lt;code&gt;$regex&lt;/code&gt; is never index-assisted (it's always evaluated against the documents), and &lt;code&gt;$or&lt;/code&gt; queries bypass index planning entirely. So the regex search above is a full collection scan — perfectly fine for a notes app, but don't expect an index to speed it up.&lt;/p&gt;
&lt;p&gt;Indexes shine on equality and range lookups. Say notes belong to notebooks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// run once at startup, safe to call repeatedly
notes.createIndex('notebook', { type: 'string' })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a query like &lt;code&gt;notes.find({ notebook: 'work' })&lt;/code&gt; reads only the matching documents instead of scanning the collection — the planner picks the index up automatically, no change needed in your query code. The same goes for a number index on &lt;code&gt;createdAt&lt;/code&gt; if you query by date range.&lt;/p&gt;
&lt;h4 id=&quot;periodic-compaction&quot;&gt;Periodic compaction&lt;/h4&gt;
&lt;p&gt;The append-only log accumulates dead records as notes are edited and deleted. Schedule compaction when the app is idle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;app.on('browser-window-blur', () =&amp;gt; {
  db.compact()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;compact()&lt;/code&gt; is fast and synchronous — it rewrites the file in a single forward pass. Calling it when the window loses focus means it runs during natural pauses without affecting responsiveness.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;packaging-and-distribution&quot;&gt;Packaging and distribution&lt;/h3&gt;
&lt;p&gt;This is where the native module problem usually surfaces. With pocket-db, there's nothing to rebuild. The electron-vite scaffold already wires up electron-builder; its config just works as-is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# electron-builder.yml (generated by the scaffold)
mac:
  target: dmg
win:
  target: nsis
linux:
  target: AppImage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;electron-rebuild&lt;/code&gt; in your build pipeline. No platform-specific post-install hooks. The same config builds cleanly on all three platforms.&lt;/p&gt;
&lt;p&gt;If you're using GitHub Actions to build releases:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .github/workflows/release.yml
- name: Build
  run: npm run build &amp;amp;&amp;amp; npx electron-builder
  # No electron-rebuild step needed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One fewer thing to maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-about-data-migration-&quot;&gt;What about data migration?&lt;/h3&gt;
&lt;p&gt;pocket-db is schemaless — documents in the same collection can have different shapes. That means additive changes (adding a new field) require nothing: new documents have the field, old ones don't, and &lt;code&gt;$exists&lt;/code&gt; lets you query for either.&lt;/p&gt;
&lt;p&gt;For breaking changes, a one-time migration on startup is straightforward. Keep a version marker in a metadata collection — and remember that on a fresh database the marker doesn't exist yet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const schema = db.collection('_meta')
const marker = schema.findOne({ key: 'version' })
const version = typeof marker?.value === 'number' ? marker.value : 0

if (version &amp;lt; 2) {
  // run migration
  notes.updateMany({}, { $set: { archived: false } })

  if (marker) {
    schema.replaceOne(marker._id as string, { key: 'version', value: 2 })
  } else {
    schema.insertOne({ key: 'version', value: 2 })
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;replaceOne(marker._id, …)&lt;/code&gt; call: the first argument is the document &lt;strong&gt;id&lt;/strong&gt;. (Passing a full document as the only argument is a separate overload that replaces that document by itself — not what you want here.)&lt;/p&gt;
&lt;p&gt;No migration framework needed for most desktop app scenarios.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-trade-offs-to-know&quot;&gt;The trade-offs to know&lt;/h3&gt;
&lt;p&gt;pocket-db is designed for single-process use. An Electron app fits that model perfectly — the main process is the only writer, and that's exactly the intended use case.&lt;/p&gt;
&lt;p&gt;It is not the right choice if your Electron app spawns multiple Node worker processes that all need to write to the same database simultaneously. It is also not the right choice if you're storing hundreds of thousands of documents and need complex aggregation — at that scale, an actual database server starts making sense even in a desktop context.&lt;/p&gt;
&lt;p&gt;For the common case — a desktop app storing user data, preferences, history, cached API responses, local task lists — it fits well.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-started&quot;&gt;Getting started&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @axfab/pocket-db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full documentation at &lt;a rel=&quot;nofollow&quot; href=&quot;https://pocket-db.axfab.net&quot;&gt;pocket-db.axfab.net&lt;/a&gt;. Source and issues at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/axfab/pocket-db&quot;&gt;github.com/axfab/pocket-db&lt;/a&gt;. The complete example app from this article is in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/AxFab/pocket-example/tree/develop/01_electron&quot;&gt;repository of exemple&lt;/a&gt;, tests included.&lt;/p&gt;
&lt;p&gt;If you've been putting up with native binding headaches in Electron, give it a try and let me know how it goes.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;pocket-db is MIT licensed.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24329/build-an-electron-app-with-local-data-storage-no-sqlite-bindings-no-server</guid>
<pubDate>Sun, 09 Aug 2026 10:38:03 +0000</pubDate>
</item>
<item>
<title>I Ported ComfyUI Desktop to Ubuntu 26.04  1 a day till I run out!</title>
<link>https://coderlegion.com/14844/i-ported-comfyui-desktop-to-ubuntu-26-04-1-a-day-till-i-run-out</link>
<description>&lt;hr&gt;
&lt;h3 id=&quot;and-another-one-&quot;&gt;And Another One!&lt;/h3&gt;
&lt;hr&gt;
&lt;h3 id=&quot;another-desktop-app-ported-to-linux-that-was-not-available-i-will-keep-going-until-release-day-or-i-run-out-of-apps-to-port-let-me-know-what-one-you-would-like-to-see-next-&quot;&gt;Another Desktop App ported to Linux That was not available. I will Keep going until release day or I run out of apps to port. Let me Know what one you would like to see next!&lt;/h3&gt;
&lt;p&gt;The official ComfyUI Desktop app ships for Windows and macOS.&lt;/p&gt;
&lt;p&gt;I wanted it on Ubuntu 26.04.&lt;/p&gt;
&lt;p&gt;So I ported it.&lt;/p&gt;
&lt;p&gt;Not a web wrapper. Not a fake launcher. Not a shell script around a browser tab.&lt;/p&gt;
&lt;p&gt;The actual desktop app.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-goal&quot;&gt;The Goal&lt;/h3&gt;
&lt;p&gt;I had two requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;build the real app on Linux&lt;/li&gt;
&lt;li&gt;make it usable on a machine that already runs ComfyUI normally&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That second one mattered more than people think.&lt;/p&gt;
&lt;p&gt;If a Linux user already has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a real &lt;code&gt;~/ComfyUI&lt;/code&gt; install&lt;/li&gt;
&lt;li&gt;a running server on &lt;code&gt;127.0.0.1:8188&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;then the desktop app should not pretend the machine is blank and try to push a fresh install into &lt;code&gt;~/Documents/ComfyUI&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That is bad product behavior, even if the package technically launches.&lt;/p&gt;
&lt;h3 id=&quot;the-starting-point&quot;&gt;The Starting Point&lt;/h3&gt;
&lt;p&gt;The upstream repo is here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Comfy-Org/desktop&quot;&gt;https://github.com/Comfy-Org/desktop&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At first glance it looks cross-platform enough:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Electron app&lt;/li&gt;
&lt;li&gt;bundled frontend assets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv&lt;/code&gt; bootstrap&lt;/li&gt;
&lt;li&gt;asset build pipeline&lt;/li&gt;
&lt;li&gt;electron-builder config with Linux bits already half-present&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But “half-present” is the dangerous state.&lt;/p&gt;
&lt;p&gt;That usually means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;some paths exist&lt;/li&gt;
&lt;li&gt;some assets exist&lt;/li&gt;
&lt;li&gt;some packaging config exists&lt;/li&gt;
&lt;li&gt;and the app still does not actually ship on Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which is exactly what was going on here.&lt;/p&gt;
&lt;h3 id=&quot;the-real-problems&quot;&gt;The Real Problems&lt;/h3&gt;
&lt;p&gt;There were a few distinct failures.&lt;/p&gt;
&lt;h4 id=&quot;1-linux-was-gated-out-of-asset-bootstrap-flow&quot;&gt;1. Linux was gated out of asset/bootstrap flow&lt;/h4&gt;
&lt;p&gt;The app would build fine for supported platforms, but Linux was not being carried all the way through asset setup and build verification.&lt;/p&gt;
&lt;p&gt;That meant some Linux artifacts were never properly prepared even though the source tree looked close.&lt;/p&gt;
&lt;h4 id=&quot;2-packaged-builds-were-missing-desktop-ui&quot;&gt;2. Packaged builds were missing &lt;code&gt;desktop-ui&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This was the first hard packaging bug.&lt;/p&gt;
&lt;p&gt;The app launched, then died with a file-not-found error because the packaged build did not contain the desktop onboarding UI bundle.&lt;/p&gt;
&lt;p&gt;That is the kind of bug that makes a port look dead before you even get to runtime behavior.&lt;/p&gt;
&lt;h4 id=&quot;3-appimage-hit-electron-sandbox-issues&quot;&gt;3. AppImage hit Electron sandbox issues&lt;/h4&gt;
&lt;p&gt;This is the standard Linux Electron tax.&lt;/p&gt;
&lt;p&gt;The AppImage mounted fine, but Chromium tripped over the sandbox helper and aborted before the app could become useful.&lt;/p&gt;
&lt;p&gt;I did not waste time pretending AppImage was the right hill to die on.&lt;/p&gt;
&lt;p&gt;For Ubuntu, &lt;code&gt;.deb&lt;/code&gt; was the better target.&lt;/p&gt;
&lt;h4 id=&quot;4-linux-runtime-assumptions-were-wrong&quot;&gt;4. Linux runtime assumptions were wrong&lt;/h4&gt;
&lt;p&gt;The app still had platform checks and install behavior that were effectively “Windows/macOS first, Linux later.”&lt;/p&gt;
&lt;p&gt;A Linux port cannot just compile. It has to stop acting like Linux is unsupported at runtime.&lt;/p&gt;
&lt;h4 id=&quot;5-existing-comfy-installs-were-ignored&quot;&gt;5. Existing Comfy installs were ignored&lt;/h4&gt;
&lt;p&gt;This was the most important UX issue.&lt;/p&gt;
&lt;p&gt;On my machine, ComfyUI was already installed and already running in the browser.&lt;/p&gt;
&lt;p&gt;The desktop app still tried to start from scratch and push a managed install path into &lt;code&gt;Documents&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That is technically launchable, but functionally wrong.&lt;/p&gt;
&lt;p&gt;So I fixed it.&lt;/p&gt;
&lt;h3 id=&quot;what-i-changed&quot;&gt;What I Changed&lt;/h3&gt;
&lt;p&gt;I split the work into build fixes, runtime fixes, and “make it behave like a sane Linux app” fixes.&lt;/p&gt;
&lt;h4 id=&quot;build-and-packaging-fixes&quot;&gt;Build and packaging fixes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;enabled Linux asset bootstrap&lt;/li&gt;
&lt;li&gt;enabled Linux build verification&lt;/li&gt;
&lt;li&gt;packaged the missing &lt;code&gt;desktop-ui&lt;/code&gt; bundle&lt;/li&gt;
&lt;li&gt;switched the Linux target to &lt;code&gt;.deb&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;runtime-fixes&quot;&gt;Runtime fixes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;added Linux config/reset path handling&lt;/li&gt;
&lt;li&gt;made Linux hardware validation non-fatal&lt;/li&gt;
&lt;li&gt;added fallback behavior for missing compiled requirements&lt;/li&gt;
&lt;li&gt;added Linux-safe Electron startup defaults&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;existing-install-behavior&quot;&gt;Existing-install behavior&lt;/h4&gt;
&lt;p&gt;This was the good part.&lt;/p&gt;
&lt;p&gt;I changed the app so it now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prefers an existing &lt;code&gt;~/ComfyUI&lt;/code&gt; directory as the default base path&lt;/li&gt;
&lt;li&gt;detects a live local Comfy server on &lt;code&gt;127.0.0.1:8188&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;persists external-server mode into desktop config&lt;/li&gt;
&lt;li&gt;skips the desktop-managed venv validation path when attaching to that external server&lt;/li&gt;
&lt;li&gt;opens the frontend against the already-running instance instead of forcing first-run install flow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last bit is what makes the port feel real instead of merely “bootable.”&lt;/p&gt;
&lt;h3 id=&quot;why-deb-won&quot;&gt;Why &lt;code&gt;.deb&lt;/code&gt; Won&lt;/h3&gt;
&lt;p&gt;I know someone will ask why I did not just ship AppImage.&lt;/p&gt;
&lt;p&gt;Because Ubuntu users need something that works, not something ideologically pure.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.deb&lt;/code&gt; route gave me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stable installation under &lt;code&gt;/opt/ComfyUI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;normal desktop integration&lt;/li&gt;
&lt;li&gt;no AppImage sandbox headache&lt;/li&gt;
&lt;li&gt;a clean launcher path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On Linux, packaging format is not theology. It is operational reality.&lt;/p&gt;
&lt;h3 id=&quot;what-the-app-does-now&quot;&gt;What the App Does Now&lt;/h3&gt;
&lt;p&gt;On an Ubuntu 26.04 machine with an existing Comfy setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;launch the desktop app&lt;/li&gt;
&lt;li&gt;it detects &lt;code&gt;~/ComfyUI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it sees the live server on &lt;code&gt;127.0.0.1:8188&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it stores that as the desktop install state&lt;/li&gt;
&lt;li&gt;it attaches instead of onboarding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is the behavior I wanted from the start.&lt;/p&gt;
&lt;h3 id=&quot;commands-i-actually-used&quot;&gt;Commands I Actually Used&lt;/h3&gt;
&lt;p&gt;Install dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;corepack enable
corepack yarn install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bootstrap app assets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;corepack yarn make:assets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;corepack yarn typecheck
corepack yarn vitest run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build the package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;corepack yarn make
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install ./dist/ComfyUI-0.8.30-amd64.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-nice-surprise&quot;&gt;The Nice Surprise&lt;/h3&gt;
&lt;p&gt;The upstream repo was not hopeless.&lt;/p&gt;
&lt;p&gt;This was not one of those situations where “Linux support” secretly meant rewriting the app from scratch.&lt;/p&gt;
&lt;p&gt;Most of the work was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;finishing incomplete Linux paths&lt;/li&gt;
&lt;li&gt;fixing packaging omissions&lt;/li&gt;
&lt;li&gt;picking the right distribution target&lt;/li&gt;
&lt;li&gt;making startup logic respect an existing local Comfy workflow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is still real work, but it is not fantasy.&lt;/p&gt;
&lt;h3 id=&quot;the-useful-lesson&quot;&gt;The Useful Lesson&lt;/h3&gt;
&lt;p&gt;A port is not finished when the window appears.&lt;/p&gt;
&lt;p&gt;A port is finished when the app behaves correctly on the target machine.&lt;/p&gt;
&lt;p&gt;For this one, the difference was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;not just “ComfyUI Desktop launches on Ubuntu”&lt;/li&gt;
&lt;li&gt;but “ComfyUI Desktop understands an Ubuntu user who already runs ComfyUI”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is the difference between a demo and a port.&lt;/p&gt;
&lt;h3 id=&quot;upstream-and-source&quot;&gt;Upstream and Source&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Upstream desktop app: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Comfy-Org/desktop&quot;&gt;https://github.com/Comfy-Org/desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ComfyUI core: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/comfyanonymous/ComfyUI&quot;&gt;https://github.com/comfyanonymous/ComfyUI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;my-repo&quot;&gt;My Repo&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/johnohhh1/comfyui-desktop-port-linux.git&quot;&gt;https://github.com/johnohhh1/comfyui-desktop-port-linux.git&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/johnohhh1/comfyui-desktop-port-linux.git

&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14844/i-ported-comfyui-desktop-to-ubuntu-26-04-1-a-day-till-i-run-out</guid>
<pubDate>Wed, 15 Apr 2026 03:17:57 +0000</pubDate>
</item>
<item>
<title>Cross-Platform Desktop Wars: Electron vs Tauri: How do you explain the tradeoffs to users?</title>
<link>https://coderlegion.com/14746/cross-platform-desktop-wars-electron-vs-tauri-how-do-you-explain-the-tradeoffs-to-users</link>
<description>&lt;p&gt;Hello,&lt;/p&gt;
&lt;p&gt;I am writing cause I wanted to get some opinions from folks here that have actually built and shipped with Electron (or Tauri).&lt;/p&gt;
&lt;p&gt;Background: Building an API IDE on Electron. Not really “just an API client”, and not a(nother) thin wrapper around a webapp either. It’s a pretty original desktop tool with a lot of editor/IDE-like behavior - not the typical form centric behavior that postman or others have: local workflows, richer interactions, and some things that honestly would have been much harder for us to build and iterate on this quickly in a more constrained setup. Thats why Electron.&lt;/p&gt;
&lt;p&gt;this is the tool: &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/VoidenHQ/voiden&quot;&gt;https://github.com/VoidenHQ/voiden&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, as adoption is growing, we are starting to get the usual questions about memory footprint and app size.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn3k9wmn5jk47qrfe1wy.webp&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The (slightly) frustrating part is this:&lt;/p&gt;
&lt;p&gt;When the app is actually being used, the app-side memory is often pretty reasonable. In many normal cases we are seeing something like 50–60 MB for the actual usage we care about (even added it in the app itself for people to check it out).&lt;/p&gt;
&lt;p&gt;But then people open Activity Monitor, see all the Chromium/Electron-related processes, and the conversation immediately becomes:&lt;/p&gt;
&lt;p&gt;“yeah but Tauri would use way less”&lt;/p&gt;
&lt;p&gt;And then, without realizing, I suddenly end up talking and philosophizing about Electron, instead of discussing the tool itself (which is what I am passionate about :)&lt;/p&gt;
&lt;p&gt;And of course, I get it. The broader footprint is real. Chromium is not free. Electron has overhead. Pretending otherwise would be foolish. So we are constantly optimizing what we can, and we will keep doing so…&lt;/p&gt;
&lt;p&gt;At the same time, I do feel that a lot of these comparisons feel weirdly flattened. For example people often compare:&lt;/p&gt;
&lt;p&gt;full Electron process footprint VS the smallest possible Tauri/native mental model&lt;/p&gt;
&lt;p&gt;…without always accounting for development speed, cross-platform consistency, ecosystem maturity, plugin/runtime complexity, UI flexibility, and the fact that some apps are doing much more than others. Which is by the way the reason that we went with Electron.&lt;/p&gt;
&lt;p&gt;So all this context to get to my real question, which is:&lt;/p&gt;
&lt;p&gt;How do you explain this tradeoff to users in a way that feels honest and understandable, without sounding like you are making excuses for Electron?&lt;br&gt;
And also, for those of you who have had this conversation a hundred times already:&lt;/p&gt;
&lt;p&gt;What do you say when people reduce the whole discussion to “Electron bad, Tauri good”?&lt;/p&gt;
&lt;p&gt;Have you found a good way to explain footprint in practical terms?&lt;/p&gt;
&lt;p&gt;Where do you think optimization actually matters, vs where people are mostly reacting to the idea of Electron?&lt;/p&gt;
&lt;p&gt;Mostly trying to learn how others think about this , especially those who have built more serious desktop products and had to answer these questions in the wild.&lt;/p&gt;
&lt;p&gt;Would love your thoughts and advice!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14746/cross-platform-desktop-wars-electron-vs-tauri-how-do-you-explain-the-tradeoffs-to-users</guid>
<pubDate>Tue, 14 Apr 2026 07:12:44 +0000</pubDate>
</item>
<item>
<title>Run the Real ChatGPT Desktop App on Ubuntu Linux (Not a Wrapper)</title>
<link>https://coderlegion.com/14372/run-the-real-chatgpt-desktop-app-on-ubuntu-linux-not-a-wrapper</link>
<description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; OpenAI ships a Windows MSIX binary. You can unpack it, patch three platform assumptions, and run the actual official app natively on Ubuntu — same binary Windows users get. No Electron wrapper, no web view in a box.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-every-other-guide&quot;&gt;The Problem With Every Other Guide&lt;/h3&gt;
&lt;p&gt;Search &quot;ChatGPT desktop Ubuntu&quot; and you'll find two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Articles pointing you at &lt;code&gt;lencx/ChatGPT&lt;/code&gt; — a third-party Electron wrapper that loads chatgpt.com in a window. It's not the real app.&lt;/li&gt;
&lt;li&gt;Articles pointing you at other wrappers doing the same thing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These work, but you're getting a community-built shell around a website — not the app itself. The real ChatGPT Desktop (the one in the Windows Store) has features, update hooks, and auth flows that wrapper apps can't replicate cleanly.&lt;/p&gt;
&lt;p&gt;This guide unpacks the official binary and runs it on Ubuntu 26.04. Tested on kernel 7.0.0, RTX 5070, NVIDIA 580/CUDA 13.0, both X11 and Wayland via XWayland.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-the-script-actually-does&quot;&gt;What the Script Actually Does&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Extracts the &lt;code&gt;x64&lt;/code&gt; MSIX from the official &lt;code&gt;.msixbundle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pulls out the official &lt;code&gt;app.asar&lt;/code&gt; (the real app logic)&lt;/li&gt;
&lt;li&gt;Patches three platform assumptions so it boots on Linux:&lt;ul&gt;
&lt;li&gt;Routes the platform chooser through the macOS-style implementation (Linux is close enough)&lt;/li&gt;
&lt;li&gt;Disables macOS-only &lt;code&gt;setVibrancy()&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;Skips the macOS &lt;code&gt;ioreg&lt;/code&gt; device ID path&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stages Linux Electron around the official app resources&lt;/li&gt;
&lt;li&gt;Packages everything as a proper &lt;code&gt;.deb&lt;/code&gt; — &lt;code&gt;chatgpt-desktop-native&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You end up with a system package you can install, update, and uninstall like anything else.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;h4 id=&quot;system-packages&quot;&gt;System packages&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get install -y dpkg-dev nodejs python3 file
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;local-electron-tooling&quot;&gt;Local Electron tooling&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir ~/chatgpt-windows-deb &amp;amp;&amp;amp; cd ~/chatgpt-windows-deb
npm install electron @electron/asar --no-save
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;get-the-official-msix-bundle&quot;&gt;Get the Official MSIX Bundle&lt;/h3&gt;
&lt;p&gt;You need the official Windows package from OpenAI. Download &lt;code&gt;OpenAI.ChatGPT-Desktop_&amp;lt;version&amp;gt;.Msixbundle&lt;/code&gt; from the Microsoft Store or OpenAI's distribution endpoint and drop it in your working directory.&lt;/p&gt;
&lt;p&gt;The repo includes the version current at time of writing as an example payload.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;build&quot;&gt;Build&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/chatgpt-windows-deb
git clone https://github.com/johnohhh1/chatgpt_desktop_ubuntu .
npm install electron @electron/asar --no-save
./build-chatgpt-native-deb.sh --exe ./OpenAI.ChatGPT-Desktop_2026.212.2039.0.Msixbundle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output lands in &lt;code&gt;dist/&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dist/chatgpt-desktop-native_2026.212.2039.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;install&quot;&gt;Install&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get install ./dist/chatgpt-desktop-native_2026.212.2039.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebuilding without a version bump? Force refresh:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get install --reinstall ./dist/chatgpt-desktop-native_2026.212.2039.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;register-auth-callback-handlers&quot;&gt;Register Auth Callback Handlers&lt;/h3&gt;
&lt;p&gt;The package installs a helper that registers your desktop session as the handler for the &lt;code&gt;chatgpt:&lt;/code&gt; and &lt;code&gt;chatgpt-alt:&lt;/code&gt; URL schemes (used for the login flow):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chatgpt-desktop-native-register
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify it took:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xdg-mime query default x-scheme-handler/chatgpt
xdg-mime query default x-scheme-handler/chatgpt-alt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both should return &lt;code&gt;chatgpt-desktop-native.desktop&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;launch&quot;&gt;Launch&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chatgpt-desktop-native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The desktop entry sets the WM class to &lt;code&gt;electron&lt;/code&gt; so GNOME binds the running window to the ChatGPT icon instead of the generic gear icon.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;known-quirks&quot;&gt;Known Quirks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The terminal will print Electron/NVIDIA/VA-API noise. This is normal — ignore it.&lt;/li&gt;
&lt;li&gt;The success signal is functional login and working chat, not a clean terminal.&lt;/li&gt;
&lt;li&gt;If GNOME still shows the generic icon after first launch: close the app fully and relaunch once. Stubborn shell? Log out and back in.&lt;/li&gt;
&lt;li&gt;If OpenAI updates the Windows app significantly, the patch targets in &lt;code&gt;build-chatgpt-native-deb.sh&lt;/code&gt; may need updating. PR welcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reproducing-on-another-machine&quot;&gt;Reproducing on Another Machine&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Clone the repo to the target machine&lt;/li&gt;
&lt;li&gt;Drop a real ChatGPT &lt;code&gt;.msix&lt;/code&gt;, &lt;code&gt;.msixbundle&lt;/code&gt;, &lt;code&gt;.appx&lt;/code&gt;, or &lt;code&gt;.appxbundle&lt;/code&gt; in the directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install electron @electron/asar --no-save&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./build-chatgpt-native-deb.sh --exe &amp;lt;your-payload&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install the generated &lt;code&gt;.deb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;chatgpt-desktop-native-register&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Launch &lt;code&gt;chatgpt-desktop-native&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;repo&quot;&gt;Repo&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/johnohhh1/chatgpt_desktop_ubuntu&quot;&gt;github.com/johnohhh1/chatgpt_desktop_ubuntu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Issues and PRs open. If a new MSIX version breaks the patches, open an issue with the version string and I'll update the patch targets.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Tested on Ubuntu 26.04 &quot;Noble&quot; with kernel 7.0.0-10, RTX 5070, NVIDIA driver 580, CUDA 13.0.&lt;/em&gt;&lt;strong&gt;strong text&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14372/run-the-real-chatgpt-desktop-app-on-ubuntu-linux-not-a-wrapper</guid>
<pubDate>Thu, 09 Apr 2026 20:58:42 +0000</pubDate>
</item>
</channel>
</rss>