Migrate Large Volumes of Files from NAS and File Servers to the Cloud

Migrate file-server data to cloud storage with initial transfer, change synchronization, cutover verification, and progress tracking.

IT EngineersDevelopers

Getting Started#

Basic Concepts#

Migrate data from existing file environments to cloud storage

Enterprise files are distributed across various environments, including NAS, SMB shared folders, NFS storage, and file servers.

Cloud migration consists of transferring existing files to designated cloud storage and continuously applying changes that occur during the migration period to manage file state until the final cutover.

  1. Existing File Environment
  2. Initial File Migration
  3. Changed Files Synchronization
  4. Check Cutover Status
  5. Cloud Cutover

By connecting the initial file migration with changed-file synchronization, you can migrate data from the existing file environment to the cloud and manage file state until the cutover.

Migration Flow#

Continue from initial file migration through changed-file synchronization to final cutover

File migration consists of transferring the initial data to the cloud and continuously applying files created or modified during the migration period.

  1. ```flow
  2. Source Environment Connection
  3. Initial File Migration
  4. Detect Changes
  5. Changed Files Synchronization
  6. Check Cutover Status
  7. Cloud Cutover

This flow lets you manage the initial files and subsequent changes together while proceeding with the cloud cutover.

### Cutover Benefits ⁣3⁣

**Continue the existing file flow into the cloud environment**

Configuring initial migration and change synchronization together lets you manage file state across the source environment and cloud throughout the cutover period.

|Category|Existing File Environment|Cloud Cutover Flow|
|---|---|---|
|Initial Data|Managed in the existing environment|Migrate large volumes of files to the cloud|
|Changed Files|Check status by file|Continuously synchronize changes|
|Cutover Preparation|Check file state by environment|Verify based on migration and synchronization results|
|Subsequent Work|Use the existing storage location|Connect based on the cloud storage location|



## IT Engineers ⁣4⁣



### Source Environment ⁣5⁣

**Connect migration paths from NAS and file servers**

First, connect the NAS, SMB, NFS, or file server where the files to be migrated are stored, and specify the file paths to migrate to the cloud.

Set the access scope so files can be read and changes can be checked in each source environment.

```pipe
- NAS
- SMB
- NFS
- File Server
Migration Flow

By connecting multiple source environments, you can configure migration tasks for each file location in a single flow.

Cloud Connection#

Configure cloud storage and target paths

After connecting the source environment, configure the cloud storage and target location where files will be stored.

Specify the target bucket or container and storage path, and connect them to the source file path.

  1. Original Storage
  2. Migration Flow
  3. Cloud StorageTarget Path

Configuring the source paths to migrate and cloud storage locations prepares the file flow for initial migration and change synchronization.

Initial Migration#

Transfer existing large volumes of files to the cloud

After configuring the migration environment, transfer existing files and large datasets to cloud storage.

During execution, you can check the number of processed files, total capacity, progress, and task status.

  1. Original Files
  2. Initial Transfer
  3. Cloud Storage

Use the initial migration results as the baseline for subsequent changed-file synchronization and final cutover.

Change Synchronization#

Continuously apply files changed during the migration period

Even after the initial migration, new files may be created or existing files may be modified in the original file environment.

