Skip to content
Offline App — Electron Wrapper

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:

ModeExecutableWorking directory
Development./venv/bin/python -m app.main --api-onlyProject root
Production (packaged)april-robots-backend.exe --api-onlyprocess.resourcesPath

The process is spawned with stdio captured and piped to the logger. On shutdown:

  • Windowstaskkill /pid {pid} /T /F (kills the entire process tree)
  • UnixSIGTERM signal

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-cache on 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).

WindowSizeNotes
Main window1280×720 (min 1024×600)Frameless, custom title bar, context isolation on
Loading window220×140Frameless, transparent, always on top
Update notification550×400Frameless, 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:

  1. Derive image tag from APP_VERSION — the manager reads APP_VERSION from .env (e.g., 1.4.2 or 1.4.2-beta) and constructs the MS image tag as {major.minor}{suffix}-latest:
    • 1.4.21.4-latest
    • 1.4.2-beta1.4-beta-latest This tag is set as both NAO_AGENT_VERSION and FURHAT_AGENT_VERSION in the process environment, overriding any values from .env.
  2. Start the Podman machine (WSL-backed VM), if not already running.
  3. Login to GHCR using GHCR_ACTOR and GHCR_TOKEN from .env.
  4. podman compose down — ensure a clean slate.
  5. podman compose pull — pulls the image tagged 1.4-latest (or equivalent) from GHCR.
  6. podman compose up -d — start containers in the background.
  7. 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.

This means the offline app always runs the latest patch of the microservice that is compatible with its current major.minor version — matching the major.minor-latest tag pushed to GHCR during the MS release. See Microservices and Release docs — Robot Microservices for how those tags are published.

Auto-updater

The auto-updater is incomplete. The update ZIP is downloaded successfully and the PowerShell post-update script works correctly when run manually — but the script is not triggered automatically after the download completes. As a workaround, the script can be run by hand to finish the update. This needs to be fixed before the updater can be considered fully functional.

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:

  1. Fetches all releases from the GitHub API (authenticated with GHCR_TOKEN).
  2. Filters out drafts, sorts by semantic version.
  3. Compares with APP_VERSION from .env.
  4. If a newer release exists, shows the update notification window.

Install flow:

  1. Downloads the release ZIP from the GitHub API (with redirect following).
  2. Tracks download progress, reporting to the notification window in 5% increments.
  3. Extracts the ZIP using PowerShell Expand-Archive.
  4. Environment merge — the new .env from the release is merged with the existing one:
    • CENTER_JWT_KEY is always preserved from the existing .env (it is center-specific and must not be overwritten).
    • APP_VERSION is updated to the new version.
  5. Generates a SHA256 hash manifest for all files to be copied.
  6. 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.json and database/storage/sessions.json (local data must not be overwritten).
    • Verifies copied files against the hash manifest.
    • Relaunches the updated app.

Files and data preserved across updates:

  • CENTER_JWT_KEY in .env — the center’s authentication key entered at installation time.
  • database/storage/children.json — local children records.
  • database/storage/sessions.json — local session records.

All other files (including the rest of .env) are overwritten from the release package.

