Skip to content

Add scripts to migrate users from WebODM - #11

Open
MJohnson459 wants to merge 7 commits into
mainfrom
migrations
Open

MJohnson459 wants to merge 7 commits into
mainfrom
migrations

Conversation

@MJohnson459

Copy link
Copy Markdown
Collaborator

We updated both Django and Postgres when forking to WebUI. Both require migrations.

There are two main variants we need to consider.

  1. A default user will have the data stored in a docker volume.
  2. Expert users may use --db-dir to store the data on the native
    filesystem.

Both cases have extra checks in place to ensure we do not lose any data. The full migration in both cases is carefully constructed to allow WebODM to function afterwards if the migration fails. Data will always be preserved.

On first boot of WebUI, we check to see if the WebUI database exists. If it doesn't, we prompt for the migration process. If accepted, we start up both the old and new database and run pg_dump and pipe it to the new database. Finally once we know the data is secure, in the case of a native disk, we swap the new and old files. This gives us an updated Postgres database. We don't copy or modify the media files to avoid excessive disk consumption.

If the user declines to migrate their data, it is possible to still do the migration as long as the new database remains empty.

Found a WebODM database (docker volume webodm_dbdata). Import it? [Y/n] y
Importing from WebODM
================================
WebODM database: webodm_dbdata
WebODM media: docker volume webodm_appmedia (used in place)
================================
Starting the WebODM database...
Starting the OpenDroneMap WebUI database...
Copying the database...
time="2026-10-27T14:59:10Z" level=warning msg="/tmp/webui-fresh/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"
users: 2
projects: 1
tasks: 1
processing nodes: 1
Done! WebODM data imported.
docker compose -f docker-compose.yml -f docker-compose.nodeodm.yml up

The implementation is split into a python file importwebodm.py and a bash file import-webodm.sh. This is to aid reusability.

Once the postgres migration is complete, the next step is to migrate the Django models. This is done automatically when the app is first run. eg.

webapp      | Operations to perform:
webapp      |   Apply all migrations: admin, app, auth, contenttypes, guardian, nodeodm, sessions
webapp      | Running migrations:
webapp      |   Applying app.0045_django52_field_updates... OK
webapp      |   Applying app.0052_merge_0045_django52_field_updates_0051_init_basemaps... OK
webapp      |   Applying app.0053_alter_basemap_id_alter_plugindatum_id_and_more... OK
webapp      |   Applying app.0054_alter_setting_organization_name_and_more... OK
webapp      |   Applying auth.0012_alter_user_first_name_max_length... OK
webapp      |   Applying guardian.0002_generic_permissions_index... OK
webapp      |   Applying nodeodm.0010_django52_jsonfield_update... OK
webapp      |   Applying nodeodm.0011_merge_20260630_2319... OK
webapp      |   Applying nodeodm.0012_alter_processingnode_id_and_more... OK

I've been testing this locally with dummy data. We obviously need to be extra careful testing this so I would appreciate it if others could also test to make sure. If you do want to help, please make sure you have a spare copy of the data if its important!

When forking from WebODM the defaults were changed. This doesn't affect
the database but does avoid them being bundled in a later change.

I also added a CI check to catch this kind of missing migration.
0008 and 0010 imported ProcessingNode from nodeodm.models, so their
queries selected every column the current model has. Adding a field to
ProcessingNode in a later migration would make fresh installs fail at
0008 with "column does not exist". Resolve the model through
apps.get_model instead, as the Django migrations docs require.
There are two main variants we need to consider.

1. A default user will have the data stored in a docker volume.
2. Expert users may use --db-dir to store the data on the native
   filesystem.

Both cases have extra checks in place to ensure we do not lose any data.
The full migration in both cases is carefully constructed to allow
WebODM to function afterwards if the migration fails. Data will always
be preserved.

On first boot of WebUI, we check to see if the WebUI database exists. If
it doesn't, we prompt for the migration process. If accepted, we start
up both the old and new database and run `pg_dump` and pipe it to the
new database. Finally once we know the data is secure, in the case of a
native disk, we swap the new and old files. This gives us an updated
Postgres database. We don't copy or modify the media files to avoid
excessive disk consumption.

The next step is to migrate the Django models. This is done
automatically when the app is first run.

If the user declines to migrate their data, it is possible to still do
the migration as long as the new database remains empty.

The implementation is split into a python file `importwebodm.py` and a
bash file `import-webodm.sh`. This is to aid reusability.
On Windows, communicate() starts a reader thread for pg_dump's stdout
even though it was closed after being handed to psql, and that thread
raises "read of closed file".
WebODM and WebUI used the same container names (db, webapp, worker,
broker), so a WebODM installation stopped with webodm.sh stop blocked
WebUI from creating its containers. Service names are unchanged, so the
hostnames stored in the database (db, broker, node-odm-1) still resolve.
The import checks for a running WebODM database by image, not by name.
Both layouts now copy from a source into a target that this run owns:
the docker volume webodm_dbdata into this installation's volume, or the
directory given with --webodm-db-dir into the one given with --db-dir
(or the volume). The WebODM database is never written to. A start on a
WebODM database directory is refused with the import command.

Previously the path layout started Postgres 18 inside the WebODM
directory and swapped the two clusters afterwards, which needed a
temporary directory, its removal on failure, a leftover check and a
wait for the WebODM entrypoint's chown to finish.

A failed import removes the database volume it created, so the next
start offers the import again instead of starting empty. docker run is
called with MSYS_NO_PATHCONV=1 where container paths are passed, so Git
Bash does not rewrite them into Windows paths.
@MJohnson459

Copy link
Copy Markdown
Collaborator Author

While testing on Windows I found a few more issues. First, Git Bash rewrites arguments that look like POSIX paths before docker sees them, so /data/PG_VERSION arrived as C:/Program Files/Git/data/PG_VERSION.

Second, WebODM and WebUI used the same container names, so a WebODM stopped with webodm.sh stop blocked the import with a name conflict on db. The new containers are now prefixed with webui- with the service names unchanged so the stored hostnames still resolve.

Third, the --db-dir migration design was hard to get right. It started Postgres 18 in a temporary directory inside the WebODM data directory and swapped the clusters afterwards, so failures had to be handled at every step: a race with the WebODM entrypoint's chown crashed initdb on Windows, and the cleanup removed a directory inside the user's data. Nothing lost data, but the script had become hard to reason about and every fix added another special case.

Instead the import now copies into a separate target: --db-dir must point at a new directory, and the new --webodm-db-dir names the old one, which is never written to.

I will note that I'm struggling to keep the bash under control. In future we might move these scripts to Python if we can afford adding that as a requirement for the native install.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant