Offline App — Electron Wrapper
Overview
The April Robots Offline application is a FastAPI backend paired with a compiled SvelteKit frontend, wrapped in Electron to give center specialists a native desktop experience on Windows. Electron manages the full lifecycle: spawning the backend process, serving the frontend over a local HTTP server, managing robot microservice containers via Podman, and handling automatic updates from GitHub Releases.
The Electron wrapper lives in the electron/ subdirectory of the april-robots-offline-general-api repository.
Directory structure
april-robots-offline-general-api/
├── app/ # FastAPI backend (Python)
├── frontend/ # Pre-built SvelteKit static files
├── database/ # File-based database (DictDataBase)
├── logs/ # Runtime logs (created on first run)
├── electron/
│ ├── src/
│ │ ├── main.js # Main process — app lifecycle, window management, IPC
│ │ ├── preload.js # IPC bridge exposed to renderer
│ │ ├── server.js # Local HTTP server for the frontend
│ │ ├── auto-updater.js # Update manager (GitHub Releases)
│ │ ├── podman-manager.js # Robot microservice container orchestration
│ │ ├── logger.js # Centralized logging utility
│ │ ├── loading.html # Splash screen shown during startup
│ │ ├── titlebar.html # Custom frameless window title bar
│ │ └── update-notification.html # Update download progress UI
│ ├── package.json
│ └── electron-builder.yml
├── build.ps1 # Windows build script (PyInstaller + electron-builder)
└── .env # Runtime configuration (not committed)Startup sequence
When the app is launched, the main process follows a fixed four-step startup before showing the main window. A loading window (220×140 frameless splash screen) is shown throughout and updates its status text at each step.
flowchart TD
Start([App launched]) --> LW["Show loading window\n(splash screen)"]
LW --> P{ENABLE_PODMAN?}
P -->|true| PM["PodmanManager.startup()\n1. Start Podman machine\n2. Login to GHCR\n3. podman compose down\n4. podman compose pull\n5. podman compose up -d"]
P -->|false| BE
PM -->|error → log + continue| BE
BE["Start backend process\n(see Backend execution)"]
BE --> W["Wait 3 seconds\n(initialization buffer)"]
W --> FS["Start frontend HTTP server\nlocalhost:3000"]
FS --> MW["Create main BrowserWindow\nload localhost:3000"]
MW --> TB["Inject custom title bar\nvia executeJavaScript"]
TB --> AU["Check for updates\n(only if APP_ENV=prod)"]
AU --> Show["Close loading window\nmaximize and show main window"]
Show --> Ready([App ready])
Backend execution
The backend is a standard FastAPI + Uvicorn app started as a child process. How it is started depends on whether the app is packaged or running in development:
| Mode | Executable | Working directory |
|---|---|---|
| Development | ./venv/bin/python -m app.main --api-only | Project root |
| Production (packaged) | april-robots-backend.exe --api-only | process.resourcesPath |
The process is spawned with stdio captured and piped to the logger. On shutdown:
- Windows —
taskkill /pid {pid} /T /F(kills the entire process tree) - Unix —
SIGTERMsignal
The --api-only flag tells the FastAPI app to run without the bundled frontend, since Electron serves the frontend separately.
Frontend serving
server.js creates a plain Node.js HTTP server that serves the pre-built SvelteKit static files from the frontend/ directory on localhost:3000. It handles:
- MIME type detection per file extension
- SPA fallback — any unmatched path returns
index.html(required for client-side routing) Cache-Control: no-cacheon all responses- Path traversal prevention (requests cannot escape the
frontend/directory)
The main BrowserWindow loads http://localhost:3000 once the server is up.
Window setup
The main window is frameless (no native OS title bar) with a custom 34px title bar injected into the page via executeJavaScript after load. This gives the app a branded look while keeping native window behaviour (drag, minimize, maximize, close).
| Window | Size | Notes |
|---|---|---|
| Main window | 1280×720 (min 1024×600) | Frameless, custom title bar, context isolation on |
| Loading window | 220×140 | Frameless, transparent, always on top |
| Update notification | 550×400 | Frameless, transparent, centered |
The custom title bar (titlebar.html) is a green (#72cd78) fixed-height bar positioned at the top of the page. It reflects the current page title dynamically and forwards minimize / maximize / close button clicks to the main process via IPC.
IPC communication
preload.js exposes a safe window.electronAPI object to the renderer (Svelte frontend) using Electron’s contextBridge. The renderer cannot access Node.js APIs directly.
window.electronAPI = {
// Window controls
windowMinimize(), windowMaximize(), windowClose(), windowIsMaximized()
// App info
getBackendUrl() // returns http://127.0.0.1:8000
getAppVersion() // returns APP_VERSION from .env
isDevMode() // true if not packaged
// Updates
checkForUpdates()
onUpdateDownloadProgress(callback)
sendUpdateAction(action)
onShowUpdateNotification(callback)
// Session and language
setSessionActive(payload) // prevents accidental quit mid-session
setAppLanguage(language) // en | ru | kk
platform // process.platform
}Podman manager
podman-manager.js orchestrates the robot microservice containers (NAO MS, Furhat MS) using Podman Compose. It is only active when APP_ENV=prod and ENABLE_PODMAN=true.
Startup sequence:
- Start the Podman machine (WSL-backed VM), if not already running.
- Login to GHCR using
GHCR_ACTORandGHCR_TOKENfrom.env. podman compose down— ensure a clean slate.podman compose pull— fetch the latest images for the configured versions.podman compose up -d— start containers in the background.- Begin streaming container logs to the logger.
On app shutdown, podman stop is called for each container. Containers are stopped but not removed, so their state is preserved.
Container versions are set in .env via NAO_AGENT_VERSION and FURHAT_AGENT_VERSION.
See Microservices for details on what the NAO and Furhat containers do.
Auto-updater
auto-updater.js manages over-the-air updates from the april-robots-offline-deployment-kit GitHub repository. Updates only run when APP_ENV=prod.
Update check:
- Fetches all releases from the GitHub API (authenticated with
GHCR_TOKEN). - Filters out drafts, sorts by semantic version.
- Compares with
APP_VERSIONfrom.env. - If a newer release exists, shows the update notification window.
Install flow:
- Downloads the release ZIP from the GitHub API (with redirect following).
- Tracks download progress, reporting to the notification window in 5% increments.
- Extracts the ZIP using PowerShell
Expand-Archive. - Environment merge — the new
.envfrom the release is merged with the existing one:CENTER_JWT_KEYis always preserved from the existing.env(it is center-specific and must not be overwritten).APP_VERSIONis updated to the new version.
- Generates a SHA256 hash manifest for all files to be copied.
- Generates and runs a PowerShell post-update script that:
- Waits for the Electron app to close (up to 30 seconds).
- Copies all new files into place.
- Preserves
database/storage/children.jsonanddatabase/storage/sessions.json(local data must not be overwritten). - Verifies copied files against the hash manifest.
- Relaunches the updated app.
CENTER_JWT_KEY and the local database files. All other .env values are overwritten from the release. Make sure any new environment variables have sensible defaults in the release .env.Logging
logger.js provides category-based file logging for each major subsystem. Log files are written to logs/{category}/YYYYMMDD_HHMMSS_{category}.log. A maximum of 10 log files per category are kept; older files are deleted automatically.
Categories: app, api, podman, nao, furhat, updater.
Subprocess stdout and stderr are piped directly into the appropriate logger via logger.pipeStream().
Build and packaging
The app is built for Windows with build.ps1, which runs two steps in sequence:
Step 1 — PyInstaller bundles the FastAPI app into a single Windows executable:
dist/april-robots-backend.exeStep 2 — electron-builder packages the Electron app as an unpacked directory (no NSIS installer). The output in dist/ contains the .exe launcher alongside a resources/ folder with everything bundled:
april-robots-backend.exeapp/podman/— Podman Compose files for robot microservicesfrontend/— pre-built SvelteKit static filesdatabase/,data/,logs/,static/,robot_scripts/.env.example
The app is distributed as a ZIP of this unpacked directory via GitHub Releases in the deployment-kit repository.
Development vs production
| Aspect | Development | Production |
|---|---|---|
| Backend | ./venv/bin/python -m app.main | april-robots-backend.exe |
| Working directory | Project root | process.resourcesPath |
| Podman | Disabled (APP_ENV != prod) | Enabled |
| Auto-updates | Disabled | Enabled |
| DevTools | Auto-opened | Hidden |
Environment variables
The .env file is generated and bundled into each release by the CI/CD pipeline in the april-robots-offline-deployment-kit repository. Values are sourced from that repository’s GitHub Secrets and Variables — nothing is committed to the repository. To add or change an env value in a release, update the corresponding secret or variable in the deployment-kit repo settings.
At runtime, .env lives in the app root (project root in dev, resources/ in packaged).
| Variable | Source | Purpose |
|---|---|---|
APP_VERSION | GitHub Variable | Current app version (also updated by auto-updater on install) |
APP_ENV | GitHub Variable | prod enables Podman and updates; anything else disables both |
CENTER_JWT_KEY | GitHub Secret | Center authentication key for cloud sync — preserved across updates |
DEBUG | GitHub Variable | Enables verbose FastAPI logging |
BACKEND_URL | GitHub Variable | FastAPI URL (default: http://127.0.0.1:8000) |
FRONTEND_DIR | GitHub Variable | Path to compiled frontend directory (default: frontend) |
ONLINE_BACKEND_URL | GitHub Variable | URL of the AR Online backend for sync |
ENABLE_PODMAN | GitHub Variable | Set to false to skip Podman startup |
GHCR_TOKEN | GitHub Secret | Token for pulling container images and checking GitHub releases |
GHCR_ACTOR | GitHub Variable | GitHub username for GHCR login |
NAO_AGENT_VERSION | GitHub Variable | Version tag for the NAO MS image |
FURHAT_AGENT_VERSION | GitHub Variable | Version tag for the Furhat MS image |
NAO_MICROSERVICE_URL | GitHub Variable | NAO MS URL (default: http://localhost:5050) |
FURHAT_MICROSERVICE_URL | GitHub Variable | Furhat MS URL (default: http://localhost:5051) |
NAO_USERNAME / NAO_PASSWORD | GitHub Secret | SSH credentials for NAO robot |
FURHAT_USERNAME / FURHAT_PASSWORD | GitHub Secret | SSH credentials for Furhat robot |