Auto-updates are currently enabled in prod but the updater is incomplete (see Auto-updater). Consider setting APP_ENV to a non-prod value to disable auto-updates until the updater is fixed.

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 release build is fully automated via GitHub Actions in the april-robots-offline-deployment-kit repository. It triggers on push to any release/** branch (or manually via workflow_dispatch).

Local build (development only)

build.ps1 in the backend repo builds the app locally in two steps:

  1. PyInstaller — bundles the FastAPI app into dist/april-robots-backend.exe.
  2. electron-builder — packages the Electron app as an unpacked directory under electron/dist/win-unpacked/.

This is only used for local testing. The production release always goes through the CI/CD pipeline below.

CI/CD release pipeline

The pipeline runs on Windows (windows-latest) and produces two release assets: a setup.exe installer and a release.zip of the raw unpacked app.

Step 1 — Extract version and derive image tag

The version is extracted from the branch name (e.g., release/1.4.21.4.2). The MS image tag is derived from it:

  • 1.4.2NAO_AGENT_VERSION=1.4
  • 1.4.2-betaNAO_AGENT_VERSION=1.4-beta

Step 2 — Tag the release

A Git tag v{VERSION} is created and pushed. If the tag already exists it is deleted and recreated.

Step 3 — Check out source repos

Both april-robots-offline-fe and april-robots-offline-general-api are checked out at their matching release/{VERSION} branch. All three repos must have a branch with this exact name.

Step 4 — Build frontend

Installs npm dependencies and runs npm run build in the frontend repo. The compiled output lands in frontend/build/.

Step 5 — Build backend

Sets up a Python 3.12.3 venv, installs requirements, and runs build.ps1 from the backend repo. This produces the Electron unpacked directory at backend/electron/dist/win-unpacked/.

Step 6 — Assemble release directory

The compiled frontend is copied into resources/frontend/ inside the unpacked directory. The contents of win-unpacked/ are then copied to a top-level release/ folder.

Step 7 — Assemble .env

A .env file is written into release/resources/ with all environment variables injected from GitHub Secrets and Variables:

  • APP_VERSION — the release version
  • NAO_AGENT_VERSION / FURHAT_AGENT_VERSION — derived from APP_VERSION
  • Robot SSH credentials, GHCR token, and all other variables
CENTER_JWT_KEY is not written into the .env at this stage. It is entered by the client during installation via the InnoSetup prompt and written to .env at that point.

Step 8 — Compile installer

InnoSetup 6 is installed on the runner and compiles installer.iss into Output/setup.exe. The installer prompts the client for CENTER_JWT_KEY and writes it into the installed app’s .env.

Step 9 — Package and publish

release/ is zipped into release.zip. Both setup.exe and release.zip are uploaded to a GitHub Release tagged v{VERSION}:

  • Versions containing - (e.g., 1.4.2-beta) are published as pre-releases.
  • Clean versions (e.g., 1.4.2) are published as latest.

Development vs production

AspectDevelopmentProduction
Backend./venv/bin/python -m app.mainapril-robots-backend.exe
Working directoryProject rootprocess.resourcesPath
PodmanDisabled (APP_ENV != prod)Enabled
Auto-updatesDisabledEnabled (incomplete — see Auto-updater)
DevToolsAuto-opened in Electron windowOpen via browser at localhost:3000

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).

VariableSourcePurpose
APP_VERSIONGitHub VariableCurrent app version (also updated by auto-updater on install)
APP_ENVGitHub Variableprod enables Podman and updates; anything else disables both
CENTER_JWT_KEYEntered during installationCenter authentication key for cloud sync — inputted by the client in the InnoSetup installer window; preserved across updates and never overwritten by CI/CD
DEBUGGitHub VariableEnables verbose FastAPI logging
BACKEND_URLGitHub VariableFastAPI URL (default: http://127.0.0.1:8000)
FRONTEND_DIRGitHub VariablePath to compiled frontend directory (default: frontend)
ONLINE_BACKEND_URLGitHub VariableURL of the AR Online backend for sync
ENABLE_PODMANGitHub VariableSet to false to skip Podman startup
GHCR_TOKENGitHub SecretToken for pulling container images and checking GitHub releases
GHCR_ACTORGitHub VariableGitHub username for GHCR login
NAO_AGENT_VERSIONGitHub VariableFallback MS image tag (overridden at runtime in prod — derived from APP_VERSION)
FURHAT_AGENT_VERSIONGitHub VariableFallback MS image tag (overridden at runtime in prod — derived from APP_VERSION)
NAO_MICROSERVICE_URLGitHub VariableNAO MS URL (default: http://localhost:5050)
FURHAT_MICROSERVICE_URLGitHub VariableFurhat MS URL (default: http://localhost:5051)
NAO_USERNAME / NAO_PASSWORDGitHub SecretSSH credentials for NAO robot
FURHAT_USERNAME / FURHAT_PASSWORDGitHub SecretSSH credentials for Furhat robot

Installation

The offline app is distributed as a setup.exe built with InnoSetup. The installer walks the client through setup, including a prompt to enter the CENTER_JWT_KEY for that center. This key is written into .env at install time and is preserved by the auto-updater across future updates.

The client machine must have CPU virtualization enabled in BIOS/UEFI. Podman requires WSL2, which requires virtualization. The app will fail to start robot microservices if virtualization is off or unavailable.

TODO: detailed installation walkthrough (Malika).

Debugging

Browser access

The app and all its internal services can be accessed directly in a browser without opening the Electron window. This is the primary way to debug in production:

ServiceURL
Frontend (SvelteKit app)http://localhost:3000
Backend (FastAPI)http://localhost:8000
Backend API docs (Swagger)http://localhost:8000/docs
NAO microservicehttp://localhost:5050
Furhat microservicehttp://localhost:5051

Logs

To inspect logs, navigate to the app installation directory, open resources/logs/. Each subsystem writes to its own subfolder:

resources/logs/
├── app/        # main Electron process
├── api/        # FastAPI backend
├── podman/     # Podman startup and compose operations
├── nao/        # NAO container output
├── furhat/     # Furhat container output
└── updater/    # auto-updater activity

Each folder keeps the 10 most recent log files, named YYYYMMDD_HHMMSS_{category}.log.

Robot microservice issues

If a robot microservice is not working correctly, use the Podman CLI to inspect the containers directly:

# List running containers
podman ps

# View logs for a specific container
podman logs podman_nao-agent_1
podman logs podman_furhat-agent_1

# Restart a container manually
podman restart podman_nao-agent_1

The microservice health endpoints can also be checked in the browser at localhost:5050 and localhost:5051.