Skip to content

Reference ​

Everything the daemon exposes, in tables.

Command line ​

muxen-blocswap

There are no options. The binary accepts no arguments and ignores anything passed to it — there is no --help, no --version and no verbosity control. The systemd unit invokes it bare.

Exit codes ​

CodeMeaning
0every termination

The daemon has no failure exit. A configuration it cannot use — file missing, feature off, no device to monitor — is reported on standard output and the daemon enters its main loop anyway. It leaves that loop only on SIGINT or SIGTERM, and returns 0.

Signals are handled from the main loop, so one arriving while a muxen-uds command is running takes effect only once that command returns.

Environment ​

The binary reads no environment variable of its own. Three are inherited and matter:

VariableEffect
PATHmuxen-uds is invoked by name through /bin/sh, so it must be on the path
TMPDIRwhere the temporary scan file is created

Files ​

PathAccessContent
/usr/bin/muxen-blocswap—the daemon
/usr/lib/systemd/system/muxen-blocswap.service—the systemd unit
/etc/muxen/deploy.jsonread once, at startupthe boat's deployment file. Not shipped by this package
/var/lib/muxen/deployed.jsonread once, at startupthe UID last deployed at each deviceId. Written by muxen-uds deploy, never by this daemon
$TMPDIR/blocswap-scan.XXXXXXcreated and deleted every 30 sthe scan result, handed to muxen-uds and read back

Both input paths are hard-coded. Neither can be moved.

The temporary file is created with g_file_open_tmp, so it lands in TMPDIR — and the unit sets PrivateTmp=true, so under systemd that is the service's own /tmp, invisible from a shell on the Brain.

Commands it runs ​

Verbatim, through /bin/sh:

WhenCommand
every 30 smuxen-uds uid --scan-to-json <tmpfile> >/dev/null
on a detected swapmuxen-uds --device-id <id> deploy
immediately aftermuxen-uds --device-id <id> reset

Notes that matter:

  • --device-id precedes the subcommand. The order is not decorative.
  • The scan's standard output is discarded; its standard error is not, so muxen-uds diagnostics reach the journal.
  • Exit codes are not examined. The daemon distinguishes only "the command could not be started, or was killed by a signal" from anything else. A muxen-uds invocation that ran and reported failure is treated as a success.
  • The calls are synchronous. The daemon's main loop is blocked for the duration of each one.

The scan file ​

Written by muxen-uds, read back and parsed by the daemon. It is the daemon's whole view of the bus.

json
{
  "devices": [
    { "deviceId": 65, "uid": "27005E000B504256" }
  ],
  "duplicate": false
}
KeyTypeUse
devices[]arrayone entry per unit answering the scan
devices[].deviceIdintegerthe address. Entries without it are skipped
devices[].uidstring, 16 hex charactersthe physical unit's serial number. Entries without it are skipped
duplicatebooleantrue when two different units answer at the same address

An entry whose deviceId matches no monitored device is ignored. A monitored device that appears in no entry is marked absent for that scan.

duplicate is the interlock: while it is true, nothing is deployed, no state advances and nothing is logged. A missing duplicate key is treated as true — the daemon locks rather than assume.

If the file cannot be read the daemon logs cmd: failed to read '<path>'; if it is not valid JSON, cmd: failed to parse the JSON. In both cases every monitored device stays marked absent for that scan.

/var/lib/muxen/deployed.json ​

Read once, at startup, to seed the expected UID of each monitored device. The daemon reads exactly one field per device — the uid of the entry whose deviceId matches — and nothing else.

A device with no entry there gets an empty expected UID, which matches no real unit, so the device is treated as a replacement and deployed on the daemon's second tick.

Configuration keys ​

Read from /etc/muxen/deploy.json. Everything not listed here is ignored by this daemon.

LocationKeyTypeMeaning
settings[]FeatureBlocSwapobject with a value fieldthe feature switch. The daemon monitors nothing unless value is the string "1"
devices[]deviceIdintegerthe address to watch. Required
devices[].parameters[]VirtualDeviceobject with a value field"1" excludes the device from monitoring

Matching is by the literal name field, exactly. There is no default for FeatureBlocSwap: absent means off, and so does any value other than "1" — a JSON number 1 included.

The state machine ​

One instance per monitored device, advanced once per scan. States are internal and never named in the log.

StateEnteredDevice seenDevice not seen
settlingat startup, and after every deploy→ watching. No UID comparisonstays
watchingfrom settlingUID differs → deploy; UID same → nothing, absence counter clearedcounter++; at 3 → waiting, logs become OFFLINE
waitingafter 3 consecutive absencesUID same → watching, logs become ONLINE; UID differs → deploystays

