Installer
Developer guide for the HelixScreen installer infrastructure: modular shell scripts, platform detection, KIAUH integration, Moonraker updater, and the bats test suite.
User-facing install instructions: See the project README for quick-start installation commands.
Architecture Overview
Section titled “Architecture Overview”The installer is a modular POSIX shell system with 10 library modules that get bundled into monolithic scripts for end-user distribution. All shell code targets /bin/sh for maximum compatibility, including BusyBox on embedded platforms (AD5M, K1).
scripts/ install-dev.sh # Development installer (sources modules at runtime) install.sh # Bundled installer (auto-generated, committed) uninstall.sh # Bundled uninstaller (auto-generated, committed) bundle-installer.sh # Generates install.sh from modules bundle-uninstaller.sh # Generates uninstall.sh from modules helix-launcher.sh # Runtime launcher with watchdog supervision lib/installer/ common.sh # Logging, colors, error handler, process killing platform.sh # Platform/firmware detection, install paths, tmp dir permissions.sh # Root/sudo checks requirements.sh # Pre-flight: commands, deps, disk space, init system forgex.sh # ForgeX-specific: display config, screen.sh patching competing_uis.sh # Stop GuppyScreen, KlipperScreen, Xorg, stock UI release.sh # Download from R2 CDN/GitHub, extract, validate arch service.sh # systemd/SysV service install, platform hooks moonraker.sh # Moonraker update_manager configuration uninstall.sh # Uninstall, clean, re-enable previous UIs kiauh.sh # KIAUH extension auto-detection and install kiauh/helixscreen/ __init__.py # KIAUH extension constants and install dir detection helixscreen_extension.py # KIAUH BaseExtension implementation metadata.json # KIAUH extension metadata (display name, description)Module Dependencies
Section titled “Module Dependencies”Modules use source guards (_HELIX_*_SOURCED) to prevent double-sourcing. The load order in install-dev.sh matters — uninstall.sh must be last because it uses functions from other modules.
Bundled vs Development Installer
Section titled “Bundled vs Development Installer”install-dev.sh | install.sh | |
|---|---|---|
| Usage | Development, from repo checkout | End-user, via curl | sh |
| Modules | Sources from lib/installer/ at runtime | All modules inlined by bundle-installer.sh |
| Guard | Checks _HELIX_BUNDLED_INSTALLER is unset | Sets _HELIX_BUNDLED_INSTALLER=1 |
| Regeneration | N/A | ./scripts/bundle-installer.sh -o scripts/install.sh |
When modifying any module in lib/installer/, you must regenerate the bundled scripts:
./scripts/bundle-installer.sh -o scripts/install.sh./scripts/bundle-uninstaller.sh -o scripts/uninstall.shThe bundler uses awk to strip shebangs, SPDX headers, and source guards from each module, then concatenates them with the main orchestration code.
Installation Methods
Section titled “Installation Methods”1. One-Line Install (curl)
Section titled “1. One-Line Install (curl)”The primary end-user method. Downloads and runs the bundled install.sh:
curl -sSL https://raw.githubusercontent.com/prestonbrown/helixscreen/main/scripts/install.sh | shOptions:
| Flag | Description |
|---|---|
--update | Update existing installation (preserves config) |
--uninstall | Remove HelixScreen |
--clean | Remove old installation completely, then fresh install |
--version VER | Install specific version (e.g., --version v1.1.0) |
--local FILE | Install from a local archive (.zip or .tar.gz, skip download) |
2. Local Archive Install
Section titled “2. Local Archive Install”For devices without HTTPS support (e.g., AD5M with BusyBox wget):
# On your computer: download the release# On the device:sh /data/install.sh --local /data/helixscreen-ad5m.zipThe installer auto-detects when HTTPS is unavailable and prints manual download instructions.
3. KIAUH Extension
Section titled “3. KIAUH Extension”See the KIAUH Integration section below.
4. Development Install
Section titled “4. Development Install”From a repo checkout, the modular installer sources modules directly:
./scripts/install-dev.sh./scripts/install-dev.sh --update./scripts/install-dev.sh --uninstallInstallation Flow
Section titled “Installation Flow”The main() function orchestrates this sequence:
- Platform detection —
detect_platform()returnsad5m,k1,pi,pi32, orunsupported - Firmware detection — AD5M:
klipper_modorforge_x; K1:simple_aforstock_klipper - Path configuration —
set_install_paths()setsINSTALL_DIR,INIT_SCRIPT_DEST,PREVIOUS_UI_SCRIPT,TMP_DIR - Permission check — Root required on AD5M/K1; sudo on Pi
- Pre-flight checks — Required commands, runtime deps (libdrm2/libinput10 on Pi), disk space, init system detection
- Klipper ecosystem check — Verifies Klipper/Moonraker running (AD5M/K1 only, warns if missing)
- Platform configuration — ForgeX: display mode, screen.sh patching, logged wrapper
- Stop competing UIs — GuppyScreen, KlipperScreen, Xorg, stock FlashForge UI
- Download release — R2 CDN primary (
releases.helixscreen.org), GitHub Releases fallback - Extract with atomic swap — Validates ELF architecture, backs up config,
mvold to.old, rollback on failure - Platform hooks — Deploys
hooks-{platform}.shto$INSTALL_DIR/platform/hooks.sh - Install service — systemd unit or SysV init script (templated with
@@HELIX_USER@@, etc.) - Moonraker integration — Adds
[update_manager helixscreen]section, writesrelease_info.json - KIAUH extension — Auto-installs if KIAUH detected. Note: The
kiauh.shmodule exists but is not currently integrated into the installer flow. KIAUH extension files are installed manually or via the KIAUH UI. - Install-time printer detection — Tier-1 model fingerprint, falling back to Tier-2 Moonraker detection with a B/C confidence gate. Seeds device defaults (and, when confident, a full preset) into
settings.jsonbefore first launch. See Install-Time Printer Detection below. - Config symlink —
printer_data/config/helixscreensymlink for Mainsail/Fluidd access - Start service — Waits up to 5 seconds for startup confirmation
- Cleanup — Remove temp files, remove
.oldbackup
Install-Time Printer Detection
Section titled “Install-Time Printer Detection”After the release is in place but before the service starts, the installer tries to
recognize the printer and pre-seed settings.json so the first launch lands on (or near)
the right configuration without the user driving the whole wizard. The logic lives in
scripts/lib/installer/printer_seed.sh, orchestrated from main.sh:
seed_pid=$(detect_printer_model) # Tier-1if [ -n "$seed_pid" ]; then seed_settings_for_printer "$seed_pid" # device-level seed install_klipper_include_for_printer "$seed_pid"else seed_from_moonraker_detection || true # Tier-2fiIt runs in two tiers, Tier-1 first and Tier-2 only as a fallback:
Tier-1 — model fingerprint (detect_printer_model). A filesystem-based binary
fingerprint: it looks for a stock-firmware artifact at a known path that uniquely
identifies a model. Currently the only fingerprint shipped is the Sovol SV06 Ace, keyed on
the presence of the stock mksclient binary (e.g. /home/sovol/printer_data/build/mksclient).
On a match it seeds the printer’s device-level blocks (display/input) directly and
installs any Klipper include for that printer. This tier is intentionally narrow — it only
fires for signals strong enough to avoid false positives — so most installs fall through to
Tier-2.
Tier-2 — Moonraker detection (seed_from_moonraker_detection). When Tier-1 finds
nothing, the installer shells out to the freshly-installed binary:
helix-screen --detect-printer --host 127.0.0.1 --port 7125That one-shot queries the local Moonraker over REST and prints a JSON verdict (see
--detect-printer in DEVELOPMENT.md for
the exact shape). Tier-2 parses preset, confidence, and runner_up_confidence from that
verdict. It is a no-op (returns success without seeding) when Moonraker is unreachable, the
verdict carries no preset, or the JSON is malformed.
The B/C confidence gate
Section titled “The B/C confidence gate”Tier-2 decides what to seed using two numeric thresholds (overridable via environment):
| Variable | Default | Meaning |
|---|---|---|
HELIX_DETECT_MIN_CONFIDENCE | 85 | Minimum top-match confidence to auto-apply a full preset |
HELIX_DETECT_MIN_MARGIN | 10 | Minimum lead over the runner-up (confidence - runner_up_confidence) |
margin=$(( conf - rconf ))if [ "$conf" -ge "$HELIX_DETECT_MIN_CONFIDENCE" ] && [ "$margin" -ge "$HELIX_DETECT_MIN_MARGIN" ]; then # Detection B -> full preset seed_full_preset_for_printer "$preset"else # Detection C -> device-level seed + localhost host seed_settings_for_printer "$preset" _seed_moonraker_host_localhostfiNote these are not lettered confidence levels (there is no A/D). Confidence is a continuous 0-100 score; “B” and “C” are simply the two seeding paths the gate selects:
-
Path B (confident:
confidence >= 85ANDmargin >= 10). Auto-apply the full preset viaseed_full_preset_for_printer. This writes the preset’sdisplayblock, merges itsprinterblock (heaters, fans, LEDs, filament sensors) intoprinters["default"], sets the top-level"preset"marker so the app knows a preset is already applied, and setsprinters["default"]["wizard_completed"] = falseso the wizard still runs once for the user to verify rather than silently trusting the seed. -
Path C (ambiguous:
confidence < 85ORmargin < 10). Seed only the safe device-level blocks (input,display) viaseed_settings_for_printer, then pre-fillprinters["default"]["moonraker_host"] = "127.0.0.1"so the app can reach Moonraker on first launch. Crucially it does not write the"preset"marker — the app re-runs its own detection at startup and asks the user to confirm, rather than committing to an uncertain guess. -
Skip (no-op). Moonraker unreachable, no
presetin the verdict, or malformed JSON: nothing is seeded and the installer continues.
Seeded printer ids are recorded to ${INSTALL_DIR}/config/.seeded_settings (idempotently)
so uninstall can be seed-aware.
Because Path B’s seed can be wrong on an ambiguous-but-just-over-threshold match, it is
recoverable: re-running the app with --wizard (see
DEVELOPMENT.md) clears the "preset" marker and host, turning
the next launch back into a full wizard.
Error Handling
Section titled “Error Handling”The installer uses trap 'error_handler $LINENO' ERR to catch failures. On error:
- Reports the failing line number and exit code
- Cleans up temp files
- Restores backed-up configuration if the install was partially complete
- Prints help resources
Atomic Extraction with Rollback
Section titled “Atomic Extraction with Rollback”The extract_release() function in release.sh implements a safe upgrade path:
- Extract archive to a temp directory
- Validate the
helix-screenbinary exists and has correct ELF architecture - Move existing
$INSTALL_DIRto$INSTALL_DIR.old - Move extracted content to
$INSTALL_DIR - Restore user config from backup
- If step 4 fails, automatically roll back from
.old
NoNewPrivileges and Self-Update on Pi (systemd)
Section titled “NoNewPrivileges and Self-Update on Pi (systemd)”When helix-screen performs a self-update (user presses “Check for Updates”, the binary downloads a new archive and spawns install.sh), the installer runs as a subprocess of the helix-screen systemd service.
The Pi systemd unit includes:
[Service]NoNewPrivileges=trueThis is a hardening flag that prevents any process in the service’s cgroup — including child processes — from gaining new privileges. Concretely: sudo is completely non-functional inside install.sh when spawned by helix-screen.
Affected operations and how they are handled:
| Operation | Old behavior | Fixed behavior |
|---|---|---|
fix_install_ownership() — chown files to klipper user | sudo chown → fatal exit | Warns and continues; not critical for self-update |
Remove stale $INSTALL_DIR.old | sudo rm -rf | Try plain rm -rf first; fall back to timestamped name (*.old.TIMESTAMP) |
Cleanup .old* dirs post-install | sudo rm -rf | Try plain rm -rf first; warn and skip if blocked |
Root-owned .old directory — a common scenario after a manual root-level install leaves behind a root-owned helixscreen.old/. The pi user cannot remove it even with sudo blocked by NoNewPrivileges. The installer detects this and creates a timestamped fallback (helixscreen.old.1234567890). A one-time manual cleanup is needed on the Pi:
# Diagnose: find files not owned by the current userfind ~/helixscreen* ! -user "$(id -un)" 2>/dev/null
# Fix: remove root-owned stale backupsudo rm -rf ~/helixscreen.oldDesign principle: Under NoNewPrivileges, the installer must complete the core swap (mv old → .old, mv new → INSTALL_DIR, restore config) without sudo. Anything that requires sudo must be either non-fatal or deferred to a manual step.
Platform-Specific Installation
Section titled “Platform-Specific Installation”Raspberry Pi (pi, pi32)
Section titled “Raspberry Pi (pi, pi32)”| Setting | Value |
|---|---|
| Detection | /etc/os-release contains Debian/Raspbian, or /home/pi, /home/biqu, /home/mks exists |
| 32/64-bit | getconf LONG_BIT determines userspace bitness (64-bit kernel with 32-bit userspace is common) |
| Install dir | Auto-detected based on Klipper ecosystem: ~/helixscreen if klipper/moonraker/printer_data found, else /opt/helixscreen |
| Klipper user | Detected via systemd service owner, process table, printer_data scan, or well-known users (biqu, pi, mks) |
| Init system | systemd (service template with @@HELIX_USER@@ substitution) |
| Runtime deps | libdrm2, libinput10 installed via apt |
| Config symlink | ~/printer_data/config/helixscreen -> $INSTALL_DIR/config for web UI access |
FlashForge Adventurer 5M — Forge-X Firmware (ad5m, forge_x)
Section titled “FlashForge Adventurer 5M — Forge-X Firmware (ad5m, forge_x)”| Setting | Value |
|---|---|
| Detection | armv7l + kernel contains ad5m or 5.4.61 |
| Firmware | Forge-X detected by /opt/config/mod/.root directory |
| Install dir | /opt/helixscreen |
| Init script | /etc/init.d/S90helixscreen |
| Previous UI | /opt/config/mod/.root/S80guppyscreen |
ForgeX-specific patches (all reversible on uninstall):
- Display mode: Sets
variables.cfgdisplay toGUPPYmode (required for backlight) - GuppyScreen disable:
chmod -xon/opt/config/mod/.root/S80guppyscreen - tslib disable:
chmod -xon/opt/config/mod/.root/S35tslib - Stock UI disable: Comments out
ffstartup-armin/opt/auto_run.sh - screen.sh backlight patch: Blocks non-100 backlight changes when HelixScreen active (allows S99root init cycle)
- screen.sh drawing patch: Skips
draw_splash,draw_loading,boot_messagewhen HelixScreen active - logged wrapper: Wraps
/opt/config/mod/.bin/exec/loggedto strip--send-to-screenflag (prevents direct framebuffer writes)
FlashForge Adventurer 5M — Klipper Mod (ad5m, klipper_mod)
Section titled “FlashForge Adventurer 5M — Klipper Mod (ad5m, klipper_mod)”| Setting | Value |
|---|---|
| Firmware | Detected by /root/printer_software or /mnt/data/.klipper_mod |
| Install dir | /root/printer_software/helixscreen |
| Init script | /etc/init.d/S80helixscreen |
| Previous UI | /etc/init.d/S80klipperscreen |
| Xorg | Stopped and disabled (S40xorg) since HelixScreen uses fbdev directly |
Creality K1 Series — Simple AF (k1, simple_af)
Section titled “Creality K1 Series — Simple AF (k1, simple_af)”| Setting | Value |
|---|---|
| Detection | Buildroot OS + /usr/data + 2+ K1 indicators (pellcorp, printer_data, get_sn_mac.sh, etc.) |
| Install dir | /usr/data/helixscreen |
| Init script | /etc/init.d/S99helixscreen |
| Previous UI | /etc/init.d/S99guppyscreen |
K2 / Other Platforms
Section titled “K2 / Other Platforms”K2 support exists in the release_info.json asset naming (helixscreen-k2.zip) but platform detection is not yet implemented in detect_platform().
KIAUH Integration
Section titled “KIAUH Integration”KIAUH (Klipper Installation And Update Helper) is the standard tool for managing Klipper ecosystem components.
Extension Structure
Section titled “Extension Structure”The KIAUH extension lives in scripts/kiauh/helixscreen/ and consists of three files:
metadata.json — Extension metadata for KIAUH’s menu system:
{ "metadata": { "index": 14, "module": "helixscreen_extension", "maintained_by": "prestonbrown", "display_name": "HelixScreen", "description": ["Modern touchscreen interface for Klipper..."], "repo": "https://github.com/prestonbrown/helixscreen", "updates": true }}__init__.py — Constants and install directory detection:
HELIXSCREEN_INSTALLER_URL— URL to the bundledinstall.shfind_install_dir()— Scans platform-dependent paths for existing installation
helixscreen_extension.py — BaseExtension subclass with three operations:
install_extension()— Downloads and runsinstall.shupdate_extension()— Runsinstall.sh --updateremove_extension()— Runsinstall.sh --uninstall
How the Extension Gets Installed
Section titled “How the Extension Gets Installed”During installation, install_kiauh_extension() in lib/installer/kiauh.sh:
- Calls
detect_kiauh_dir()to find~/kiauh/kiauh/extensions/or/home/*/kiauh/kiauh/extensions/ - If KIAUH is found and extension source files exist in the release package (
$INSTALL_DIR/scripts/kiauh/helixscreen/) - Copies
__init__.py,helixscreen_extension.py, andmetadata.jsonto the KIAUH extensions directory - Installs by default when KIAUH is detected;
--skip-kiauh-registrationopts out - On updates, silently updates the extension files
Updating the KIAUH Extension
Section titled “Updating the KIAUH Extension”When modifying the extension:
- Edit files in
scripts/kiauh/helixscreen/ - The extension files are included in release archives and auto-updated during
--update - Run the KIAUH extension bats tests to verify structural correctness
Important: metadata.json Structure
Section titled “Important: metadata.json Structure”The metadata top-level key is required (GitHub issue #3 was caused by this being missing). The bats tests validate this structure to prevent regressions.
Moonraker Update Manager Integration
Section titled “Moonraker Update Manager Integration”The installer configures Moonraker to enable one-click updates from Mainsail/Fluidd web UIs.
What Gets Configured
Section titled “What Gets Configured”-
[update_manager helixscreen]section appended tomoonraker.conf:[update_manager helixscreen]type: webchannel: stablerepo: prestonbrown/helixscreenpath: /home/biqu/helixscreenpersistent_files:config/settings.jsonconfig/helixscreen.envconfig/.disabled_services -
release_info.jsonwritten to$INSTALL_DIR/— Moonrakertype:webneeds this to detect the installed version -
moonraker.asvc— HelixScreen added to Moonraker’s service allowlist so it can restart the service after updates
Config Survival on Updates
Section titled “Config Survival on Updates”Moonraker’s type: web updater wipes the install directory (shutil.rmtree) before extracting each update. Config is preserved via three layers:
persistent_filesin moonraker.conf — Moonraker backs up listed files before rmtree and restores them after extraction- Rolling backups —
Config::save()maintains backups in/var/lib/helixscreen/(systemdStateDirectory) and$HOME/.helixscreen/(fallback).Config::init()auto-restores from these if the config file is missing after an update. - SysV init script — On BusyBox systems (K1, AD5M) where systemd isn’t available, the init script exports
HOME=/rootand creates/var/lib/helixscreen/so backup paths are persistent (not volatile/tmp/).
The installer also runs ensure_persistent_files() on every upgrade to add persistent_files to existing Moonraker configs that predate this feature.
Migration
Section titled “Migration”The installer detects old type: git_repo and type: zip configurations and auto-migrates them to type: web, cleaning up the sparse clone directory.
moonraker.conf Discovery
Section titled “moonraker.conf Discovery”The find_moonraker_conf() function searches in this order:
$KLIPPER_HOME/printer_data/config/moonraker.conf(detected user)- Static fallbacks:
/home/pi/...,/home/biqu/...,/home/mks/...,/root/...,/opt/config/...,/usr/data/...
Skipped Platforms
Section titled “Skipped Platforms”Moonraker update_manager is skipped on AD5M (typically no Mainsail/Fluidd web UI).
Uninstaller
Section titled “Uninstaller”The uninstaller (scripts/uninstall.sh) reverses the installation:
- Stop service — systemd or SysV, plus kill remaining processes (watchdog first to prevent crash dialog)
- Remove service — Delete systemd unit or init script
- Re-enable disabled services — Reads
config/.disabled_servicesstate file and re-enables each recorded entry - Remove installation — Checks all known paths:
/opt/helixscreen,/root/printer_software/helixscreen,/usr/data/helixscreen - Restore previous UI — Platform-specific:
- Klipper Mod: Re-enable Xorg and KlipperScreen
- K1: Re-enable GuppyScreen
- ForgeX: Full cleanup via
uninstall_forgex()(restore display mode, unpatch screen.sh, remove logged wrapper, re-enable GuppyScreen/tslib)
- Remove caches — Thumbnail caches, temp files, PID files, log files
- Remove Moonraker section — Strips
[update_manager helixscreen]from moonraker.conf
Disabled Services State File
Section titled “Disabled Services State File”The installer tracks what it disabled in $INSTALL_DIR/config/.disabled_services:
systemd:KlipperScreensysv-chmod:/etc/init.d/S80klipperscreensysv-chmod:/etc/init.d/S40xorgThe uninstaller reads this file and reverses each action (systemd enable, chmod +x). This is listed in persistent_files in the Moonraker config so it survives zip updates.
Release Download System
Section titled “Release Download System”R2 CDN (Primary)
Section titled “R2 CDN (Primary)”Downloads go through releases.helixscreen.org (Cloudflare R2 bucket):
- Fetch
stable/manifest.jsonfor latest version and per-platform download URLs - Download the platform-specific archive from R2
GitHub Releases (Fallback)
Section titled “GitHub Releases (Fallback)”If R2 is unavailable or returns a corrupt file:
- Query
api.github.com/repos/.../releases/latestfor the tag name - Download from
github.com/.../releases/download/{version}/{filename}
HTTPS Capability Check
Section titled “HTTPS Capability Check”On embedded platforms (AD5M), BusyBox wget does not support HTTPS. The installer:
- Tests curl HTTPS, then wget HTTPS
- If neither works, prints step-by-step manual install instructions with
scpcommands
Architecture Validation
Section titled “Architecture Validation”After extraction, validate_binary_architecture() reads the ELF header (first 20 bytes) to verify:
- ELF magic bytes
- ELF class (32-bit vs 64-bit)
- Machine type (ARM vs AARCH64)
This prevents installing a Pi binary on AD5M or vice versa.
Shell Test Infrastructure (bats)
Section titled “Shell Test Infrastructure (bats)”The installer has 543 test cases across 30 bats files (~6700 lines of test code), making it one of the most thoroughly tested shell installer systems for 3D printer firmware.
Running Tests
Section titled “Running Tests”# Run all shell testsbats tests/shell/
# Run a specific test filebats tests/shell/test_platform_detection.bats
# Run with verbose outputbats --verbose-run tests/shell/test_platform_detection.batsTest Organization
Section titled “Test Organization”| Test File | Coverage |
|---|---|
test_platform_detection.bats | Pi 32/64-bit detection, AD5M/K1 identification |
test_platform_hooks.bats | Platform hook deployment |
test_pi_install_path.bats | Pi install directory auto-detection cascade |
test_user_detection.bats | Klipper user detection (systemd, process, printer_data, well-known) |
test_forgex_boot.bats | ForgeX boot patches, screen.sh, logged wrapper |
test_arch_validation.bats | ELF header parsing, architecture mismatch detection |
test_download_validation.bats | Archive validation, HTTPS capability |
test_r2_installer.bats | R2 CDN manifest parsing, fallback to GitHub |
test_extract_release.bats | Extraction, atomic swap, rollback |
test_release_packaging.bats | Release archive structure |
test_service_install.bats | systemd/SysV service installation |
test_service_template.bats | Service template placeholder substitution |
test_moonraker_config.bats | update_manager section add/remove/migrate |
test_moonraker_paths.bats | moonraker.conf discovery across platforms |
test_config_symlink.bats | printer_data config symlink creation |
test_uninstall.bats | Full uninstall flow, cache cleanup, UI restore |
test_disabled_services.bats | Service disable/re-enable state tracking |
test_requirements.bats | Command checking, disk space, init system detection |
test_detect_tmp_dir.bats | Temp directory selection with space checking |
test_kiauh_extension.bats | KIAUH metadata.json structure, Python syntax |
test_kiauh_installer.bats | KIAUH extension install/update logic |
test_klipper_check.bats | Klipper/Moonraker ecosystem pre-flight |
test_monolithic_installer.bats | Bundled install.sh/uninstall.sh structural checks |
test_helix_launcher.bats | Launcher script, env file sourcing, watchdog |
test_generate_manifest.bats | Release manifest generation |
test_no_echo_ansi.bats | No raw ANSI in echo (BusyBox compat) |
test_code_lint.bats | Shell code quality checks |
test_symbol_extraction.bats | Debug symbol extraction for crash reporting |
test_telemetry_pull.bats | Telemetry data pull scripts |
test_resolve_backtrace.bats | Backtrace symbol resolution |
Test Helpers
Section titled “Test Helpers”tests/shell/helpers.bash provides shared utilities:
mock_command— Create a mock executable that outputs specific textmock_command_fail— Create a mock that exits non-zeromock_command_script— Create a mock with custom shell logicsetup_mock_pi— Create temp directory structure mimicking a Pi systemcreate_fake_elf/create_fake_arm32_elf/create_fake_aarch64_elf— Generate minimal ELF headers for architecture validation testsSUDO=""— Exported no-op for tests that call$SUDO- Logging stubs (
log_info,log_warn, etc.) suppressed during tests
Writing New Tests
Section titled “Writing New Tests”Pattern for a new test file:
#!/usr/bin/env bats# SPDX-License-Identifier: GPL-3.0-or-later
WORKTREE_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
setup() { load helpers
# Reset globals unset _HELIX_MYMODULE_SOURCED . "$WORKTREE_ROOT/scripts/lib/installer/mymodule.sh"}
@test "my function does the right thing" { result=$(my_function "arg") [ "$result" = "expected" ]}
@test "my function handles errors" { run my_function "bad_arg" [ "$status" -ne 0 ]}Key patterns:
- Use
WORKTREE_ROOT(not hardcoded paths) so tests work in git worktrees unset _HELIX_*_SOURCEDbefore sourcing modules to reset source guards- Use
$BATS_TEST_TMPDIRfor temp files (auto-created, auto-cleaned) - Mock system commands by prepending
$BATS_TEST_TMPDIR/binto$PATH
Developer Guide: Adding Platform Support
Section titled “Developer Guide: Adding Platform Support”Step 1: Platform Detection
Section titled “Step 1: Platform Detection”Add a new case in detect_platform() in scripts/lib/installer/platform.sh. Detection must be reliable and specific — avoid false positives on other ARM devices.
# In detect_platform():if [ "$arch" = "aarch64" ] && is_my_platform; then echo "myplatform" returnfiStep 2: Install Paths
Section titled “Step 2: Install Paths”Add a case in set_install_paths():
elif [ "$platform" = "myplatform" ]; then INSTALL_DIR="/path/to/helixscreen" INIT_SCRIPT_DEST="/etc/init.d/S90helixscreen" PREVIOUS_UI_SCRIPT="/path/to/previous/ui"Step 3: Platform Hooks (Optional)
Section titled “Step 3: Platform Hooks (Optional)”If the platform needs runtime hooks (pre-start/post-start behavior), create config/platform/hooks-myplatform.sh in the release package and add the mapping in install_platform_hooks() in the bundled installer.
Step 4: Firmware-Specific Module (Optional)
Section titled “Step 4: Firmware-Specific Module (Optional)”For platforms with complex setup (like ForgeX), create a dedicated module lib/installer/myplatform.sh:
- Add source guard
- Implement install-time and uninstall-time functions
- Source it in
install-dev.shand add to thebundle-installer.shmodule list
Step 5: Tests
Section titled “Step 5: Tests”Create tests/shell/test_myplatform.bats covering:
- Platform detection (positive and negative cases)
- Install path configuration
- Any firmware-specific patching
- Uninstall/restore behavior
Step 6: Regenerate Bundles
Section titled “Step 6: Regenerate Bundles”./scripts/bundle-installer.sh -o scripts/install.sh./scripts/bundle-uninstaller.sh -o scripts/uninstall.shStep 7: Release Asset
Section titled “Step 7: Release Asset”Add the platform to the CI/CD build matrix so release archives are generated. Update the write_release_info() case statement in moonraker.sh with the asset name.
Troubleshooting
Section titled “Troubleshooting””HTTPS Download Not Available”
Section titled “”HTTPS Download Not Available””Cause: BusyBox wget on AD5M/K1 doesn’t support HTTPS.
Fix: Download the archive on another computer and use --local:
scp -O helixscreen-ad5m.zip root@printer-ip:/data/ssh root@printer-ip "sh /data/install.sh --local /data/helixscreen-ad5m.zip"“Architecture mismatch”
Section titled ““Architecture mismatch””Cause: Wrong release archive for the platform (e.g., Pi binary on AD5M).
Fix: Ensure you download the correct platform variant. The installer validates ELF headers before proceeding.
”Insufficient disk space”
Section titled “”Insufficient disk space””Cause: The target filesystem needs at least 50MB free, plus temp space for extraction (~3x archive size).
Fix: Free space, or override the temp directory: TMP_DIR=/path/with/space sh install.sh
”Failed to extract archive: no space left on device”
Section titled “”Failed to extract archive: no space left on device””Cause: Temp directory ran out of space during extraction.
Fix: The installer tries multiple temp locations (/data/, /mnt/data/, /var/tmp/, /tmp/), picking the first with 100MB+ free. Override with TMP_DIR= env var.
ForgeX: Screen flickers or goes blank after install
Section titled “ForgeX: Screen flickers or goes blank after install”Cause: ForgeX display mode not set correctly, or screen.sh patches didn’t apply.
Fix: Check display mode: grep display /opt/config/mod_data/variables.cfg — should be GUPPY. Verify patches: grep helixscreen_active /opt/config/mod/.shell/screen.sh.
Moonraker update_manager not working
Section titled “Moonraker update_manager not working”Cause: Missing release_info.json, wrong section type, or service not in moonraker.asvc.
Fix:
- Check
release_info.jsonexists in install dir - Verify section is
type: zip(notgit_repo) - Ensure
helixscreenis inprinter_data/moonraker.asvc - Restart Moonraker:
systemctl restart moonraker
Uninstall doesn’t restore previous UI
Section titled “Uninstall doesn’t restore previous UI”Cause: The previous UI init script wasn’t found or config/.disabled_services was deleted.
Fix: Manually re-enable the previous UI:
# ForgeXchmod +x /opt/config/mod/.root/S80guppyscreen
# K1chmod +x /etc/init.d/S99guppyscreen
# Klipper Modchmod +x /etc/init.d/S40xorgchmod +x /etc/init.d/S80klipperscreenKIAUH extension not showing up
Section titled “KIAUH extension not showing up”Cause: Extension files not copied to KIAUH’s extensions directory.
Fix: Manually copy:
cp -r /opt/helixscreen/scripts/kiauh/helixscreen ~/kiauh/kiauh/extensions/“sudo: The ‘no new privileges’ flag is set, which prevents sudo from running as root”
Section titled ““sudo: The ‘no new privileges’ flag is set, which prevents sudo from running as root””Cause: The helix-screen systemd service has NoNewPrivileges=true. When a self-update is triggered from the UI, install.sh runs as a child of the service and inherits this restriction — sudo is fully non-functional.
What this affects:
- Chowning files to the klipper user (non-fatal, a warning is logged)
- Removing a root-owned stale
helixscreen.oldbackup from a prior manual install
Fix for root-owned stale backup: Manually clean it up on the Pi before or after an update:
# Check what's root-ownedfind ~/helixscreen* ! -user "$(id -un)" 2>/dev/null
# Remove itsudo rm -rf ~/helixscreen.oldThe installer handles this gracefully: if it cannot remove the stale .old directory, it renames the new backup to helixscreen.old.TIMESTAMP so the atomic swap can still proceed.