Repository navigation
Headless Linux Appliance
Field notes for turning a headless x86-64 Linux box (no desktop environment) into a boot-to-EFIS appliance that auto-starts pyEFIS + FIX-Gateway on power-on and drives an HDMI/touchscreen. Verified on an Intel N150 mini-PC, Ubuntu Server 24.04, Python 3.12, running the development branches from source.
This is the deployment/appliance companion to Running from Source
(which covers running from a checkout on a normal desktop with a display).
Read that first for the basics of clone / venv / [qt,svs] extras; this page
covers everything that's different when there's no display server and you
want it to come up as an EFIS by itself.
The single biggest surprise: Qt's eglfs platform does not work with the
PyQt6 pip wheel. If that's all you're chasing, jump to
Display backend.
-
The "Raspberry-Pi-only" dependencies install fine on x86. FIX-Gateway pins
rpi-lgpio,smbus, andtables(PyTables); all three resolve from PyPI wheels onx86_64with no workaround (rpi-lgpiopullslgpio, which has x86 wheels;tablesships a manylinux wheel). Installinglibhdf5-dev/libi2c-devfirst is belt-and-suspenders for the rare source build. Don't bother pre-patching the pin list. - PyQt6 (6.8.0), pyavtools (from git), numpy, PyOpenGL all come from wheels.
pyEFIS pins PyYAML==6.0.1, FIX-Gateway pins pyyaml==6.0.2. In one shared
venv pip warns about the mismatch; 6.0.1 runs both fine. Ignore it, or give
each package its own venv.
The system Python is externally managed — pip install outside a venv is
refused. Use virtualenvs; don't reach for --break-system-packages.
sudo apt install build-essential git python3-venv python3-dev pkg-config \
libhdf5-dev libi2c-dev \
libgl1 libegl1 libgles2 libglib2.0-0t64 libdbus-1-3 libxkbcommon0 \
libfontconfig1 libfreetype6(libglib2.0-0t64 is the Ubuntu 24.04 name; older releases use libglib2.0-0.)
mkdir -p ~/src && cd ~/src
git clone --single-branch -b dev <fork>/FIX-Gateway.git fix-gateway
git clone --single-branch -b dev <fork>/pyEfis.git
python3 -m venv ~/pyefis-venv && source ~/pyefis-venv/bin/activate
pip install --upgrade pip setuptools wheel
pip install -e ~/src/fix-gateway # the server; [qt] not needed
pip install -e ~/src/pyEfis[qt,svs]On first run each app auto-seeds its config under $HOME
(~/makerplane/fixgw/config, ~/makerplane/pyefis/config) — see
Running from Source.
fixgw binds 0.0.0.0:3490; verify with ss -ltn | grep :3490.
Install is not finished until the software notice is accepted — install the
nav-data updater, then run pyefis-accept-notice. See
Accept the software notice.
pyEFIS is a Qt app; a headless server has no display server. On a Raspberry Pi
the supported path is Qt's eglfs platform (fullscreen on KMS/DRM, no X).
This does not work with the PyQt6 installed from the pip wheel, and the
failure is opaque:
$ QT_QPA_PLATFORM=eglfs pyefis
qt.qpa.plugin: Could not load the Qt platform plugin "eglfs" ... even though it was found.
This application failed to start because no Qt platform plugin could be initialized.
Root cause: the PyQt6-Qt6 wheel ships the eglfs plugin stubs but omits the
backing Qt eglfs KMS support libraries. Confirm with ldd:
P=~/pyefis-venv/lib/python3.12/site-packages/PyQt6/Qt6/plugins
ldd $P/platforms/libqeglfs.so | grep 'not found'
# => libQt6EglFSDeviceIntegration.so.6, libQt6EglFsKmsSupport.so.6,
# libQt6EglFsKmsGbmSupport.so.6 (absent from the wheel and the system)The system EGL/GLES/GBM/DRM (libEGL, libGLESv2, libgbm, libdrm) are all
fine — it's Qt's own private eglfs libs that are missing, and you can't borrow
the distro's because they're a different Qt version than the wheel.
Why the Pi "just works" and your x86 box doesn't: the Pi image uses the
distro PyQt6 (apt install python3-pyqt6), whose system Qt6 was built with
eglfs. The convenience of pip install PyQt6 is exactly what costs you eglfs.
Two ways out:
-
Option A — distro PyQt6 (keeps eglfs).
sudo apt install python3-pyqt6 python3-pyqt6.qtopengl, create the venv with--system-site-packages, and install pyEFIS without the[qt]extra. Caveat: you inherit Ubuntu's Qt (6.4.x on 24.04), older than pyEFIS's 6.8.0 target — verify the SVS/GL paths. -
Option B — minimal X +
xcb(documented below). Keeps the tested pip PyQt6 6.8.0 and full GL/SVS; adds a small X server. Reliable x86 kiosk path.
sudo apt install xserver-xorg-core xserver-xorg-legacy xinit x11-xserver-utils
# let a service (not just a logged-in seat) start X on the console:
printf 'allowed_users=anybody\nneeds_root_rights=yes\n' | sudo tee /etc/X11/Xwrapper.configThe xcb plugin needs a set of xcb runtime libs a minimal server image lacks.
Qt only names xcb-cursor0 in its error; find the rest with ldd:
ldd $P/platforms/libqxcb.so | grep 'not found'
sudo apt install libxcb-cursor0 libxkbcommon-x11-0 \
libxcb-icccm4 libxcb-keysyms1 libxcb-shape0 libxcb-xkb1 \
libxcb-image0 libxcb-render-util0 libxcb-util1Launch script ~/start-pyefis.sh:
#!/bin/bash
source /home/USER/pyefis-venv/bin/activate
export QT_QPA_PLATFORM=xcb
exec pyefisSmoke test on the physical monitor (vt1):
xinit /home/USER/start-pyefis.sh -- :0 vt1 -nolisten tcp- The iGPU exposes
/dev/dri/card0(ownedroot:video). Add the run user tovideoandrender(andinputfor touch/encoders):sudo usermod -aG video,render,input USER— effective on next login/service start. - Mesa DRI/EGL/GBM (
libgl1-mesa-dri,libegl-mesa0,libgbm1,libdrm2) were already present on Ubuntu Server; install if missing.
Two system services, both WantedBy=multi-user.target. Mask the console getty
so the EFIS owns the screen: sudo systemctl mask getty@tty1.service.
/etc/systemd/system/fixgw.service:
[Unit]
Description=FIX Gateway (CAN-FIX bus)
After=network-online.target
Wants=network-online.target
[Service]
User=USER
ExecStart=/home/USER/pyefis-venv/bin/fixgw
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target/etc/systemd/system/pyefis.service (X/xcb kiosk):
[Unit]
Description=pyEFIS display (X/xcb kiosk)
After=fixgw.service systemd-user-sessions.service
Wants=fixgw.service
Conflicts=getty@tty1.service
[Service]
User=USER
SupplementaryGroups=video render input tty
PAMName=login
WorkingDirectory=/home/USER
ExecStart=/usr/bin/xinit /home/USER/start-pyefis.sh -- :0 vt1 -nolisten tcp
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now fixgw.service pyefis.serviceNotes:
-
PAMName=logingives the service a real logind session on seat0 (lets X grab DRM master); withXwrapper allowed_users=anybodythe setuid Xorg handles the VT. - The harmless
PAM unable to dlopen(pam_lastlog.so)line is just newer PAM having droppedpam_lastlog— ignore it. - A one-off
Failed with result 'exit-code'can appear when yousystemctl restartthe kiosk (X releasing vt1); systemd restarts it and it settles. Cold boot is clean (NRestarts=0).
pyEFIS boots to the screen named by defaultScreen: in main/default.yaml
(PFD as shipped). It never boots to the Data Status screen: if
defaultScreen names it, or names a screen that is not loaded, pyEFIS boots
to the Data Status screen's Continue target (or the first other screen)
instead. A panel pulled from the configurator
takes the defaultScreen name, so the device boots to it. See
Setting Up a Device.
The "Update" button on pyEFIS's data-status screen and the pyefis-data CLI
(status | catalog | update | pair | config-pull | …) are not part of
pyEFIS — they live in the makerplane-data repo (pyefis_data/ package). If
it's missing you get "Updater unavailable on this device" on screen and
pyefis-data: command not found on the shell.
python3 -m venv ~/pyefis-data-venv # its own venv
~/pyefis-data-venv/bin/pip install -e ~/src/makerplane-data
ln -sf ~/pyefis-data-venv/bin/pyefis-data ~/.local/bin/pyefis-data- The Data Status screen runs the updater from its
update_commandoption,~/.local/bin/pyefis-dataas shipped, which is why the symlink above goes there. A barepyefis-datawould fail under a systemd service, whosePATHexcludes~/.local/bin. - Environment selection lives in
~/.makerplane/pyefis/data.yaml(configurator_url:for device pairing / panel pull;base_url:for the nav-data packs, defaulting to production). -
~/.local/binis only onPATHfor a login shell that sourced~/.profileafter that directory existed — re-login ifpyefis-dataisn't found.
A fresh install and every pyEFIS update end with an explicit accept of this notice, at a terminal, before pyEFIS is started:
pyEfis is experimental software for Experimental / Amateur-Built aircraft. It is not certified. It is provided as-is with no warranty, and any use of it is at your own risk.
pyefis-accept-notice # or: python -m pyefis.install_ack (from the pyEFIS venv)It shows the full notice and requires the typed word accept. Anything else
stops with exit code 3 and records nothing. Once accepted, re-running it for the
same pyEFIS version just reports when and by whom. After a pyEFIS update
(git pull + pip install -e ~/src/pyEfis[qt,svs], or a new release), run it
again: a new pyEFIS version needs a new accept.
The record is written by the updater, not by pyEFIS: pyefis-accept-notice
hands over to pyefis-data accept --software pyefis=<version>, so the updater
must be installed first (above).
It looks for it in $PYEFIS_DATA, then on PATH, then at
~/pyefis-data-venv/bin/pyefis-data; --pyefis-data <path> overrides all three. The record is an append-only log at
~/.local/state/makerplane/acknowledgments.jsonl, outside both config trees, so a
config reset does not erase it. Its schema is in makerplane-data
docs/install_acknowledgment.md.
-
pyefis-accept-notice --checkexits 0 if this pyEFIS version is accepted and 3 if not, without prompting — for an install script. - Installing or updating data asks too.
pyefis-data updateat a terminal asks on its own. On the touchscreen, the Data Status screen's Update picker shows the same notice full-screen with Accept / Decline the first time (and after apyefis-dataupgrade); Decline installs nothing. - Nothing here runs at boot. The accept is asked for only on the ground, during install or update; pyEFIS itself never puts a modal in front of the instruments.
- An
sshsession without a terminal (ssh host cmd) cannot accept; usessh -t.
-
ldd <plugin>.so | grep 'not found'is the fastest way to diagnose "plugin found but could not load" — it's almost always a missing shared lib. - List available Qt platforms by running with
QT_QPA_PLATFORM=bogus(the fatal error prints the list), orQT_LOGGING_RULES="qt.qpa.*=true"for verbose init. - The
SIGUSR1 → /tmp/pyefis_screenshot.pnghandler is unreliable for verification — it grabs a fixed window, not necessarily the live screen. Trust the monitor.
-
fixgwrunning,ss -ltn | grep :3490shows a listener -
ldd $P/platforms/libqxcb.so | grep 'not found'→ empty -
xinit ~/start-pyefis.sh -- :0 vt1paints the EFIS on the monitor -
systemctl is-active fixgw pyefis→ active/active;NRestarts=0after a cold reboot -
pyefis-data statuslists the pack catalog (sourcesshowsnetwork: yes) -
pyefis-accept-notice --check→ exit 0 (notice accepted for this pyEFIS version) - instruments come alive once a FIX source is pointed at
fixgw:3490— until then they read stale /XXX, which is expected (see Customizing Beyond the Configurator)
Start here
Flying it
- The Panel at a Glance
- Attitude and Synthetic Vision
- Airspeed, Altitude and Vertical Speed
- The HSI
- Moving Map
- Flight Plan
- Engine Monitoring
- Data Status, Updates and Notices
Customizing
Install & Run
Instrument Reference
- Index
- Flight Instruments
- Attitude & SVS
- Navigation
- Engine & Data Gauges
- Text & Interactive
- System & Containers
- FIX Database Keys
Legacy