A deploy is the pair deploy + reset, after which the newly seen UID becomes the expected one and the device returns to settling.

The settling pass is why a cold swap takes two ticks to be found: the first tick only establishes that something is answering.

Journal messages ​

The complete set. The daemon emits nothing else.

MessageWhen
Auto deploy is not activated in the deployed configurationstartup, when the feature is off, the deployment file is unreadable, or no device qualifies
Feature BlocSwap enabledstartup, when FeatureBlocSwap is "1"
Monitoring device <id> (0x<id>)startup, once per monitored device
Loading deployed.jsonstartup, before the UID table
<id> => <uid>startup, once per monitored device. An empty right-hand side means no recorded UID
device <id> has been changeda different UID answered at that address. A deploy and a reset follow
device <id> become OFFLINEthree consecutive scans found nothing at that address
device <id> become ONLINEthe same unit returned after being offline
cmd: failed to read '<path>'the scan file could not be read back
cmd: failed to parse the JSONthe scan file was not valid JSON

There is no message for a deploy starting, succeeding or failing, and none for the duplicate interlock.

All of it goes to standard output, unflushed. Under systemd, standard output is not a terminal and is therefore fully buffered, so lines reach the journal in blocks rather than as they are written.

Constants ​

None of these is configurable.

ConstantValue
Scan interval30 s
Absences before a device is declared offline3
Expected feature valuethe string "1"
Expected virtual-device valuethe string "1"
UID length16 characters
Command buffer512 bytes, for both the scan and the deploy commands
Temporary file templateblocswap-scan.XXXXXX

systemd ​

Unit muxen-blocswap.service:

DirectiveValue
DescriptionMuxen Automatic Device Deploy
PartOfmuxen-deploy.target
WantedBymuxen-deploy.target
Aftermosquitto.service
User / Groupmuxen
ExecStart/usr/bin/muxen-blocswap
Restartalways, RestartSec=30
ConfigurationDirectorymuxen, mode 0755
StateDirectorymuxen, mode 0755

Hardening: NoNewPrivileges, LockPersonality, PrivateDevices, PrivateTmp, ProtectClock, ProtectControlGroups, ProtectHome, ProtectHostname, ProtectKernelLogs, ProtectKernelModules, ProtectKernelTunables, ProtectProc=invisible, ProtectSystem=full, RemoveIPC, RestrictNamespaces, RestrictNetworkInterfaces=lo, RestrictRealtime, RestrictSUIDSGID, SystemCallArchitectures=native. Twenty capabilities are removed from the bounding set, including CAP_NET_ADMIN, CAP_SYS_ADMIN, CAP_SYS_BOOT and CAP_SYS_PTRACE. The @clock, @cpu-emulation, @debug, @module, @mount, @obsolete, @privileged, @raw-io, @reboot, @resources and @swap system-call sets are denied with EPERM.

These restrictions apply to the muxen-uds processes the daemon starts as well, since they are children of the unit.

Restarting muxen-deploy.target restarts the daemon. That is the mechanism by which a change to the deployment file is picked up: the muxen-systemd package watches /etc/muxen/deploy.json and restarts the target when it changes.

Packaging ​

FieldValue
Source / packagemuxen-blocswap
Section / Prioritymisc / optional
Architectureany, Multi-Arch: foreign
Dependsmuxen-uds, muxen-systemd (>= 1.2.0), plus ${shlibs:Depends} and ${misc:Depends}
Replaces / Breaksmuxen-auto-deploy
Triggeractivates muxen-restart-target
LicenceMIT
Homepagehttps://www.muxen.fr

muxen-auto-deploy is this program's original name; the two packages cannot be installed together.

The muxen-restart-target trigger is what makes the MUXEN services restart when the package is upgraded.

The package ships one binary and one unit file. No configuration file, no bash completion, no data files.

Building from source ​

sh
meson setup build
meson compile -C build

The makefile wraps the common cases: make deb builds the Debian package, make dev an address-sanitiser build, make lint and make lint-check run clang-format, make mrproper cleans the tree and the vendored libraries.

Dependencies: GLib, json-c, and the MUXEN libcanmqtt and libmuxenfile, resolved as installed packages when present and as Meson subprojects otherwise.

Build options in force: default_library=static, warning_level=3, LTO with a reproducible debug-path mapping, and full RELRO with immediate binding.

libcanmqtt supplies main() and the GLib main loop; Bloc swap provides application_init() and application_exit() as the weak-symbol overrides. That is why the daemon has no argument parsing of its own — it never looks at argc or argv.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.