Use New ItemsandModified Items` to identify changes and configure those files to be synchronized to cloud storage.

Original Environment
New / Modified
Change Detection
Sync
Cloud Storage

By continuously applying changed files, you can continue managing file state in the cloud after the initial migration.

Cutover Verification#

Verify initial migration and change-synchronization results

Before the final cutover, check the initial file migration results together with the synchronization status of subsequently changed files.

You can verify cutover readiness between the source and cloud environments based on file count and capacity, recent execution results, and synchronization status.

Initial Transfer
Change Sync
Migration Status
Ready
Check Items

Reviewing the initial migration and change-synchronization results together lets you determine when to switch operations to the cloud environment.

Final Cutover#

Switch file operations to the cloud environment

After cutover verification is complete, set cloud storage as the baseline environment for subsequent file operations.

Connect the storage locations of business systems, applications, and subsequent file tasks to the cloud to continue the file flow.

  1. NAS / File ServerBefore
  2. Migration Flow
  3. Cloud Storage
  4. App / Service / WorkflowAfter

After migration, you can connect subsequent work and automation tasks based on files stored in the cloud.

Operations Response#

Review and rerun migration and synchronization tasks

For tasks requiring review during migration, use Runs and detailed execution records to check source connectivity, target paths, file-processing results, and synchronization status.

Migration Run
Status Check
Complete
Check Items
View Details
Update Settings
Retry
Result Confirm

Check Item Details Follow-up Action
Source Environment NAS, SMB, NFS, and File Server Connections and Paths Check connection and path
Cloud Storage location and access status Check target settings
Initial Migration Large-file processing results Rerun the required task
Change Synchronization Status of added and modified files Rerun the synchronization task
Execution History Task status and detailed processing results Check settings and rerun

Developers#

Move an existing shared folder to the cloud and continuously apply changes during the migration period

Integration Setup#

Prepare common API-call code and path notation

import os
import requests

BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com").rstrip("/")
TOKEN = os.environ["INNORIX_ACCESS_TOKEN"]
WORKSPACE_ID = os.getenv("INNORIX_WORKSPACE_ID")   # optional; falls back to the current workspace

STATUS_COMPLETE = 2
TERMINAL = {2, 4, 5, 9, 99}          # complete / error / cancelled / partial / failed
NOT_SUCCEEDED = {4, 5, 9, 99}


def api(method, path, body=None, params=None):
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    }

    if WORKSPACE_ID:
        headers["x-workspace-id"] = WORKSPACE_ID

    response = requests.request(
        method, BASE_URL + path,
        headers=headers, json=body, params=params, timeout=30,
    )

    payload = response.json() if response.content else {}

    if not response.ok:
        raise RuntimeError(payload.get("message") or f"HTTP {response.status_code}")

    return payload.get("data")


def is_terminal(detail):
    return detail.get("isTerminal", detail.get("status") in TERMINAL)
import base64
import time


def encode_path(device_id, raw_path):
    normalized = str(raw_path or "").replace("\\", "/")
    token = base64.b64encode(normalized.encode("utf-8")).decode("ascii")
    return f"{device_id}_ino_{token}"


def now_iso():
    return time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())

Transfer status is determined by the values below. There are five terminal states, and the successful value is Complete (2).

Status Value Meaning Terminal
2 Complete Yes
4 Error Yes
5 Canceled Yes
9 Partially Complete Yes
99 Failed Yes
1 , 6 , 12 , 13 Starting, Transferring, Synchronizing, Receiving No

Determine the Migration Scope#

Check the number and capacity of files to migrate in advance

You need to know the scale before starting the migration so you can plan the schedule.

def scan(device_id, path, max_pages=200):
    page = api("POST", f"/api/devices/{device_id}/files/search",
               {"path": path, "pageSize": 1000})

    total = size = 0
    search_id = page.get("searchId")

    try:
        for _ in range(max_pages):
            for item in page.get("items") or []:
                if item["type"] == "file":
                    total += 1
                    size += item.get("size") or 0

            if not page.get("hasMore"):
                break

            page = api("GET", f"/api/devices/{device_id}/files/search",
                       params={"cursor": page["nextCursor"]})
    finally:
        # stop the device scan once the count is done
        if search_id:
            api("POST", f"/api/devices/{device_id}/files/search/cancel",
                {"uuid": search_id})

    return total, size


count, size = scan("device-nas-01", "/share/team")
print(f"{count} files / {size / (1024 ** 3):.1f}GB")

A search actually scans the device's disk. If it is not stopped, scanning continues even after the check is complete.

Initial Migration#

Send existing large volumes of files to the cloud

Sending everything at once increases the scope that must be restarted after a failure. Split the transfer by top-level folder.

def target_for(folder, base):
    return folder.replace("/share", base, 1)


def migrate(source, target, folder, base):
    transfer = api("POST", "/api/transfers/manual", {
        "sourceDevice": source,
        "targetDevice": target,
        "targetPath": target_for(folder, base),
        "sourcePaths": [folder],
        "sendAllFolder": True,
        "transferOptions": {"target-action": "overwrite"},
    })

    return transfer["monitorId"]


FOLDERS = ["/share/team/design", "/share/team/docs", "/share/team/archive"]

transfers = {
    folder: migrate("device-nas-01", "device-cloud-01", folder, "/migrated")
    for folder in FOLDERS
}

Use the overwrite destination policy. Reruns are common during migration, and numbering causes copies to accumulate.

Folders containing very large numbers of files can take a long time to transfer. If interrupted, the transfer can resume, so there is no need to retransmit from the beginning.

def wait(monitor_id, timeout=3600, interval=3):
    deadline = time.time() + timeout

    while time.time() < deadline:
        detail = api("GET", f"/api/transfers/{monitor_id}")

        if is_terminal(detail):
            return detail

        time.sleep(interval)

    raise TimeoutError(monitor_id)


def failed_files(monitor_id):
    result = api("GET", f"/api/transfers/{monitor_id}/files", params={
        "state": "any", "size": 500,
    }) or {}

    return [r for r in (result.get("children") or [])
            if r.get("status") in NOT_SUCCEEDED]


def retry_failed(monitor_id):
    rows = failed_files(monitor_id)

    if not rows:
        return 0

    api("POST", f"/api/transfers/{monitor_id}/retry", {
        "filesRetry": [
            {"filePath": r["sourceFilePath"], "isDir": bool(r.get("isFolder"))}
            for r in rows
        ]
    })

    return len(rows)
for folder, monitor_id in transfers.items():
    detail = wait(monitor_id, timeout=86400)

    print(f"{folder:28} {detail.get('statusName')}"
          f" {detail.get('fileCount')} files")

    if detail["status"] != STATUS_COMPLETE:
        print(f"  retried {retry_failed(monitor_id)} files")

Changes Synchronization#

Continuously apply files changed during the migration period

Files continue to change at the source even while the initial migration takes several days. Send only the changes periodically.

transfer = api("POST", "/api/transfers/manual", {
    "sourceDevice": "device-nas-01",
    "targetDevice": "device-cloud-01",
    "targetPath": "/migrated/team/design",
    "sourcePaths": ["/share/team/design"],
    "sendAllFolder": True,
    "incremental": True,
    "transferOptions": {"target-action": "overwrite"},
})

The default is off. The device agent calculates changes and sends only files added or modified since the last transfer.

Always use overwrite for incremental transfers. With numbering, modified files accumulate under new names, causing the cloud state to differ from the source.

If you cannot keep the script running, register it as hourly automation.

def build_sync(name, source, source_path, target, target_path, schedule):
    return {
        "name": name,
        "flowName": name,
        "transferType": "normal",
        "timezone": "Asia/Seoul",
        "step": 1,
        "isUpcoming": False,
        "details": [
            {
                "senderId": source,
                "receiverId": target,
                "sourceItem": [
                    {
                        "hash": encode_path(source, source_path),
                        "filePath": source_path,
                        "isDir": True,
                    }
                ],
                "targetPath": encode_path(target, target_path),
                "step": 1,
                "transferOptions": {
                    "noSchedule": False,
                    "target-action": "overwrite",
                    "send-fileoption": {},
                },
            }
        ],
        "schedules": [schedule],
    }


HOURLY = {
    "type": "hour",
    "startDateType": "now",
    "startDate": now_iso(),
    "timezone": "Asia/Seoul",
}

syncs = {
    folder: api("POST", "/api/automations", build_sync(
        f"sync {folder}", "device-nas-01", folder,
        "device-cloud-01", target_for(folder, "/migrated"),
        HOURLY))["automationId"]
    for folder in FOLDERS
}

There are four items that must be observed in automation requests.

Item Configuration
isUpcoming Must be false. The server default true ignores the schedule in the request and replaces it with a five-minute one-time schedule. Steps with triggerAutomation are forced to false by the server, so specify it directly only on the first step without a trigger
step Include it at both the top level and in details. It is the hop position within the flow
sourceItem Include both hash (path token) and filePath (plain-text path)
syncType Put it inside transferOptions. 1 is one-way and 2 is two-way

Registration succeeds even if all four items are omitted, but behavior changes at execution time. If a recurring schedule was registered but ran only once, check isUpcoming first.

Progress Statistics#

Query progress by transfer and synchronization run

def progress(transfers):
    for folder, monitor_id in transfers.items():
        detail = api("GET", f"/api/transfers/{monitor_id}")

        print(f"{folder:28} {detail.get('statusName'):12}"
              f" {detail.get('percent', 0):>5}%"
              f" {detail.get('fileCount', 0)} files")

Review each run's synchronization-automation results in the execution history.

for folder, automation_id in syncs.items():
    runs = api("GET", f"/api/automations/{automation_id}/executions") or []
    latest = runs[0] if runs else {}

    print(f"{folder:28} runs {len(runs):>3}"
          f" last {latest.get('startTime')} {latest.get('status')}")

The latest execution appears first in the array. If the history is empty, the automation was registered but not executed, so check isUpcoming and the start condition.

Cutover Verification#

Verify that source and cloud files match

To determine the cutover point, verify that files in the two environments match. Compare the source and target file lists by folder to find missing files.

def list_names(device_id, path):
    names, page = set(), 1

    while True:
        result = api("GET", f"/api/devices/{device_id}/files", params={
            "path": path, "page": page, "size": 200, "type": "file",
        }) or {}

        names.update(item["name"] for item in result.get("items") or [])

        if page >= (result.get("lastPage") or 1):
            return names

        page += 1


missing = list_names("device-nas-01", "/share/team/design") \
        - list_names("device-cloud-01", "/migrated/team/design")

print(f"{len(missing)} missing")

If files are missing, retransmit the corresponding folder to align the lists. To compare size as well as names, include size in the list.

Cutover and Cleanup#

Stop synchronization and make the cloud the baseline environment

After the cutover, stop the synchronization automation. If the source continues to be applied, changes on the cloud side may be overwritten.

for automation_id in syncs.values():
    api("POST", f"/api/automations/{automation_id}/pause", {"pause": True})

Delete it after confirming there are no problems for a period of time. Leaving it paused may allow it to be accidentally resumed later.

for automation_id in syncs.values():
    api("DELETE", f"/api/automations/{automation_id}")
Check Item Details
Migration Scope File count and total capacity
Initial Migration Transfer status by folder
Change Synchronization Execution results by run
Verification Whether source and target file lists match
Cutover Stop and clean up synchronization