Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/yummy-pandas-stand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kiji-privacy-proxy": patch
---

build fix
28 changes: 27 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.PHONY: format lint lint-go lint-frontend lint-frontend-fix lint-all typecheck typecheck-frontend check check-all ruff-fix ruff-all
.PHONY: test test-go test-all test-e2e test-e2e-dataset
.PHONY: clean clean-venv clean-all
.PHONY: build-dmg build-linux verify-linux
.PHONY: build-dmg build-linux verify-linux build-go build-go-embed
.PHONY: setup-onnx setup-tokenizers
.PHONY: electron-build electron-run electron electron-dev electron-install
.PHONY: list show shell jupyter info quickstart
Expand Down Expand Up @@ -274,6 +274,32 @@ build-go: ## Build Go binary for development
./src/backend
@echo "$(GREEN)✅ Go binary built at build/kiji-proxy (version $(VERSION))$(NC)"

build-go-embed: ## Build PRODUCTION Go binary with embedded UI + model (-tags embed)
@echo "$(BLUE)Building production Go binary (embedded UI + model)...$(NC)"
@echo "$(BLUE) → Building frontend (webpack production)...$(NC)"
@cd src/frontend && npm run build:electron
@if [ ! -d "src/frontend/dist" ]; then \
echo "$(YELLOW)❌ src/frontend/dist not found after build — aborting$(NC)"; \
exit 1; \
fi
@echo "$(BLUE) → Staging embed assets into src/backend/ (go:embed can't use ../ paths)...$(NC)"
@rm -rf src/backend/frontend/dist && mkdir -p src/backend/frontend/dist
@cp -R src/frontend/dist/. src/backend/frontend/dist/
@rm -rf src/backend/model/quantized && mkdir -p src/backend/model/quantized
@cp -R model/quantized/. src/backend/model/quantized/
@echo "$(BLUE) → Compiling with -tags embed...$(NC)"
@mkdir -p build src/frontend/resources
@CGO_ENABLED=1 \
CGO_LDFLAGS="$(CGO_LDFLAGS)" \
go build \
-tags embed \
-ldflags="-s -w -X main.version=$(VERSION) -extldflags '$(CGO_LDFLAGS)'" \
-o build/kiji-proxy \
./src/backend
@cp build/kiji-proxy src/frontend/resources/kiji-proxy
@chmod +x src/frontend/resources/kiji-proxy
@echo "$(GREEN)✅ Embedded Go binary built (version $(VERSION)) and copied to src/frontend/resources/$(NC)"

electron-build: ## Build Electron app for production
@echo "$(BLUE)Building Electron app...$(NC)"
@cd src/frontend && npm run build:electron
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"start": "node server.js",
"electron": "npm run build:electron && electron .",
"electron:dev": "NODE_ENV=development electron .",
"electron:pack": "npm run build:electron && electron-builder",
"electron:pack": "(cd ../.. && make build-go-embed) && electron-builder",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"type-check": "tsc --noEmit",
Expand Down
74 changes: 57 additions & 17 deletions src/frontend/src/electron/electron-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ const { registerIpcHandlers } = require("./ipc-handlers");
const { setMenuLanguage, mt } = require("./menu-i18n");
const isDev = process.env.NODE_ENV === "development";

// Global safety net: log (and, if telemetry is on, report) any promise
// rejection or exception that escaped a local handler instead of letting Node
// crash the process. Registered before any async startup work runs. Notably
// this catches rejections from the auto-updater (Squirrel.Mac) code paths.
process.on("unhandledRejection", (reason) => {
console.error("[Main] Unhandled promise rejection:", reason);
if (typeof Sentry !== "undefined" && isTelemetryEnabled()) {
Sentry.captureException(reason);
}
});
process.on("uncaughtException", (error) => {
console.error("[Main] Uncaught exception:", error);
if (typeof Sentry !== "undefined" && isTelemetryEnabled()) {
Sentry.captureException(error);
}
});

