Skip to content

Getting started ​

Prerequisites ​

On the Brain:

  • muxen-systemd, a hard dependency of the package. It creates the muxen system user and group the daemon runs as, and it owns muxen.target, which the configuration service is part of.
  • Write access to /etc/muxen for the muxen user. The package and the units arrange this themselves; see Permissions below.
  • nginx, if the API is to be reachable from anything other than the Brain itself. The daemon binds 127.0.0.1 and nothing else. The package ships the two nginx snippets; the site that includes them belongs to the boat's interface package.

Nothing else. There is no broker, no CAN interface and no other MUXEN daemon in the way: muxen-configd reads and writes files.

Install ​

sh
sudo apt install muxen-config

One package carries everything: the muxen-configd daemon, the muxen-config-backup script, the four systemd units, the two nginx snippets, the bundled Swagger UI and the bash completion. It also creates four empty directories that the daemon expects to exist:

DirectoryHolds
/etc/muxen/configurationone <id>.json per project
/etc/muxen/appsopaque per-application blobs
/etc/muxen/backupthe dated deploy.json copies
/usr/share/muxen-config/templatesfactory templates, read-only

Start and verify ​

The service is muxen-config.service — the unit is named after the package, the binary after the daemon.

sh
systemctl status muxen-config
journalctl -u muxen-config -n 30

A healthy start prints the daemon's whole configuration and nothing else:

config: verbose = 0
config: wsPort = 12000
config: configFolder = /etc/muxen
config: swaggerFolder = /usr/share/muxen-config/swagger

Then ask the daemon itself:

sh
$ curl -s http://127.0.0.1:12000/health
{
  "status":"ready"
}

/health answers 200 with ready once the routing table is compiled, and 503 with starting during the brief window before that. It answers in both states rather than failing, which is what makes it usable as a probe: wait for 200.

If the daemon is up, the project list works too — it is empty on a fresh install unless templates are present:

sh
curl -s http://127.0.0.1:12000/project

Permissions ​

muxen-config.service runs as muxen:muxen under ProtectSystem=strict, which makes the entire filesystem read-only apart from /etc/muxen (granted back through ConfigurationDirectory=muxen). The daemon refuses to start if its configuration folder is missing or not writable, so ownership matters.

muxen-config-perms.service is the unit that fixes it. It is a oneshot that runs before the daemon and, ignoring individual failures:

chown -R muxen:muxen /etc/muxen
chmod 755 /etc/muxen
chmod 755 /etc/muxen/configuration
chmod 644 /etc/muxen/deploy.json
chmod 644 /etc/muxen/configuration/*

Run it by hand after restoring files from a backup, or after any edit made as root in /etc/muxen:

sh
sudo systemctl start muxen-config-perms
sudo systemctl restart muxen-config

Reaching the API from the boat's screen ​

The daemon listens on 127.0.0.1:12000 and refuses everything else, so the interface reaches it through nginx. The package installs two snippets into /etc/nginx/snippets/:

SnippetExposesMaps to
muxen-api-config.conf/api/config/…http://127.0.0.1:12000/…
muxen-deploy.conf/api/deploy.jsonhttp://127.0.0.1:12000/deploy

A site includes them:

nginx
include /etc/nginx/snippets/muxen-deploy.conf;
include /etc/nginx/snippets/muxen-api-config.conf;

muxen-api-config.conf strips the /api/config/ prefix, so /api/config/project is GET /project on the daemon, and it raises client_max_body_size to 50 MB for media uploads. muxen-deploy.conf falls back to serving /etc/muxen/deploy.json straight off disk if the daemon is unreachable, so a boot, a crash or an upgrade never leaves the screens without a configuration.

Both snippets force Connection: close towards the daemon. Do not point a development proxy straight at :12000; route it through nginx.

A first project, end to end ​

The examples below talk to the daemon directly. Through nginx, prefix every path with /api/config.

1. Create it. The id is the filename, so it is restricted to letters, digits and -. The optional body sets the human-readable name.

sh
$ curl -s -X PUT http://127.0.0.1:12000/project/my-boat \
       -H 'content-type: application/json' \
       -d '{"name":"My Boat"}' -o /dev/null -w '%{http_code}\n'
201

That writes /etc/muxen/configuration/my-boat.json with a fresh UUID, a creation timestamp and three default settings (DegradedModeName1, PrimaryLanguage, AlternativeLanguages).

2. Add a device. Devices are addressed by their MUXEN device id, which encodes function and instance together — `deviceId = function × 64

  • instance`. Device id 64 is therefore function 1, instance 0: the first Bloc 8 power-output module.
sh
$ curl -s -X PUT http://127.0.0.1:12000/project/my-boat/device/64 \
       -o /dev/null -w '%{http_code}\n'
201

The daemon fills in function, functionName, instance and a starting Name parameter built from the function name and the instance — here Bloc 8 #1.

3. Name it. Parameters are name/value pairs on the device.

sh
curl -s -X PUT http://127.0.0.1:12000/project/my-boat/device/64/parameter/Name \
     -H 'content-type: application/json' \
     -d '{"value":"Saloon lights"}'

4. Read it back.

sh
$ curl -s http://127.0.0.1:12000/project/my-boat/device/64
{
  "ctm":"2026-08-16T09:12:44Z",
  "function":1,
  "functionName":"Bloc 8",
  "instance":0,
  "deviceId":64,
  "parameters":[ { "name":"Name", "value":"Saloon lights", ... } ]
}

5. Deploy it. Nothing on the boat has changed yet. This is the step that changes it.

sh
$ curl -s -X PUT http://127.0.0.1:12000/project/my-boat/deploy \
       -o /dev/null -w '%{http_code}\n'
201

/etc/muxen/deploy.json is now a copy of the project. The copy goes to a temporary file next to it and is renamed into place, so a failure leaves the previous deployment intact.

6. Confirm what the boat will read.

sh
$ curl -s http://127.0.0.1:12000/deploy | head -20
$ curl -s -D - -o /dev/null http://127.0.0.1:12000/deploy | grep -i etag
etag: "6b1f…"

GET /deploy serves a stripped copy: media payloads and the heavy parts of the screen pages are removed, because the consumers of deploy.json do not need them. The ETag is a digest of the deployed bytes, so a client that already has the current configuration gets a 304.

What happens when you deploy ​

Writing /etc/muxen/deploy.json is the signal the rest of the boat watches for. muxen-systemd ships a path unit on that file which restarts muxen-deploy.target, and the daemons that consume the configuration are part of that target — so they pick the new configuration up without anyone restarting them by hand.

That also means a deploy is not free: it restarts services. Deploy deliberately, not as a way of saving your work.

Where to go next ​

  • Projects, templates, commissioning and passwords — Projects
  • Deploying, deploy.json and the daily backups — Deployment
  • When something does not work — Troubleshooting
  • Every endpoint, flag, path and code — Reference

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