Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 64 additions & 5 deletions .github/workflows/release-arm64-deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ jobs:
install -m 0755 /work/deploy/debian/prerm /work/pkgroot/DEBIAN/prerm

dpkg-deb --build --root-owner-group /work/pkgroot "/work/dist/fuelflux_${DEB_VERSION}_arm64.deb"
install -m 0755 /work/pkgroot/opt/fuelflux/bin/fuelflux "/work/dist/fuelflux_${DEB_VERSION}_arm64"
cd /work/dist
sha256sum "fuelflux_${DEB_VERSION}_arm64.deb" "fuelflux_${DEB_VERSION}_arm64" > SHA256SUMS
'

- name: Test Debian install/uninstall in Ubuntu arm64 container
Expand All @@ -95,9 +98,7 @@ jobs:
ubuntu:22.04 bash -lc '
set -euo pipefail
apt-get update
apt-get install -y --no-install-recommends \
ca-certificates passwd adduser \
libsqlite3-0 libgpiod2 libssl3 libnfc6 libcurl4 libfmt8 libstdc++6
apt-get install -y --no-install-recommends passwd adduser binutils file

cat > /usr/local/bin/systemctl << "EOF"
#!/bin/sh
Expand All @@ -111,13 +112,38 @@ jobs:
echo "Expected package not found: ${deb_pkg}" >&2
exit 1
fi
dpkg -i "${deb_pkg}"
# Resolve only the declared package dependencies in a clean image.
apt-get install -y --no-install-recommends "${deb_pkg}"

test -x /opt/fuelflux/bin/fuelflux
test -f /lib/systemd/system/fuelflux.service
getent passwd fuelflux > /dev/null

raw="/work/dist/fuelflux_${DEB_VERSION}_arm64"
test -s "${raw}"
test "$(dpkg-deb -f "${deb_pkg}" Architecture)" = arm64
readelf -h "${raw}" | grep -E "Machine:.*AArch64"
file "${raw}" | grep -E "ELF 64-bit.*ARM aarch64"
cmp "${raw}" /opt/fuelflux/bin/fuelflux
# ldd must succeed and every runtime library must be available.
ldd "${raw}" > /tmp/fuelflux-ldd.txt
cat /tmp/fuelflux-ldd.txt
if grep -q "not found" /tmp/fuelflux-ldd.txt; then exit 1; fi
cd /work/dist
test "$(wc -l < SHA256SUMS)" -eq 2
sha256sum -c SHA256SUMS

# Upgrades and removal must retain per-device settings and data.
printf "FUELFLUX_CONTROLLER_ID=000-factory-test\nFUELFLUX_BACKEND_URL=https://example.org/fleet\n" > /etc/fuelflux/fuelflux.env
printf "preserve-data\n" > /var/fuelflux/db/installation-test
cp /etc/fuelflux/fuelflux.env /tmp/expected.env
dpkg -i "${deb_pkg}"
cmp /etc/fuelflux/fuelflux.env /tmp/expected.env

dpkg -r fuelflux
test ! -e /opt/fuelflux/bin/fuelflux
cmp /etc/fuelflux/fuelflux.env /tmp/expected.env
grep -qx preserve-data /var/fuelflux/db/installation-test
'

