feat(aionui): Add AionUi AI cowork desktop app (v2.1.4)
Some checks failed
CI / check (push) Has been cancelled

Build AionUi from source using bun for dependency fetching and
electron-vite for TypeScript compilation. Native addons
(better-sqlite3, sharp) are compiled against nixpkgs node-gyp.

The package uses a fixed-output derivation for bun install,
electron-vite for the build, and wraps the result with nixpkgs
electron for a fully self-contained runtime.

Also includes desktop entry and icon generation.
This commit is contained in:
2026-05-27 13:48:18 +03:00
parent efc1ad85e2
commit 02947027ba
3 changed files with 233 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
{
pkgs,
...
}:
pkgs.callPackage ./package.nix { }

226
packages/aionui/package.nix Normal file
View File

@@ -0,0 +1,226 @@
{
lib,
stdenv,
fetchFromGitHub,
bun,
nodejs,
node-gyp,
electron,
autoPatchelfHook,
makeWrapper,
copyDesktopItems,
imagemagick,
python3,
cacert,
}:
let
version = "2.1.4";
src = fetchFromGitHub {
owner = "iOfficeAI";
repo = "AionUi";
rev = "v${version}";
hash = "sha256-A7dKGEuo5n+M7D9fJR3TN95q1P7En/ulsTO+ev6SAUY=";
};
# Fixed-output derivation: fetch all pure-JS dependencies via bun.
# We skip install scripts because they use #!/usr/bin/env shebangs
# that don't exist in the Nix sandbox. Native addons are built
# separately in the main derivation.
bunDeps = stdenv.mkDerivation {
name = "aionui-bun-deps-${version}";
inherit src;
nativeBuildInputs = [
bun
cacert
];
outputHashAlgo = "sha256";
outputHashMode = "recursive";
outputHash = "sha256-Fuj55vxDgpx5A4iwnU/5IZKPfaYMH3DeZ1yauuZHn8g=";
dontPatchShebangs = true;
dontFixup = true;
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
buildPhase = ''
export HOME=$TMPDIR
bun install --frozen-lockfile --ignore-scripts
'';
installPhase = ''
mkdir -p $out
cp -r node_modules $out/
cp -r packages $out/
# Workspace packages reference root configs with relative paths
cp tsconfig.json $out/
'';
};
in
stdenv.mkDerivation rec {
pname = "aionui";
inherit version src;
nativeBuildInputs = [
bun
nodejs
node-gyp
autoPatchelfHook
makeWrapper
copyDesktopItems
imagemagick
python3
];
buildInputs = [
stdenv.cc.cc.lib
];
buildPhase = ''
runHook preBuild
export HOME=$TMPDIR
# Use the pre-fetched node_modules from the FOD (symlink farm)
ln -sf ${bunDeps}/node_modules .
# Build better-sqlite3 native addon
# The FOD output is read-only; build in a temp dir.
echo "Building better-sqlite3 from source..."
BT3_SRC=$TMPDIR/bts3-src
BT3_OUT=$TMPDIR/bts3-out
mkdir -p "$BT3_SRC" "$BT3_OUT"
cp -rL node_modules/better-sqlite3/* "$BT3_SRC/"
chmod -R u+w "$BT3_SRC"
(
cd "$BT3_SRC"
export npm_config_nodedir=${nodejs}
node-gyp rebuild
mkdir -p "$BT3_OUT/build/Release"
cp build/Release/better_sqlite3.node "$BT3_OUT/build/Release/" 2>/dev/null || true
)
# Build sharp native addon
echo "Building sharp from source..."
SHARP_SRC=$TMPDIR/sharp-src
SHARP_OUT=$TMPDIR/sharp-out
mkdir -p "$SHARP_SRC" "$SHARP_OUT"
cp -rL node_modules/sharp/* "$SHARP_SRC/" 2>/dev/null || true
if [ -f "$SHARP_SRC/package.json" ]; then
chmod -R u+w "$SHARP_SRC"
(
cd "$SHARP_SRC"
export npm_config_nodedir=${nodejs}
node-gyp rebuild 2>/dev/null || echo " sharp: build skipped (needs libvips)"
mkdir -p "$SHARP_OUT/build/Release"
cp build/Release/sharp.node "$SHARP_OUT/build/Release/" 2>/dev/null || true
)
fi
# electron-vite build
echo "Running electron-vite build..."
${nodejs}/bin/node node_modules/.bin/electron-vite build \
--config packages/desktop/electron.vite.config.ts
# Bundle MCP servers
echo "Building MCP servers..."
${nodejs}/bin/node scripts/build-mcp-servers.js
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/{bin,lib/aionui}
# Built output
cp -r out $out/lib/aionui/
cp -r public $out/lib/aionui/
cp package.json $out/lib/aionui/
# Runtime dependencies
# Copy node_modules (from the symlink) the compiled output
# has externalized require() calls for every dependency.
cp -rL node_modules $out/lib/aionui/
# Remove prebuilt musl (Alpine) binaries from sharp they
# are incompatible with glibc and we built from source anyway.
find $out/lib/aionui/node_modules -path "*/sharp-linuxmusl*" -type d -exec rm -rf {} + 2>/dev/null || true
find $out/lib/aionui/node_modules -path "*/sharp-libvips-linuxmusl*" -type d -exec rm -rf {} + 2>/dev/null || true
# Inject compiled native addons
# cp -rL from the FOD preserves read-only permissions.
# Make native addon dirs writable before overlaying binaries.
chmod -R u+w $out/lib/aionui/node_modules/better-sqlite3 2>/dev/null || true
chmod -R u+w $out/lib/aionui/node_modules/sharp 2>/dev/null || true
BT3_OUT=$TMPDIR/bts3-out
if [ -f "$BT3_OUT/build/Release/better_sqlite3.node" ]; then
mkdir -p $out/lib/aionui/node_modules/better-sqlite3/build/Release
cp "$BT3_OUT/build/Release/better_sqlite3.node" \
$out/lib/aionui/node_modules/better-sqlite3/build/Release/
fi
SHARP_OUT=$TMPDIR/sharp-out
if [ -f "$SHARP_OUT/build/Release/sharp.node" ]; then
mkdir -p $out/lib/aionui/node_modules/sharp/build/Release
cp "$SHARP_OUT/build/Release/sharp.node" \
$out/lib/aionui/node_modules/sharp/build/Release/
fi
# Desktop entry
mkdir -p $out/share/applications
cat > $out/share/applications/aionui.desktop << EOF
[Desktop Entry]
Name=AionUi
Comment=Free, open-source Cowork app with AI Agents
Exec=$out/bin/aionui
Icon=aionui
Terminal=false
Type=Application
Categories=Office;Utility;
EOF
mkdir -p $out/share/icons/hicolor/256x256/apps
if [ -f resources/app.png ]; then
convert resources/app.png -resize 256x256 \
$out/share/icons/hicolor/256x256/apps/aionui.png
fi
# Launcher
makeWrapper ${electron}/bin/electron $out/bin/aionui \
--add-flags "$out/lib/aionui"
runHook postInstall
'';
dontStrip = true;
autoPatchelfIgnoreMissingDeps = true;
desktopItems = [ "aionui.desktop" ];
doCheck = false;
passthru = {
category = "AI Coding Agents";
updateScript = [
"nix-update"
"--flake"
".#aionui"
];
};
meta = with lib; {
description = "Free, open-source, Cowork app with AI Agents";
homepage = "https://github.com/iOfficeAI/AionUi";
changelog = "https://github.com/iOfficeAI/AionUi/releases/tag/v${version}";
license = licenses.asl20;
sourceProvenance = with sourceTypes; [ fromSource ];
mainProgram = "aionui";
platforms = platforms.linux;
};
}