A service for managing over-the-air (OTA) updates for Librescoot vehicles.
Part of the Librescoot open-source platform.
The Update Service is responsible for:
- Checking a configurable endpoint (GitHub Releases API) for available updates specific to its component and channel (stable, testing, nightly).
- Orchestrating the download and installation of updates using Mender.
- Tracking download and installation progress via Redis.
- Managing power states and update inhibitions to ensure safe update application.
- Rebooting the specific component's system if necessary, adhering to defined constraints.
- Component-Specific Instances: Runs as separate, focused services for MDB and DBC updates.
- GitHub Releases API Integration: For update discovery.
- Startup Commit Check: Ensures that any update pending from a previous run is properly committed.
- Power Management Integration: Uses an inhibitor client to coordinate with vehicle power states, preventing updates during critical operations.
- Safe Update Application: Manages vehicle state and update inhibitions.
- Controlled Reboots: Schedules reboots based on component-specific rules and vehicle state (e.g., MDB reboots only in stand-by).
- Dry-Run Mode: Allows testing update logic without performing actual reboots.
- Redis-Based State and Communication: Uses Redis for status tracking and inter-service communication.
- Flexible Update Sources: Supports updates from local files or remote URLs (both full and delta updates).
The service is typically built and installed using the provided Makefile.
# Clone the repository
git clone https://github.com/librescoot/update-service.git
cd update-service
# Build the distribution binary (ARM)
make dist
# This creates ./update-service-arm-dist
# Install the binary (requires root)
make install
# This copies ./update-service-arm-dist to /usr/bin/update-service
# Install systemd services (requires sudo)
# The repository includes service files like librescoot-update-mdb.service and librescoot-update-dbc.service.
# These should be copied to /etc/systemd/system/. For example:
sudo cp librescoot-update-mdb.service /etc/systemd/system/
sudo cp librescoot-update-dbc.service /etc/systemd/system/
# Then, enable and start the services:
sudo systemctl daemon-reload
sudo systemctl enable librescoot-update-mdb.service
sudo systemctl start librescoot-update-mdb.service
sudo systemctl enable librescoot-update-dbc.service
sudo systemctl start librescoot-update-dbc.serviceThe service is typically run as a systemd service. Each instance (MDB, DBC) is configured via its respective service file.
The binary itself requires the --component flag.
Manual execution (example):
# Run for MDB component
./update-service --component=mdb --channel=nightly
# Run for DBC component with dry-run
./update-service --component=dbc --channel=stable --dry-run --redis-addr=127.0.0.1:6379The Makefile provides convenience targets for running locally:
# Run for MDB (nightly, dry-run)
make run-mdb
# Run for DBC (nightly, dry-run)
make run-dbcThe service can be configured via command-line flags or Redis settings. CLI flags take precedence over Redis settings.
| Flag | Description | Default | Required | Redis Configurable |
|---|---|---|---|---|
--component |
Component to manage updates for. | "" |
Yes (must be mdb or dbc) |
No (CLI only) |
--redis-addr |
Redis server address. | localhost:6379 |
No | No (CLI only) |
--channel |
Update channel to track. | nightly |
No | Yes |
--releases-url |
Release index base URL for update discovery. | https://downloads.librescoot.org/releases |
No | Yes |
--check-interval |
Interval between update checks. | 6h |
No | Yes |
--dry-run |
If true, log reboot actions instead of performing them. | false |
No | Yes |
Note: --component and --redis-addr are CLI-only and cannot be configured via Redis.
Settings can be configured per-component in the Redis settings hash. The update service monitors the settings channel for changes and applies them at runtime.
Setting Keys:
updates.{component}.channel- Update channel (stable,testing, ornightly)updates.{component}.check-interval- Check interval (e.g.,6h,1h,30m)updates.{component}.releases-url- Release index base URLupdates.{component}.dry-run- Dry-run mode (trueorfalse)updates.{component}.method- Update method (fullordelta)
Examples:
# Set MDB to stable channel
redis-cli HSET settings updates.mdb.channel stable
redis-cli PUBLISH settings updates.mdb.channel
# Set DBC check interval to 12 hours
redis-cli HSET settings updates.dbc.check-interval 12h
redis-cli PUBLISH settings updates.dbc.check-interval
# Enable delta updates for MDB
redis-cli HSET settings updates.mdb.method delta
redis-cli PUBLISH settings updates.mdb.method
# Enable dry-run for testing
redis-cli HSET settings updates.dbc.dry-run true
redis-cli PUBLISH settings updates.dbc.dry-runPriority: CLI flags (if specified) > Redis settings > hardcoded defaults
Many previous Redis key configurations are now handled internally based on the specified --component.
The update service listens for commands on the scooter:update list. Commands can be sent using Redis LPUSH.
Available Commands:
check-now- Immediately trigger an update check, bypassing the configured check interval
update-from-file:/path/to/file.mender- Update from a local Mender fileupdate-from-file:/path/to/file.mender#sha256=checksum- Update from local file with checksum verificationupdate-from-url:https://example.com/file.mender- Update from a remote URLupdate-from-url:https://example.com/file.mender#sha256=checksum- Update from URL with checksum verification
Examples:
# Force an immediate update check
redis-cli LPUSH scooter:update check-now
# This triggers update checks for all running update-service instances (both MDB and DBC)
# Update from a local file (auto-detects checksum if provided)
redis-cli LPUSH scooter:update:dbc "update-from-file:/data/ota/librescoot-unu-dbc-nightly-20251212T024719.mender"
# Update from a URL with checksum verification
redis-cli LPUSH scooter:update:dbc "update-from-url:https://github.com/librescoot/librescoot/releases/download/nightly-20251212T024719/librescoot-unu-dbc-nightly-20251212T024719.mender#sha256=abc123..."
# Update specific component only
redis-cli LPUSH scooter:update:mdb "update-from-file:/data/ota/librescoot-unu-mdb-nightly-20251212T024719.mender"Auto-Detection:
- URLs are automatically detected by
http://,https://, orfile://prefixes - All other paths are treated as local file paths
Update Method Selection:
- The service automatically chooses between delta and full updates based on:
- The configured update method (
updates.{component}.methodin Redis) - Availability of the base Mender file for the current version
- If delta is configured but no base file exists, falls back to full update
- The configured update method (
Checksum Format:
- Only SHA256 checksums are supported
- Preferred: append
#sha256=<hexdigest>to the file path or URL (keeps URLs valid; the fragment is stripped before download) - Legacy
:sha256:<hexdigest>is still accepted - Applies only to the
update-from-file/update-from-urlcommands; scheduled channel updates are not checksum-verified
Note: The check-now command is useful for:
- Testing update functionality without waiting for the next scheduled check
- Manually checking for updates after deploying new releases
- Forcing an update check after changing update settings
Note: Custom update sources are useful for:
- Testing new releases before publishing to GitHub
- Installing updates from alternative sources
- Development and debugging scenarios
- Manual update deployment with specific files
- DBC updates should not turn off the DBC during the update process.
- The vehicle must remain capable of locking and becoming un-drivable during DBC updates.
- Custom update sources work the same way as automatic updates - the service handles power management automatically.
- MDB updates can generally be installed at any time the vehicle is not in a critical state.
- MDB reboots should only occur when the scooter is in stand-by mode, managed via the power inhibitor client.
- Custom update sources respect the same reboot constraints as automatic updates.
Note: When using custom update sources with the delta update method:
- The service requires a base Mender file for the current version to exist in the download directory
- If no base file is found, the update automatically falls back to full update mode
- The service automatically detects the current version from Redis (
version:{component})
The Update Service operates as component-specific instances. Each instance includes:
- Main Application: Parses flags, sets up logging, and initializes clients.
- Config: Holds runtime configuration derived from flags.
- Redis Client: Handles all communication with the Redis server for state and messaging.
- Inhibitor Client: Communicates with a power management or vehicle state service (via Redis) to request and release update/reboot inhibitions. This ensures updates and reboots only happen at safe times.
- Updater:
- Contains the core logic for the update lifecycle.
- Fetches release information from the GitHub API.
- Compares current version with available updates for its assigned component and channel.
- Manages the download and installation process (interacting with Mender tools via Redis messages).
- Handles post-installation steps, including reboots, respecting inhibitions.
- Performs a startup check to commit any pending updates.
The Update Service uses Redis to track update state and communicate with other services. All keys are stored in the ota hash.
| Key | Type | Description | Values |
|---|---|---|---|
status:{component} |
String | Current update status | idle, downloading, installing, rebooting, error |
update-version:{component} |
String | Target version being installed | Version string (e.g., 20251009t162327) |
download-progress:{component} |
Integer | Download progress percentage (0-100) | 0 to 100 |
download-bytes:{component} |
Integer | Bytes downloaded so far | Byte count (e.g., 12582912) |
download-total:{component} |
Integer | Total download size in bytes | Byte count (e.g., 104857600) |
error:{component} |
String | Error type when status is error |
invalid-release-tag, download-failed, install-failed, reboot-failed |
error-message:{component} |
String | Human-readable error message when status is error |
Detailed error message |
Status Meanings:
rebooting: Update is installed and will be applied on next reboot/power cycle- MDB: Service waits for vehicle to be in standby for 3 minutes, then actively triggers reboot
- DBC: Update will be applied on next natural power-on (no active reboot triggered)
Examples:
status:mdb→downloadingupdate-version:mdb→20251009t162327download-progress:mdb→45download-bytes:mdb→47185920download-total:mdb→104857600error:dbc→download-failederror-message:dbc→Failed to download update: connection timeout
Note: Error and download progress keys are automatically cleared when:
- The service starts/restarts
- An update completes successfully and status returns to
idle - An error occurs (clears download progress only)
# Tidy, Format, Test
make tidy fmt test
# Build for host (development)
make host
# This creates ./update-service-host
# Run for MDB component in development (nightly, dry-run)
./update-service-host --component=mdb --channel=nightly --dry-run --check-interval=1m
# Run for DBC component in development (nightly, dry-run)
./update-service-host --component=dbc --channel=nightly --dry-run --check-interval=1mThis project is dual-licensed. The source code is available under the GNU Affero General Public License v3.0. The maintainers reserve the right to grant separate licenses for commercial distribution; please contact the maintainers to discuss commercial licensing.