- name: Upload arm64 deb artifact
Expand All @@ -128,6 +154,16 @@ jobs:
path: dist/*.deb
if-no-files-found: error

- name: Upload standalone arm64 executable and checksums
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: fuelflux-binary-arm64
path: |
dist/fuelflux_${{ steps.version.outputs.version }}_arm64
dist/SHA256SUMS
if-no-files-found: error

release:
name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
Expand All @@ -143,9 +179,32 @@ jobs:
name: fuelflux-deb-arm64
path: dist

- name: Download standalone executable and checksums
uses: actions/download-artifact@v8
with:
name: fuelflux-binary-arm64
path: dist

- name: Verify complete release assets
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
cd dist
test -s "fuelflux_${version}_arm64.deb"
test -s "fuelflux_${version}_arm64"
test "$(wc -l < SHA256SUMS)" -eq 2
sha256sum -c SHA256SUMS
dpkg-deb -x "fuelflux_${version}_arm64.deb" ../release-extracted
cmp "fuelflux_${version}_arm64" ../release-extracted/opt/fuelflux/bin/fuelflux

- name: Publish release assets
uses: softprops/action-gh-release@v3
with:
files: dist/*.deb
fail_on_unmatched_files: true
files: |
dist/fuelflux_*_arm64.deb
dist/fuelflux_*_arm64
dist/SHA256SUMS
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
# build
build/
build-*/
# Staged release packaging and extracted verification files
pkgroot/
dist/
release-extracted/
_codeql_build_dir/
_codeql_detected_source_root

Expand Down
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This file is a part of fuelflux application

cmake_minimum_required(VERSION 3.16)
project(FuelFlux VERSION 0.3.8 LANGUAGES CXX)
project(FuelFlux VERSION 0.3.9 LANGUAGES CXX)

