Backup & Migration
Move connections, functions, topics, pipelines and dashboards between workspaces or installations as one .mhub.json bundle, for example from test to live.
- Where
- Admin › Maintain › Backup & Migration
- Permission
- dependencies:export
- Credentials
- Left out by default
- 2.x files
- Cannot be imported into 3.0
Move configuration between MaestroHub workspaces. You export what you have built into a single .mhub.json bundle, then import that bundle wherever you need it: a different workspace on the same installation, or a different installation entirely. The usual reason is promoting work from a test system to a live one.
Open the page from Admin > Maintain > Backup & Migration in the sidebar. The older /<workspace-slug>/system-management/export-import address still works, so existing bookmarks and links keep going to the right place.
3.0.0 replaced the export format. A file produced by any earlier release (its schemaVersion is the text "1.0.0") is refused at upload with an explanation, and there is no converter.
To move data off a 2.x instance: upgrade that instance to 3.0.0 first, export again from it, then import the new file. Keep the old file until you have the new one. Nothing in 3.0.0 can read it.
What a bundle is
A bundle is a template, not a copy of your database rows. It describes what to build in the destination rather than naming things that exist in the source.
It contains connections, functions, global functions, topics, pipelines and dashboards. It does not contain:
- historical readings or any other time-series data
- the semantic layer: libraries and properties, topic meanings and their units, limits and alert thresholds. An imported topic arrives without them, so set them up again in the destination
- users, roles or permissions
- instance settings such as email or licensing
- App Studio apps (App Studio is in release 3.0 as Beta, a paid add-on), which have their own backup, described in App Studio backup below
Two properties follow from being a template, and both matter in practice:
No source identity travels. There are no internal record identifiers, no timestamps, and nothing naming the instance that produced the file. Exporting the same selection twice produces byte-identical files, so a bundle checked into version control only changes when the configuration does.
Every namespace path is written with a placeholder. Instead of mHv1.0/production/line-1/temperature, the file stores mHv1.0/<workspace>/line-1/temperature. The <workspace> part is filled in with the destination workspace you pick at import time. This is what lets one bundle be imported into several workspaces, each binding to its own namespace, with no editing between.
A path that names some other workspace outright, rather than using the placeholder, is refused before anything is written. That case is almost always a hand-edited file, and binding it would silently produce a path that looks correct but points at the wrong place.
Permissions
| Action | Permission |
|---|---|
| Export a bundle, browse the entity list | dependencies:export |
| Export a bundle with connection credentials | dependencies:export_credentials |
| Preview an import, commit an import | dependencies:import |
The page itself is gated on dependencies:export.
dependencies:export_credentials is deliberately separate. An export without credentials is a template, safe to hand around; an export with them is the passwords themselves in a file, and this is the only place in MaestroHub where a decrypted secret leaves over HTTP. Workspace.Admin holds both, so a workspace administrator's access is unchanged; the split exists so a custom role can be given configuration export without being given the secrets. Without it, the Include connection credentials box is shown disabled with that reason, and the server refuses the request even if the box is bypassed.
Importing checks dependencies:import in the destination workspace, not only in the one you are working in. Picking a destination you hold no grant in is refused before anything about that workspace is read.
Exporting
Click Start export. Everything happens in one dialog.
Choosing what to include
Everything in this workspace is the default and needs no further input.
Specific entities shows the workspace's contents grouped by kind, with a filter box for finding things by name. Tick what you want.
References are not added for you. If you export a pipeline, the connection it uses is not pulled in automatically. Tick both. The dialog warns you when a selection references something you have not included, so an inconsistent bundle is visible before you download it rather than after someone tries to import it.
The warning lists every function a pipeline needs, including each function a read group reads: the ones picked by hand, and for a group that reads all of a connection's read functions or selects them by label, the ones it reads today. A topic written as an expression (for example {{ $item.topic }}) is worked out when the pipeline runs, so it is not an entity to include and is not listed.
The warning also lists the global functions a pipeline's JavaScript calls, including the ones those functions call in turn.
What a missing reference does at import depends on its kind:
- Connection: the import reuses a connection with the same name if the destination workspace already has one, and stops otherwise.
- Function: the import stops. Functions travel only inside the bundle, even when the destination already has one with the same name.
- Global function: the import goes ahead, and uses a global function with the same name if the destination already has one. If it has none, the pipeline fails on every run with
<name> is not defineduntil the function is created there. The preview says so before you import. - Topic: the import goes ahead. Topics are not required.
Global functions
A pipeline runs the version of a global function it was saved with, even after the function is edited. The bundle therefore carries each global function at the version the exported pipelines use, so an imported pipeline behaves as it did where it was exported. A global function no exported pipeline uses travels at its latest version. Only the code, parameters, description, labels and test cases travel, not the version history: in the destination the function starts again at version 1.
If two exported pipelines use different versions of the same function, the destination can hold only one, so the bundle carries the newer of them. The import preview names the pipeline that used the other version.
A global function you are not allowed to read is left out of the export. The pipelines that use it still travel, and the import preview names the missing function.
Credentials
Passwords, API keys and private keys are left out by default and replaced with a placeholder. The bundle still records which fields were omitted, so the import can ask for them.
Ticking Include connection credentials writes the real secrets into the file as readable text. The dialog says so, in place, whenever the box is ticked. The box needs the dependencies:export_credentials permission; without it the box is disabled and says why.
A bundle exported with credentials is as sensitive as the passwords inside it. Store and transfer it accordingly, and prefer leaving them out when the file will be emailed, shared, or committed anywhere.
Whether an export carried credentials is recorded in the audit trail, so the question "did that file have live passwords in it?" is answerable after the fact.
Description
An optional free-text note stored in the bundle and shown to whoever imports it. Leave it empty if you want byte-identical exports, since the text is part of the file.
Download
Download bundle produces <workspace-name>.mhub.json. If the button is disabled it says why, usually that nothing has been ticked yet.
Importing
Click Start import. Three steps: upload, preview, result.
Step 1. Upload
Choose the .mhub.json file and the destination workspace.
The destination is never assumed. It does not default to the workspace you are currently viewing, because importing into the wrong one is tedious to unpick. You would have to delete each entity by hand. Nothing is read from the file until both are set, and the button explains which one is still missing.
Step 2. Preview
The preview is a plan. Nothing has been written yet, and committing applies exactly the plan shown.
It gives you:
-
Bundle summary. The description, the total entity count, the format version, and the destination namespace every path will bind to.
-
Per-kind counts. How many connections, functions, global functions, topics, pipelines and dashboards will be created, replaced, or left alone.
-
Bound paths. Before-and-after pairs showing
<workspace>resolved to the destination, so you can confirm the binding is what you expected before committing. -
Blocking problems, if any, each with what to do about it.
-
Name collisions, each with a choice (see below).
-
Missing credentials, if the bundle was exported without them.
-
Things to check after import, if any. These don't block the import, but a pipeline node they name will fail or behave differently when it runs. These cases are reported:
- a read group that reads all of a connection's read functions, or selects them by label, when the bundle creates that connection without any of those functions;
- a node whose connection or function was deleted before the export, so the bundle still carries its old ID;
- a pipeline that calls a global function that is neither in the bundle nor in the destination;
- a pipeline that used a different version of a global function than the one in the bundle (see Global functions).
Bundles exported before global functions were carried do not record which ones each pipeline calls, so they cannot be checked. Re-export them to get the check.
Anything that cannot be fixed from this screen blocks the import and says so. The Import button is disabled until every collision has an answer and you have confirmed the entity count, and it explains which of those is still outstanding.
Name collisions
When something in the bundle shares a name with something already in the destination, the import stops and asks. It never overwrites silently, and the default when a question goes unanswered is to refuse the whole import rather than guess.
| Choice | Effect |
|---|---|
| Replace | Overwrite the destination entity with the version from the bundle. For a global function this publishes the bundle's code as a new version; the pipelines this import replaces run it, and the destination's other pipelines keep the version they use. |
| Keep existing | Leave the destination untouched and drop that entity from the import. References to it still resolve to what is already there. |
Dashboard names are display labels, not identities, so a destination can legitimately hold two dashboards with the same name. Replace cannot be offered there, because nothing may pick one of them by chance, and the preview says so, naming how many share the name. Keep existing still works: leaving them alone is answerable without knowing which one was meant, so one ambiguous dashboard does not make the rest of the bundle unimportable.
Re-importing a bundle into a workspace that already has it collides on everything, which can run to hundreds of entries. Apply to all sets every collision at once; individual choices remain available for the cases that differ.
The design allows for importing under a new name so both versions coexist, but this release does not offer it. Replace and Keep existing both work for every entity kind.
Credentials
If the bundle was exported without secrets, the preview lists each connection that needs them and offers a field per missing value.
Filling them in stores them with the connection. Leaving a field blank is a legitimate choice, not a skipped step: the connection is created without that value, and you add it later from the Connections page. What never happens is the placeholder being stored as though it were the password. A connection with a placeholder for a password cannot authenticate, and the failure would surface far from the import that caused it.
Step 3. Result
On success: how many entities landed, where, and the same per-kind breakdown as the preview, now describing what happened rather than what was planned.
On failure: what went wrong, and, importantly, whether anything stayed behind. A failed commit rolls back, but a rollback is not always able to undo everything (an in-place update cannot be reversed, and a compensating delete can itself fail). When entities did stay, the result screen names them so you can reconcile by hand. When the rollback was clean it says nothing was written, and that statement is trustworthy.
After importing
One checkbox in the preview decides whether imported pipelines run: Use each pipeline's on/off state from the bundle.
- Unticked (the default): every imported pipeline ends switched off, including a pipeline you chose to Replace that is running in the destination. Nothing runs until you enable it. A freshly imported pipeline points at connections you may not have finished setting up, such as one imported without its credentials, and a copy of a production workspace would otherwise start reading the same devices and brokers straight away.
- Ticked: every imported pipeline ends on or off as it is in the bundle, a replaced one included. Use it to restore a known-good setup.
The preview lists which pipelines will start and which will be switched off for the current choice, and the result says what happened. Each change goes through the same check as the Enable and Disable switches: you need permission to change a pipeline's state, and a pipeline that cannot start (for example one whose nodes do not validate) stays off, named in the result with the reason. Every start and stop is recorded in the audit trail.
With the box unticked, check connections first, then enable the pipelines you want to run from the Pipelines page.
History and auditing
This page has no history tables. Every export and every import writes an audit record instead: who did it, when, to which workspace, how many entities, and whether the export carried credentials. Query those from the Audit Trail rather than from this page.
App Studio backup
App Studio is in release 3.0 as Beta: a paid add-on that no edition includes by default. A licence or a trial licence that includes the app_studio feature switches it on. The module is on by default, so the licence decides. Names and steps may change while it is in Beta.
App Studio has its own backup, separate from the bundles described above. Open it from App Studio > Operate > App Backup & Restore. The two are not interchangeable.
Where a bundle is a portable template, an App Studio backup is an exact copy of one workspace's App Studio: every app, its versions, checkpoints and documents, plus each app's git history. The database and the git data are captured together in a single .tar.gz so they cannot drift apart. Open edit sessions are not included, so anyone mid-edit when the backup ran will find that unsaved work missing after a restore.
Restoring refuses by default if the workspace already has apps, so you cannot overwrite by accident. Replace everything in this workspace deletes every existing app and its history first. Download a backup before choosing it.
Both backup and restore need the app:backup permission, which the Workspace Administrator role has. The page needs App Studio to be enabled in your license.
File format
The bundle is a single readable .mhub.json document:
schemaVersion. An integer, currently2. An older or newer value is refused rather than guessed at.manifest. The format dialect (workspace-placeholder-v1) and your description.connections,functions,globalFunctions,topics,pipelines,dashboards. The entity definitions. Each pipeline lists the global functions its code calls inglobalFunctions.
Everything inside refers to everything else by name within the workspace, never by internal identifier, which is what makes the same file usable in more than one place. Namespace paths carry the <workspace> placeholder described above.