// Telemetry (Sentry error reporting) is OPT-IN. It is only initialized when the
// user turned on "Crash & error reporting" in Settings (persisted as
// `telemetryEnabled` in the Electron config). See initTelemetryMain().
Expand Down Expand Up @@ -79,6 +96,16 @@ autoUpdater.on("error", (err) => {
console.error("[AutoUpdater] Error:", err);
});

// Trigger the installer relaunch. This is the Squirrel.Mac crash site, so guard
// it: a synchronous throw here must not take down the click handler / process.
const quitAndInstall = () => {
try {
autoUpdater.quitAndInstall();
} catch (err) {
console.error("[AutoUpdater] quitAndInstall failed:", err);
}
};

let mainWindow;
let splashWindow = null;
let goProcess = null;
Expand Down Expand Up @@ -756,7 +783,7 @@ function updateTrayMenu() {
? [
{
label: mt("restartToUpdate"),
click: () => autoUpdater.quitAndInstall(),
click: () => quitAndInstall(),
},
]
: []),
Expand Down Expand Up @@ -1033,7 +1060,7 @@ function createMenu() {
? [
{
label: mt("restartToUpdate"),
click: () => autoUpdater.quitAndInstall(),
click: () => quitAndInstall(),
},
]
: []),
Expand Down Expand Up @@ -1087,28 +1114,41 @@ app.whenReady().then(async () => {
createWindow();

// Check for updates after launch
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.checkForUpdatesAndNotify().catch((err) => {
console.error("[AutoUpdater] checkForUpdatesAndNotify failed:", err);
});

// Re-check for updates every hour for long-running sessions
setInterval(() => autoUpdater.checkForUpdates(), 60 * 60 * 1000);
setInterval(() => {
autoUpdater.checkForUpdates().catch((err) => {
console.error("[AutoUpdater] periodic checkForUpdates failed:", err);
});
}, 60 * 60 * 1000);

app.on("activate", async () => {
// On macOS, re-create a window when the dock icon is clicked
if (BrowserWindow.getAllWindows().length === 0) {
// Ensure backend is running
if (!goProcess) {
launchGoBinary();
await waitForBackend();
} else {
// Process exists but might not be listening yet
await waitForBackend(10, 500);
// On macOS, re-create a window when the dock icon is clicked. Electron
// discards the promise returned by this async listener, so guard it here.
try {
if (BrowserWindow.getAllWindows().length === 0) {
// Ensure backend is running
if (!goProcess) {
launchGoBinary();
await waitForBackend();
} else {
// Process exists but might not be listening yet
await waitForBackend(10, 500);
}
createWindow();
} else if (mainWindow) {
// If window exists but is hidden, show it
showMainWindow();
}
createWindow();
} else if (mainWindow) {
// If window exists but is hidden, show it
showMainWindow();
} catch (err) {
console.error("[Main] activate handler failed:", err);
}
});
}).catch((err) => {
console.error("[Main] Startup (whenReady) failed:", err);
});

// Keep app running in menu bar even when all windows are closed
Expand Down
23 changes: 14 additions & 9 deletions src/frontend/src/electron/ipc-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,18 +453,23 @@ const registerIpcHandlers = ({
// ---- Model directory management ----

ipcMain.handle("select-model-directory", async () => {
const { dialog } = require("electron");
const result = await dialog.showOpenDialog(getMainWindow(), {
properties: ["openDirectory"],
title: "Select Model Directory",
message: "Choose the directory containing your PII model files",
});
try {
const { dialog } = require("electron");
const result = await dialog.showOpenDialog(getMainWindow(), {
properties: ["openDirectory"],
title: "Select Model Directory",
message: "Choose the directory containing your PII model files",
});

if (result.canceled || result.filePaths.length === 0) {
return null;
}

if (result.canceled || result.filePaths.length === 0) {
return result.filePaths[0];
} catch (error) {
console.error("[IPC] select-model-directory failed:", error);
return null;
}

return result.filePaths[0];
});

defineConfigField(
Expand Down
91 changes: 20 additions & 71 deletions src/scripts/build_dmg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,12 @@ else
fi

echo ""
echo "📦 Step 5: Building Electron app..."
echo "-----------------------------------"

npm run build:electron

if [ $? -ne 0 ]; then
echo "❌ Electron app build failed!"
exit 1
fi

echo "✅ Electron app built successfully"

echo "📦 Step 5: Returning to project root..."
echo "---------------------------------------"
# NOTE: the frontend (webpack) build now happens inside `make build-go-embed`
# (Step 7-8 below) so the exact same command produces the UI that gets embedded
# into the Go binary. This keeps the embed build and `npm run electron:pack`
# from drifting. A second frontend build for the asar still runs in Step 10.
cd "$PROJECT_ROOT"

echo ""
Expand Down Expand Up @@ -227,71 +221,26 @@ for file in model.onnx.data tokenizer.json vocab.txt model_manifest.json; do
done

echo ""
echo "📦 Step 7: Preparing files for Go embedding..."
echo "----------------------------------------------"

# Copy frontend/dist files to src/backend/frontend/dist/ for embedding
# Go embed cannot use ../ paths, so we need the files under src/backend/
if [ -d "src/frontend/dist" ]; then
mkdir -p src/backend/frontend/dist
# Use rsync for faster copying (handles incremental updates)
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete src/frontend/dist/ src/backend/frontend/dist/
echo "✅ Frontend files synced to src/backend/frontend/dist/ for embedding (rsync)"
else
cp -r src/frontend/dist/* src/backend/frontend/dist/
echo "✅ Frontend files copied to src/backend/frontend/dist/ for embedding"
fi
else
echo "❌ Frontend dist directory not found: src/frontend/dist"
exit 1
fi

# Copy model files to src/backend/model/quantized/ for embedding
if [ -d "model/quantized" ]; then
mkdir -p src/backend/model/quantized
# Use rsync for faster copying if available
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete model/quantized/ src/backend/model/quantized/
else
cp -r model/quantized/* src/backend/model/quantized/
fi

# Verify model files after copying
COPIED_MODEL_SIZE=$(stat -f%z "src/backend/model/quantized/model.onnx" 2>/dev/null || stat -c%s "src/backend/model/quantized/model.onnx" 2>/dev/null || wc -c < "src/backend/model/quantized/model.onnx" 2>/dev/null || echo "0")
echo "✅ Model files copied to src/backend/model/quantized/ for embedding (${COPIED_MODEL_SIZE} bytes)"
else
echo "❌ Model directory not found: model/quantized"
echo " This will cause runtime errors - the app needs the model files"
exit 1
fi

echo ""
echo "📦 Step 8: Building Go binary..."
echo "--------------------------------"

# Extract version from package.json
VERSION=$(cd src/frontend && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")
echo "Building version: $VERSION"

# Build the Go binary with embedded files and version injection
mkdir -p build
echo "📦 Step 7-8: Building embedded Go binary (UI + model)..."
echo "--------------------------------------------------------"

# Use parallel compilation with version injection
CGO_ENABLED=1 \
GOMAXPROCS=$PARALLEL_JOBS \
go build \
-tags embed \
-ldflags="-s -w -X main.version=$VERSION -extldflags '-L./build/tokenizers'" \
-o build/kiji-proxy \
./src/backend
# Single source of truth for the embedded backend binary: `make build-go-embed`
# builds the frontend, stages src/frontend/dist + model/quantized into
# src/backend/ for //go:embed (Go embed can't use ../ paths), compiles with
# `-tags embed`, and copies the binary into src/frontend/resources/. The same
# target backs `npm run electron:pack`, so the DMG/CI build and local packing
# can never diverge on the embed step (the root cause of the white-screen bug).
#
# NOTE: model LFS verification (Step 6 above) must run before this so the staged
# model files are the real binaries, not LFS pointers.
make build-go-embed

if [ $? -ne 0 ]; then
echo "❌ Go binary build failed!"
echo "❌ Embedded Go binary build failed!"
exit 1
fi

echo "✅ Go binary created: build/kiji-proxy"
echo "✅ Go binary created: build/kiji-proxy (embedded UI + model)"

echo ""
echo "📦 Step 9: Preparing Electron resources..."
Expand Down
Loading