# Generate version header from template
configure_file(
Expand Down Expand Up @@ -355,6 +355,7 @@ set(LIB_SOURCES
src/hardware/aht10.cpp
src/console_emulator.cpp
src/backend.cpp
src/runtime_config.cpp
src/backend_base.cpp
src/url_utils.cpp
)
Expand Down Expand Up @@ -416,6 +417,7 @@ list(APPEND LIB_SOURCES
# Headers
set(HEADERS
include/config.h
include/runtime_config.h
include/state_machine.h
include/controller.h
include/logger.h
Expand Down Expand Up @@ -589,6 +591,7 @@ if(ENABLE_TESTING)

# Test source files
set(TEST_SOURCES
tests/runtime_config_test.cpp
tests/backend_test.cpp
tests/backend_base_test.cpp
tests/backend_bounded_test.cpp
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@

## Quick Start

### Factory image preparation

[Russian factory instruction (Word)](doc/fuelflux_factory_instruction_ru.docx)
covers editing `D:\images\fuelflux.img` in place through WSL, replacing the ARM64
binary, assigning device settings and writing cards with HDD Raw Copy Tool.

Startup settings in `/etc/fuelflux/fuelflux.env`:

| Variable | Compiled default |
| --- | --- |
| `FUELFLUX_CONTROLLER_ID` | `232390330480219` |
| `FUELFLUX_BACKEND_URL` | `http://ttft.uxp.ru` |

Settings are read once before workers start and reused across retries. Blank values
use defaults. IDs are strings; leading zeros and nonnumeric IDs are preserved
(subject to server rules). Surrounding whitespace is trimmed. HTTP/HTTPS URLs may
include a port and base path; trailing slashes are removed. Invalid URLs, embedded
credentials, queries and fragments produce one warning and fall back to the compiled
server while retaining the ID. Connection failures never switch servers. Check the
`Effective FuelFlux settings` startup log to confirm the values actually in use.
Use a clean factory image when assigning a new identity or server; migrating
operational databases between identities or servers is outside this procedure.

Tagged ARM64 releases publish the `.deb`, a separate
`fuelflux_<version>_arm64` executable from the same staged installation, and
`SHA256SUMS`. The executable requires the runtime libraries declared by the matching
package; replacing it alone does not install those dependencies.

### Build and Run (Development)

Use CMake + Ninja to build and debug in VS Code. Recommended steps (PowerShell):
Expand Down
1 change: 1 addition & 0 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ After installation:
Edit `/etc/fuelflux/fuelflux.env` to configure:

- `FUELFLUX_CONTROLLER_ID` - Unique device identifier
- `FUELFLUX_BACKEND_URL` - HTTP/HTTPS backend base URL, optionally with port and base path
- `FUELFLUX_PUMP_GPIO_CHIP` - GPIO chip device
- `FUELFLUX_PUMP_RELAY_PIN` - Relay pin number
- `FUELFLUX_PUMP_ACTIVE_LOW` - Relay active low setting
Expand Down
2 changes: 1 addition & 1 deletion deploy/debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ Section: utils
Priority: optional
Architecture: @DEB_ARCH@
Maintainer: sw.consulting <support@sw.consulting>
Depends: libc6 (>= 2.35), libstdc++6, libsqlite3-0, libgpiod2, libssl3 | libssl1.1, libnfc6, libcurl4, libfmt9 | libfmt8 | libfmt7
Depends: libc6 (>= 2.35), libstdc++6, libsqlite3-0, libgpiod2, libssl3 | libssl1.1, libnfc6, libcurl4, libfmt9 | libfmt8 | libfmt7, libc-ares2, libfreetype6, ca-certificates
Description: FuelFlux controller software package
FuelFlux controller software and deployment assets for ARM64 devices.
8 changes: 6 additions & 2 deletions deploy/debian/postinst
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ case "${1:-}" in
# FuelFlux Environment Configuration
# Uncomment and modify as needed

# Controller ID (unique identifier for this device)
# FUELFLUX_CONTROLLER_ID=232390330480218
# Controller ID: opaque unique string; blank/missing uses 232390330480219
# FUELFLUX_CONTROLLER_ID=232390330480220

# HTTP(S) backend base URL; blank/missing or invalid uses http://ttft.uxp.ru
# No credentials, query or fragment. Trailing slashes are removed.
# FUELFLUX_BACKEND_URL=http://ttft.uxp.ru

# Pump GPIO configuration
# FUELFLUX_PUMP_GPIO_CHIP=/dev/gpiochip0
Expand Down
11 changes: 6 additions & 5 deletions deploy/fuelflux.env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# FuelFlux Environment Configuration
# Copy this file to /etc/fuelflux/fuelflux.env and modify as needed

# Controller ID (unique identifier for this device)
# FUELFLUX_CONTROLLER_ID=232390330480218
# Controller ID: opaque unique string; blank/missing uses 232390330480219
# FUELFLUX_CONTROLLER_ID=232390330480220

# HTTP(S) backend base URL; blank/missing or invalid uses http://ttft.uxp.ru
# No credentials, query or fragment. Trailing slashes are removed.
# FUELFLUX_BACKEND_URL=http://ttft.uxp.ru

# Pump GPIO configuration
# FUELFLUX_PUMP_GPIO_CHIP=/dev/gpiochip0
# FUELFLUX_PUMP_RELAY_PIN=17
# FUELFLUX_PUMP_ACTIVE_LOW=true

# Backend API URL (if different from default)
# FUELFLUX_BACKEND_URL=http://ttft.uxp.ru
8 changes: 6 additions & 2 deletions deploy/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,12 @@ setup_config() {
# FuelFlux Environment Configuration
# Uncomment and modify as needed

# Controller ID (unique identifier for this device)
# FUELFLUX_CONTROLLER_ID=232390330480218
# Controller ID: opaque unique string; blank/missing uses 232390330480219
# FUELFLUX_CONTROLLER_ID=232390330480220

# HTTP(S) backend base URL; blank/missing or invalid uses http://ttft.uxp.ru
# No credentials, query or fragment. Trailing slashes are removed.
# FUELFLUX_BACKEND_URL=http://ttft.uxp.ru

# Pump GPIO configuration
# FUELFLUX_PUMP_GPIO_CHIP=/dev/gpiochip0
Expand Down
Binary file added doc/fuelflux_factory_instruction_ru.docx
Binary file not shown.
17 changes: 13 additions & 4 deletions include/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
namespace fuelflux {

class MessageStorage;
#ifdef USE_CARES
class CaresResolver;
#endif

// Tank information structure for backend
struct BackendTankInfo {
Expand Down Expand Up @@ -75,6 +78,7 @@ class IBackend {
virtual std::vector<UserCard> FetchUserCards(int first, int number) = 0;
virtual std::vector<FuelTank> FetchFuelTanks(int first, int number) = 0;
virtual const std::string& GetControllerUid() const = 0;
virtual const std::string& GetBaseApiUrl() const = 0;
};

// Base backend class with shared logic for request/response handling
Expand Down Expand Up @@ -164,6 +168,7 @@ class BackendBase : public IBackend, public std::enable_shared_from_this<Backend

// Backend class for real REST API communication
class Backend : public BackendBase {
friend struct RuntimeConfigurationTestAccess;
public:
// Constructor
// Parameters:
Expand All @@ -172,6 +177,7 @@ class Backend : public BackendBase {
Backend(const std::string& baseAPI, const std::string& controllerUid, std::shared_ptr<MessageStorage> storage = nullptr);

~Backend() override;
const std::string& GetBaseApiUrl() const override { return baseAPI_; }

private:
// Private method for common parsing of responses from the backend
Expand All @@ -187,14 +193,17 @@ class Backend : public BackendBase {
const nlohmann::json& requestBody,
const std::string& bearerToken) override;

// Send async deauthorize request without mutex
// Uses only immutable settings and the resolver's own synchronization.
void SendAsyncDeauthorizeRequest(const std::string& token) override;

// Static helper for async deauthorize - doesn't use mutex or modify state
static void SendAsyncDeauthorize(const std::string& baseAPI, const std::string& token);
// The executor holds shared ownership while using this instance's resolver.
void SendAsyncDeauthorize(const std::string& baseAPI, const std::string& token);

// Base URL of backend REST API
std::string baseAPI_;
const std::string baseAPI_;
#ifdef USE_CARES
std::unique_ptr<CaresResolver> resolver_;
#endif
std::recursive_mutex requestMutex_;
};

Expand Down
1 change: 1 addition & 0 deletions include/cares_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class CaresResolver {
// Test helpers for validating targeted cache behavior
bool HasValidTargetedCacheForTesting() const;
std::string GetTargetedCachedIpForTesting() const;
const std::string& GetTargetedHostnameForTesting() const { return cached_hostname_; }

private:
struct CacheEntry {
Expand Down
4 changes: 4 additions & 0 deletions include/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <future>

#include "backend.h"
#include "runtime_config.h"
#include "message_storage.h"
#include "state_machine.h"
#include "timing_config.h"
Expand Down Expand Up @@ -62,6 +63,7 @@ struct ControllerStatus {

// Main controller class that orchestrates the entire system
class Controller {
friend struct RuntimeConfigurationTestAccess;
public:
Controller(ControllerId controllerId,
std::shared_ptr<IBackend> backend = nullptr,
Expand Down Expand Up @@ -184,6 +186,8 @@ class Controller {

// Backend creation helper
static std::shared_ptr<IBackend> CreateDefaultBackend(std::shared_ptr<MessageStorage> storage = nullptr);
static std::shared_ptr<IBackend> CreateDefaultBackend(const RuntimeConfig& config,
std::shared_ptr<MessageStorage> storage = nullptr);
static std::shared_ptr<IBackend> CreateDefaultBackendShared(const std::string& controllerUid,
std::shared_ptr<MessageStorage> storage = nullptr);

Expand Down
27 changes: 27 additions & 0 deletions include/runtime_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2026 Maxim [maxirmx] Samsonov (www.sw.consulting)
// All rights reserved.
// This file is a part of fuelflux application

#pragma once

#include <functional>
#include <optional>
#include <string>

namespace fuelflux {

// A copied startup snapshot. Never re-read the environment during recovery/retries.
struct RuntimeConfig {
const std::string controllerId;
const std::string backendUrl;
};

using EnvironmentReader = std::function<std::optional<std::string>(const char*)>;
using ConfigurationWarning = std::function<void(const std::string&)>;

RuntimeConfig LoadRuntimeConfig(EnvironmentReader read = {}, ConfigurationWarning warn = {});

// Shared by URL validation and backend construction; safe before worker startup.
void InitializeCurlNetworking();

} // namespace fuelflux
Loading
Loading