Skip to content

Repository files navigation

SOTI MobiControl → SureMDM Migration Tracker

A self-hosted web dashboard that tracks the migration of managed devices from SOTI MobiControl to SureMDM. You load a baseline of the devices that must move, the app polls SureMDM on a schedule, and it reconciles the two sides by serial number to show live, per-model and per-location migration progress.

It is tenant-agnostic — no hardcoded URLs, models, locations, or naming conventions. Any organisation can point it at their own SureMDM (and optionally SOTI) and start tracking in minutes.

Background: this started as an internal tool to track a real migration of ~13,000 devices and report progress to stakeholders during the rollout. It has since been rebuilt and generalised so any organisation can use it for their own SOTI MobiControl → SureMDM migration.

Dashboard


Contents


How it works

Two device populations are reconciled by a stable hardware identity:

Baseline The devices that exist in SOTI and must migrate — your denominator ("what has to move"). Loaded by manual entry, CSV import, or a live SOTI API pull.
Target snapshot The devices currently enrolled in SureMDM, pulled from its DeviceGrid API on a schedule (or imported from a JSON export).

On every sync the app normalises serial numbers on both sides (uppercase, strip separators, drop junk like N/A) and matches baseline → target. Each baseline device is assigned a state:

State Meaning
🟡 Pending In the SOTI baseline, not yet seen in SureMDM
🟢 Migrated Serial matched in SureMDM and checking in
🔵 Stale Matched once, but no SureMDM check-in within N days (configurable)
🔴 Unexpected A device in SureMDM with no matching baseline row — newly enrolled or off the original list

Progress is migrated / baseline total, sliced by device model and location/store. Each sync is recorded, feeding a velocity chart and an audit history.

Matching logic

  1. Primary key: normalised SerialNumber (survives OS wipe / re-enrolment).
  2. Fallback: normalised IMEI when a serial is missing on either side.
  3. Every match records which key matched, so you can spot weak matches.

Why group paths / naming don't matter

A common question: "Our SureMDM group paths and device names look nothing like the next company's — will it still work?" Yes.

  • Migration detection never uses group path or device name. It uses the serial number. A device counts as migrated the moment its serial appears in the SureMDM pull, no matter which folder/group SureMDM keeps it in. The group path is stored only for display (device detail & the Unexpected list).
  • The optional "Top locations" breakdown derives a store code from the device name using a regex you set in the UI (default ^(\d{3,4}), e.g. 0012_POS_030012). Change it to match your convention, or ignore it and rely on the overall/per-model progress. Nothing about location grouping affects whether a device is counted as migrated.

So the only thing a new company configures is its SureMDM URL + credentials — everything else adapts automatically.

Screens

Baseline Catalog
Baseline Catalog
Sync & Schedules Unexpected devices
Sync Unexpected
Device detail Login
Device detail Login

Quick start (Windows)

Requires Python 3.9+ and git.

git clone https://github.com/Avatorsinc/soti-to-suremdm.git
cd soti-to-suremdm

# create & activate a virtual environment
py -3 -m venv venv
venv\Scripts\Activate.ps1

# install dependencies
pip install -r requirements.txt

# configure secrets (edit SECRET_KEY and the admin password)
copy .env.example .env
notepad .env

# run
python run.py

Open http://localhost:5000.

If Activate.ps1 is blocked, run PowerShell once as: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, or just call the venv Python directly: venv\Scripts\python.exe run.py.

Quick start (Linux)

Requires Python 3.9+ and git.

git clone https://github.com/Avatorsinc/soti-to-suremdm.git
cd soti-to-suremdm

# create & activate a virtual environment
python3 -m venv venv
source venv/bin/activate

# install dependencies
pip install -r requirements.txt

# configure secrets (edit SECRET_KEY and the admin password)
cp .env.example .env
nano .env

# run
python run.py

Open http://localhost:5000.

Running it as a service (Linux, optional)

For anything beyond local testing, run it behind a production WSGI server such as gunicorn (add gunicorn to your environment):

