The previous DataMind OS installation ran its services under systemd — PostgreSQL, ClickHouse, carte, Lurien, the API and frontend, and a handful of others — with an NFS share between some of them. The containerised deployment replaces all of it, but it will not retire the old units for you: two PostgreSQL engines on one data directory, or an old container holding a port the new stack needs, corrupts or blocks the install. This page runs that one-off host migration.
The migration is a shell script, migrate-host.sh, shipped with the DataMind Installer. It acts only on a fixed allowlist of units, waits for each stop to finish, and tees its whole run to a timestamped log. Run it before you deploy the DataMind Installer stack, on the host itself.
Confirm the following on the host. The script enforces the first two itself and refuses to run without them.
sudo) — otherwise it exits with ERROR: run as root (sudo).systemctl is present) — otherwise it exits with ERROR: not a systemd host.docker.service is not active the script prints a warning and refuses, because once the bare-metal platform is gone Docker must run the stack next. You can override the refusal with --force, but only if you intend to bring Docker up immediately after.This step is destructive and one-way. It stops, disables and permanently neutralises the listed systemd units. Decide which units you expect it to touch before you run it, and read the dry run first.
Always run the dry run first. It echoes every command it would run (+ <command>) without changing anything, and it never prompts.
sudo ./migrate-host.sh --dry-run
Read the output and check it against what you expect on this host. In dry-run mode nothing is removed, no unit is masked, /etc/exports is not edited, and no containers are removed.
Keep the terminal output: it is the same sequence the real run performs, in order.
When the dry run shows only units you expect, run it for real. Without --yes the script prompts Proceed? [y/N] and aborts on anything but y, Y, yes or YES.
sudo ./migrate-host.sh --yes
The script is intended to be run first and standalone, before the DataMind Installer stack is deployed. The intended order is:
sudo ./migrate-host.sh.env) in the DataMind Installer.docker compose up).| Flag | Effect |
|---|---|
--dry-run | Echo each command and change nothing. No prompt. |
--yes, -y | Skip the Proceed? [y/N] prompt. Required for unattended runs. |
--force | Proceed even when docker.service is not active. |
--stop-timeout=N | Seconds to allow each unit to stop before it is force-killed (default 60). |
-h, --help | Print the header comment block as usage and exit. |
Unknown options print Unknown option: <arg> (try --help) and exit with code 2.
| Variable | Meaning | Default |
|---|---|---|
STOP_TIMEOUT | Per-unit stop timeout in seconds. | 60 |
BACKUP_DIR | Where masked-away and moved unit files are kept. | /root/disabled-units |
SHARED_DATA_DIR | The old Kettle shared directory, used when matching lines in /etc/exports. | empty (falls back to the KettleRepository/shared / pentaho match) |
LOG | Path of the run log. | /var/log/unistream-migrate-host-<YYYYMMDD-HHMMSS>.log |
OLD_REDASH_PROJECT | Compose project name of a leftover bare-metal Redash stack. | redash |
LEFTOVER_CONTAINERS | Space-separated names of standalone leftover containers to remove. | qdrant |
Set these inline before the command if you need different values, for example sudo STOP_TIMEOUT=120 ./migrate-host.sh --yes.
Once the DataMind Installer stack is up, you can start the same script from its API instead of an SSH session. Set the VM user and the platform URL first — the API refuses the run without them:
# Set the SSH/OS user on the VM, used to substitute <USER> in generated .env values.
curl -X POST https://<host>:<port>/api/migrate/vm-user \
-H 'Content-Type: application/json' \
-d '{"vmUser":"ubuntu"}'
# Start the migration; the response carries a jobId.
curl -X POST https://<host>:<port>/api/migrate/run-hostBoth endpoints require the admin role. If the VM user is unset you get VM user must be set before running host migration; if PLATFORM_URL is unset you get PLATFORM_URL must be set before running host migration.
Stream the run with Server-Sent Events, or follow it in the UI:
GET /api/deployment/jobs/<jobId>/logs
How the Installer executes it: it copies its bundled migrate-host.sh to the host path MIGRATE_HOST_SCRIPT_PATH (default /opt/delamain/migrate-host.sh), then starts a short-lived, fully privileged container from the DataMind Installer image and runs the script inside the host's namespaces:
docker run --rm --privileged --pid=host \ unistream.azurecr.io/delamain:<version> \ nsenter -t 1 -a -- bash /opt/delamain/migrate-host.sh --yes
nsenter -t 1 -a enters the namespaces of PID 1 on the host, which is what makes systemctl stop inside the container act on the host's units rather than on the container's own. The helper passes --yes, so there is no prompt. The migration job is treated as hung — and aborted — if it produces no output for 15 minutes (No output for 15 minutes — aborted as hung).
The helper container is why the DataMind Installer needs --privileged and --pid=host for this one task, and why the image ships util-linux (for nsenter). No other DataMind Installer action runs privileged.
Running it from the Installer is equivalent to running it by hand: the same units are neutralised. Do not run both.
The script neutralises exactly these units and nothing else. For each, it stops the unit (waiting for it to actually stop), disables it, then masks it — or, for a unit that lives in /etc/systemd/system, moves the unit file aside into BACKUP_DIR because mask cannot cover it there.
| Unit | Old role |
|---|---|
carte.service | Pentaho Data Integration / carte server |
clickhouse-api.service | ClickHouse analytics API |
clickhouse-server.service | ClickHouse server |
clickhouse-share-reconnect.service | Reconnects the ClickHouse NFS share |
clickhouse-share-reconnect.timer | Schedules the share reconnect |
jdbc-bridge.service | ClickHouse JDBC bridge |
lurien.service | Lurien service |
meltano-api.service | Meltano API |
minio.service | MinIO object store |
reporting-system.service | Reporting system |
service-metrics-api.service | Service metrics API |
unistream-api.service | Old backend API |
unistream-frontend.service | Old frontend |
postgresql.service | Old PostgreSQL |
nginx.service | Old reverse proxy |
Anything not on this list it never touches — in particular Docker, the operating system itself, and Jenkins, which is deliberately left running so the old environment's values stay readable for import.
A unit that is not present or already masked is reported and skipped, so re-running the script is safe.
It stops nfs-server.service, unexports the share, removes the matching export line(s) from /etc/exports (after backing the file up to BACKUP_DIR/exports.bak.<timestamp>), re-exports, and then neutralises the NFS units:
| Unit | Role |
|---|---|
nfs-server.service | NFS server |
nfs-blkmap.service | NFS block layout mapping |
rpcbind.socket | RPC portmapper socket |
rpcbind.service | RPC portmapper |
The old bare-metal Redash ran as its own Docker Compose project (default name redash), so the systemd steps above do not touch it. Its PostgreSQL mounts the same volume the new stack adopts, so leaving it up would mean two engines on one data directory. The script removes the redash project's containers but keeps its volumes for the new stack to adopt.
It also removes standalone leftover containers whose names collide with the new stack's — by default qdrant, an old container squatting on port 6333 (the new stack's own container is unistream-qdrant, so a bare qdrant is always a leftover). Extend the set with LEFTOVER_CONTAINERS="qdrant foo bar".
Any other non-DataMind Compose project is only reported, never removed — the script lists it under a note asking you to review or retire it manually.
The run ends with systemctl daemon-reload and systemctl reset-failed to clear any failed-unit state left behind.
From the point the real work begins, the script redirects stdout and stderr through tee, so the console output and the log file are identical. The log is:
/var/log/unistream-migrate-host-<YYYYMMDD-HHMMSS>.log
Override the location with LOG=/path/to/file.log. Because every command is echoed before it runs (+ systemctl stop …) and every stop's result is printed, the log reads like a manual session and is the artifact to attach if the migration goes wrong. The last line of the console output is Full log: <path>.
redash (or listed in LEFTOVER_CONTAINERS) — these are reported for manual review only.After the run, confirm no platform unit is set to auto-start. The script prints this check itself; run it again at any time:
systemctl list-unit-files --state=enabled \ | grep -iE 'carte|clickhouse|pentaho|kettle|meltano|lurien|minio|postgres|unistream|reporting|jdbc|nfs|nginx'
Expected output is (none — good); any unit listed means it is still enabled and you should stop and disable it.
Also confirm no leftover container is holding a port the new stack needs:
docker ps --format '{{.Names}}\t{{.Ports}}'Then continue with the deployment: deploy the DataMind Installer, set .env, and start DataMind OS.
There is no automated rollback. The script stops, disables and masks the legacy units permanently; it does not restore them, and it makes no attempt to reverse itself if you change your mind.
What can be recovered manually, if you must:
/etc/systemd/system) are in BACKUP_DIR (default /root/disabled-units). The script prints the list at the end. Restore one with mv back to /etc/systemd/system/ followed by systemctl daemon-reload.systemctl unmask <unit> and re-enable if needed./etc/exports was backed up to BACKUP_DIR/exports.bak.<timestamp> before the line was removed.Because reversing all of that by hand is error-prone, treat the decision to run the real migration as final. If you are unsure of the outcome, stop after the dry run and move to new hardware instead.