Skip to content

Deployment ​

Deploying is the moment a configuration stops being a draft and becomes the boat. Up to that point, everything done through the API is bookkeeping in a file nothing else reads. Deploying copies the project over /etc/muxen/deploy.json, and /etc/muxen/deploy.json is what every other MUXEN service on the Brain reads to know what is fitted, what it is called and how it behaves.

It is a deliberate act with a visible effect: services restart, and the screens redraw with the new configuration. Deploy when the change is finished, not as a way of saving work in progress.

Deploying ​

sh
curl -X PUT http://127.0.0.1:12000/project/<id>/deploy
CodeMeaning
201deployed
401the project is password-protected and no valid credential was sent
404no such project
500the copy or the rename failed — see the journal

The copy is atomic. The daemon writes /etc/muxen/deploy.json.tmp alongside the target and renames it into place, so a reader either sees the whole new file or the whole previous one, and a failure part-way through leaves the previous deployment untouched. The copy is also looped, which matters for the multi-megabyte projects that carry embedded media: a short write is an error, never a silently truncated file.

What lands in deploy.json is the project as stored — the complete document, media payloads and screen-page bodies included. The stripping happens on the way out, when the file is served, not on the way in.

A template can be deployed directly: the endpoint resolves the id the same way every other project endpoint does, falling back to /usr/share/muxen-config/templates/<id>.json. That deploys a factory catalogue to the boat, which is almost never what is wanted. Duplicate first.

What a deploy triggers ​

muxen-systemd — a hard dependency of this package — ships a path unit watching /etc/muxen/deploy.json that restarts muxen-deploy.target when the file changes. The daemons that consume the configuration belong to that target, so they reload without anyone restarting them.

Two things follow from this:

  • Any write to that path counts, not only one made through the deploy endpoint. Copying a backup over it restarts the target just the same.
  • muxen-config.service itself is PartOf=muxen.target, not muxen-deploy.target, so the configuration store does not restart itself when a deploy happens.

Reading the deployed configuration ​

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

The body is a stripped copy of deploy.json:

RemovedFrom
dataevery entry of media[]
elements, conditionGroups, imageevery entry of views[]

Everything else is served as deployed. The heavy parts are fetched individually, on demand, through GET /project/<id>/media/<name> and GET /project/<id>/view/<name>.

The response carries an ETag and cache-control: max-age=0, no-cache. The ETag is a SHA-256 digest of the bytes of deploy.json, quoted:

sh
$ curl -s -D - -o /dev/null http://127.0.0.1:12000/deploy | grep -i -e etag -e cache
etag: "9f2c…c41a"
cache-control: max-age=0, no-cache

Send it back and an unchanged deployment costs nothing:

sh
curl -s -o /dev/null -w '%{http_code}\n' \
     -H 'If-None-Match: "9f2c…c41a"' http://127.0.0.1:12000/deploy
304

Because the validator is a digest of the content rather than a timestamp, two properties hold that a client can rely on:

  • Redeploying the same project changes nothing. The ETag is identical and a cached client is not made to refetch.
  • A change made outside the deploy endpoint is still noticed. Anything that alters the bytes of deploy.json — a restored backup, an edit by hand — changes the ETag.

GET /deploy answers 404 when the file is absent or when it does not parse as JSON. On a Brain that has never been deployed to, 404 is the normal answer.

Internally the daemon keeps one cached stripped body, keyed by that same digest, so repeated 200s do not re-parse a multi-megabyte project. The cache cannot go stale: the key is the content.

Serving it to the boat ​

muxen-deploy.conf publishes the deployed configuration at /api/deploy.json with a fallback:

/api/deploy.json  ──►  127.0.0.1:12000/deploy        (normal)
                  └─►  /etc/muxen/deploy.json        (if configd is unreachable)

The fallback triggers on a connection error, a one-second connect timeout, or a 502/503/504, and serves the file straight off disk with nginx's own ETag. It is a degraded path, not an equivalent one: the raw file is not stripped, so the payload is everything the project contains, media included. It exists so that a Brain rebooting, upgrading or crashing still hands the screens a configuration.

Both responses are gzipped for bodies over 1 KB.

The daily backups ​

deploy.json is the one file whose loss costs the boat its behaviour, so the package ships a job that keeps copies of it.

UnitRole
muxen-config-backup.timerOnCalendar=daily, Persistent=true
muxen-config-backup.serviceoneshot, runs /usr/bin/muxen-config-backup

Persistent=true means a Brain that was switched off at the scheduled time runs the job at the next boot instead of skipping the day.

Check that the timer is active and when it next runs:

sh
systemctl list-timers muxen-config-backup.timer
systemctl is-enabled muxen-config-backup.timer

The script is deliberately small:

/etc/muxen/backup/deploy.json-2026-08-16_03-12-07
/etc/muxen/backup/deploy.json-2026-08-14_03-12-04
/etc/muxen/backup/last_backup -> …/deploy.json-2026-08-16_03-12-07
  • It compares /etc/muxen/deploy.json against the last_backup symlink with cmp. A day on which nothing was deployed produces no file, so the directory holds distinct configurations, not one copy per day.
  • New copies are named deploy.json-%Y-%m-%d_%H-%M-%S in local time.
  • It keeps the 30 most recent copies by modification time and deletes the rest.
  • It does nothing at all if /etc/muxen/deploy.json does not exist.

Run it on demand — before a risky change, for instance:

sh
sudo systemctl start muxen-config-backup
sudo muxen-config-backup            # same thing, with its output on the terminal

Restoring one ​

A backup is an ordinary copy of deploy.json, so restoring it is a copy in the other direction. It restarts muxen-deploy.target by itself, through the path unit — no service needs restarting by hand, but the permissions do need fixing, because the copy was made as root:

sh
sudo cp /etc/muxen/backup/deploy.json-2026-08-14_03-12-04 /etc/muxen/deploy.json
sudo systemctl start muxen-config-perms

A backup is a deployed configuration, not a project: it does not appear in the project list and cannot be edited. To get back to something editable, import it as a project first:

sh
curl -X PUT http://127.0.0.1:12000/project/recovered/restore \
     -H 'content-type: application/json' \
     --data-binary @/etc/muxen/backup/deploy.json-2026-08-14_03-12-04

That gives a project called recovered containing exactly what the boat was running that day, editable and redeployable.

What is not backed up ​

The daily job copies deploy.json and nothing else. The projects themselves — /etc/muxen/configuration/*.json — and the application blobs in /etc/muxen/apps/ are not covered by it. Include /etc/muxen in whatever image or off-boat backup the installation uses, or export the projects with GET /project/<id>/backup and keep the files.

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