pip install gunicorn
gunicorn -w 2 -b 0.0.0.0:8000 "run:app"

python run.py starts Flask's debug server — convenient for testing, but do not expose it to a network. Use gunicorn/uwsgi behind nginx for real deployments, and always set a strong SECRET_KEY.


First run & login

On first start the app creates its SQLite database and seeds a single admin account from your .env:

  • Username: ADMIN_USERNAME (default admin)
  • Password: ADMIN_PASSWORD (default changemechange this)

Viewing the dashboard is public (read-only); logging in unlocks all editing.

Using it

  1. Catalog — define your device models (with aliases that map raw SureMDM model strings to a canonical name) and, optionally, your locations/stores. Set the store-code regex and the "stale after N days" threshold here.
  2. Baseline — load the SOTI devices to migrate:
    • add them manually,
    • import a CSV (needs a serial column: serial / SerialNumber / DeviceSerial), or
    • pull from the SOTI API (configure the SOTI connector first).
  3. Sync & Schedules — set your SureMDM URL + credentials, then either:
    • hit Sync SureMDM (live API), or
    • Import from JSON export to reconcile against a SureMDM device JSON file (no live API needed — great for a first look), and
    • add schedules (every N minutes, daily at a time, or a raw cron expression) to poll automatically.
  4. Dashboard — watch overall progress, per-model and per-location breakdowns, the velocity chart, and the sync history update on every run. Devices found in SureMDM but not on your baseline appear under Unexpected.

Configuration

Connectors (SureMDM + SOTI URLs, usernames, passwords, API keys) are managed entirely in the UI on the Sync & Schedules page and stored in the database — nothing tenant-specific lives in the code.

.env (copied from .env.example) holds only host-level settings:

Variable Purpose
SECRET_KEY Flask session signing key — set a long random value
ADMIN_USERNAME / ADMIN_PASSWORD Seeded admin login
DATABASE_URL Optional; defaults to local SQLite migration_tracker.db
DEFAULT_SUREMDM_URL Optional pre-fill for the SureMDM URL field (blank by default)

Matching settings (UI → Catalog): the store-code regex and the stale threshold, applied live on the next reconcile.

SOTI connector

The SOTI MobiControl REST API is OAuth2-based and varies per tenant, so app/connectors/soti.py is a configurable adapter — set the base URL / credentials in the UI and adjust the field map if your tenant's JSON differs. Manual entry, CSV import, and the full SureMDM side work without it.

Project structure

config.py              host config (secrets via .env)
run.py                 dev entry point  ->  run:app for WSGI
requirements.txt
app/
  __init__.py          application factory (db, login, scheduler)
  models.py            User, Connector, Schedule, DeviceModel, Location,
                       BaselineDevice, TargetDevice, SyncRun, Setting
  connectors/
    suremdm.py         DeviceGrid pagination + JSON-export parsing
    soti.py            SOTI MobiControl adapter
  services/
    matching.py        serial normalisation + reconcile engine
    sync.py            SureMDM pull -> snapshot -> reconcile -> SyncRun
    baseline.py        manual / CSV / SOTI ingestion + catalog alignment
    scheduler.py       APScheduler jobs from Schedule rows
    bootstrap.py       first-run seeding
  views/               auth, dashboard, baseline, sync, catalog blueprints
  templates/           Bootstrap 5 + Chart.js UI
docs/screenshots/      images used in this README

Security notes

  • Always set a strong SECRET_KEY and change the default admin password.
  • python run.py is Flask's debug server — local use only. Put a real WSGI server (gunicorn/uwsgi) behind a reverse proxy for production.
  • Connector credentials are stored in the application database; keep the DB file and .env out of source control (both are gitignored) and restrict access to the host.

License

MIT

About

Track device migration from SOTI MobiControl to SureMDM: serial-number reconciliation, scheduled syncs, and per-model/per-store progress. Self-hosted Flask + SQLite dashboard.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages