Distribute Software and Firmware to Multiple Devices and Review Results

IT EngineersDevelopers

Getting Started

Basic Concept

Distribute installation files and firmware by device and version

Software and firmware files are used across servers, branch systems, production equipment, edge devices, and other environments.

Software and firmware distribution prepares installation packages, build artifacts, and firmware files, then configures deployment jobs based on the target devices and versions to apply.

You can build deployment flows that match your operating environment, whether distributing one file to multiple devices or applying different files and versions by device group.

Deployment Structure

text
Deployment File
    │
    ▼
Set Version Criteria
    │
    ▼
Select Target Devices
    │
    ├────────→ Server Group
    ├────────→ Branch Devices
    └────────→ Edge Devices
                    │
                    ▼
                 Review Results

This process lets you manage the files to distribute, target devices, and application criteria in a single flow.

Deployment Flow

Move from file preparation to deployment and result verification

A deployment job starts by preparing the installation file or firmware and defining the devices and versions that will receive it.

When the deployment runs under the specified conditions, files are transferred to each target device, where you can review per-device progress and file application results.

① Prepare deployment files

② Configure target devices

③ Set version and execution criteria

④ Distribute files to targets

⑤ Review application results

If needed, you can rerun the deployment for a specific device or device group.

Deployment Benefits

Manage multi-device file distribution and version control in one flow

When applying software and firmware across multiple devices, teams need to manage the deployment files, targets, versions, and execution results together.

Software and firmware distribution lets you manage deployment work by device in a single operational flow and track which files were delivered to which targets.

CategoryPer-Device ManagementDeployment Flow
Deployment TargetsReview targets device by deviceManage by device and group
Deployment FilesCheck applicable targets for each fileConfigure files as part of the deployment job
Version ApplicationReview version status by deviceDeploy according to version criteria
Execution ResultsReview results for each device separatelyReview results across targets together
RedeploymentReselect required devicesRerun by target or group

This creates a single operational flow from software and firmware distribution through result verification for each target.

IT Engineers

Deployment Files

Prepare installation packages and firmware files for deployment

First, prepare the installation packages, build artifacts, or firmware files to distribute.

A file can be used across multiple targets in a single deployment job, or different files can be assigned to different device groups depending on the operating environment.

For example, the following file types can be configured for deployment.

File TypeExample Use
Installation PackageApplication installer
Build ArtifactLatest build files and deployment packages
FirmwareFirmware for devices and edge systems
Update FileUpdate files for existing environments

After preparing the deployment files, configure the devices and groups that should receive them.

Target Setup

Organize servers, branch systems, and edge devices by group and device

Deployment targets can be configured as individual devices or grouped together.

For example, targets can be organized around operating criteria such as headquarters servers, nationwide branch systems, specific production equipment, or edge-device groups.

text
                Deployment Targets
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
      Server Group  Branch Group   Edge Group
        │           │           │
      Device A      Device D       Device G
      Device B      Device E       Device H
      Device C      Device F       Device I

This lets you select multiple devices in a single deployment job or limit the deployment scope to specific groups and devices.

Deployment Criteria

Set file versions and execution conditions by device

After configuring the targets, define which files should be deployed and under what conditions.

Link deployment-file versions to target devices, then configure execution to run immediately, on a schedule, or after another task completes.

Criteria to Configure

SettingConfiguration
Deployment FileSpecify the installation package or firmware
Applied VersionSpecify the file version to deploy to target devices
Target ScopeSelect all devices, a group, or individual devices
Execution ConditionRun immediately, on a schedule, or after another task completes
File PathSet the storage or application location on the target device

For example, you can deploy newly prepared firmware to a specific device group or distribute build artifacts to designated servers after a build job completes.

Multi-Device Deployment

Transfer files to multiple target devices in one deployment workflow

After defining the deployment criteria, configure a multi-device flow that sends one file to multiple targets.

In Flow Canvas, connect one deployment source to multiple target devices to distribute the same file across servers, branch systems, edge devices, and other environments.

text
                  Deployment File
                      │
                      ▼
                 Deployment Job
            ┌─────────┼─────────┐
            ▼         ▼         ▼
         Server      Branch     Edge
            │         │         │
            ▼         ▼         ▼
          Verify       Verify      Verify

You can process all target devices together or select specific groups and devices based on operational requirements.

Result Management

Review per-target status and rerun deployment for required devices

When a deployment runs, use Runs and the job details to review the overall deployment status and results for each target.

During review, you can check each device's progress, processed files, execution time, and related details.

Deployment Result Flow

text
Run Deployment
    │
    ▼
Review Overall Job Status
    │
    ├── Completed
    │      │
    │      └── Review per-target application results
    │
    ├── Running
    │      │
    │      └── Review progress and processing status
    │
    └── Needs Review
           │
           ▼
       Review Details
           │
           ▼
       Select Target Device
           │
           ▼
       Rerun Deployment

ItemDetails
Target DeviceServer or device that received the files
Progress StatusCurrent processing status of the deployment job
File ResultFile application result for each target device
Execution TimeDeployment start and completion times
Redeployment TargetDevice or group to rerun

If a deployment result for a specific device needs to be reviewed again, rerun the required deployment for that target or device group.

Developers

Distribute one artifact to multiple devices and aggregate the application results by device

Integration Setup

Prepare shared API calls and status values

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)

Use the following values to determine transfer status. There are five terminal states, and Complete (2) is the successful state.

Status ValueMeaningTerminal
2CompleteYes
4ErrorYes
5CancelledYes
9Partial CompleteYes
99FailedYes
1 · 6 · 12 · 13Starting · Transferring · Synchronizing · ReceivingNo

Configure Deployment Targets

Define device groups as lists and resolve their identifiers

Manage deployment targets as lists. If you work with device names, resolve them to identifiers first.

def resolve_device(name):
    result = api("GET", "/api/devices/resolve", params={"name": name}) or {}
    devices = result.get("devices") or []
    count = result.get("matchCount", len(devices))

    if count != 1 or not devices:
        raise RuntimeError(f"{name}: {count} matches")

    return devices[0]["deviceId"]


GROUPS = {
    "server": ["srv-01", "srv-02", "srv-03"],
    "branch": ["branch-seoul", "branch-busan"],
    "edge":   ["edge-line-01", "edge-line-02"],
}

RESOLVED = {
    group: [resolve_device(name) for name in names]
    for group, names in GROUPS.items()
}

You can also filter the complete device list by condition.

result = api("GET", "/api/devices", params={"page": 1, "size": 200}) or {}

edge_devices = [d["deviceId"] for d in result.get("devices") or []
                if d["name"].startswith("edge-")]

The device list is returned in the data.devices array.

Design Versioned Paths

Make the deployment-file version visible in the path

Record the deployed version in the path so you can see which version was applied where.

def package_path(product, version):
    return f"/release/{product}/{version}"


def target_path(product, version):
    return f"/opt/{product}/{version}"


PRODUCT, VERSION = "app", "2.14.0"

Keeping separate version folders preserves previous versions and makes rollback easier if a problem occurs. If every release overwrites the same path, there is no previous version to roll back to.

Multi-Device Deployment

Send one file to multiple devices

A single transfer handles one target device. Create a transfer for each target and store the returned monitorId by device to track progress and results.

def deploy(source, targets, product, version, target_paths=None):
    src = package_path(product, version)

    # pass a list when targets need different paths; the order must match
    paths = target_paths or [target_path(product, version)] * len(targets)

    return {
        device: api("POST", "/api/transfers/manual", {
            "sourceDevice": source,
            "targetDevice": device,
            "targetPath": path,
            "sourcePaths": [src],
            "sendAllFolder": True,
            "checkIntegrity": True,
            "transferOptions": {"target-action": "overwrite"},
        })["monitorId"]
        for device, path in zip(targets, paths)
    }


transfers = deploy("device-build-01", RESOLVED["edge"], PRODUCT, VERSION)

Within a version folder, redeployments usually send the same files again, so use overwrite. Using numbering creates an additional copy every time you redeploy.

Enable integrity verification. Applying corrupted firmware can prevent a device from booting.

If devices share field network links, apply a rate limit so deployment traffic does not interfere with other work.

"transferOptions": {
  "target-action": "overwrite",
  "limitRate": 20480
}

The limitRate unit is KB/s.

Aggregate Results

Collect and review application status by device

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)

Continue checking the remaining devices even if one fails so you can determine how far the deployment was applied.

def deploy_report(transfers, timeout=3600):
    results = {}

    for device, monitor_id in transfers.items():
        try:
            results[device] = wait(monitor_id, timeout=timeout)
        except TimeoutError:
            results[device] = {"status": None}

    succeeded = [d for d, r in results.items()
                 if r.get("status") == STATUS_COMPLETE]
    failed = [d for d in results if d not in succeeded]

    return succeeded, failed, results


succeeded, failed, results = deploy_report(transfers)

print(f"{len(succeeded)} succeeded / {len(failed)} failed")

for device in failed:
    detail = results[device]
    print(f"  {device:20} {detail.get('statusName') or detail.get('status')}")
ItemDetails
statusTransfer status by device
percentProgress
fileCount · totalSizeNumber and total size of applied files
endDateCompletion time

Redeploy Failed Devices

Skip successful devices and redeploy only failed targets

Rerunning the entire deployment retransfers files to devices that already succeeded. Select only the failed targets and rerun them.

for device in failed:
    monitor_id = transfers[device]
    rows = failed_files(monitor_id)

    if rows:
        # reached the device, but some files failed
        print(f"{device}: retried {retry_failed(monitor_id)} files")
    else:
        # no per file record means the transfer never started
        state = api("GET", f"/api/devices/{device}/connectivity") or {}
        print(f"{device}: transfer never started - connected {state.get('isConnected')}"
              f" ({state.get('stateLabel')})")

If there is no per-file failure record, check device connectivity first. After the connection is restored, rerun the deployment.

if still_offline:
    retry_transfers = deploy("device-build-01", recovered, PRODUCT, VERSION)
    _, still_failed, _ = deploy_report(retry_transfers)

Review Deployment History

Check which version was applied to each device and when

from datetime import datetime, timedelta, timezone


def paginate(path, params=None, limit=200, max_pages=50):
    query = dict(params or {})
    query["limit"] = limit
    cursor = None

    for _ in range(max_pages):
        if cursor:
            query["cursor"] = cursor

        result = api("GET", path, params=query) or {}

        for record in result.get("data") or []:
            yield record

        pagination = result.get("pagination") or {}

        if not pagination.get("hasMore"):
            return

        cursor = pagination.get("nextCursor")

        if not cursor:
            return


end = datetime.now(timezone.utc)
fmt = "%Y-%m-%dT%H:%M:%SZ"

for row in paginate(f"/api/devices/{device_id}/transfer-history", params={
    "startDate": (end - timedelta(days=30)).strftime(fmt),
    "endDate": end.strftime(fmt),
}):
    print(row.get("startDate"), row.get("statusName"), row.get("monitorId"))

Recording the deployment identifier and version together in the application links deployment history to the corresponding version.

db.insert("deployments", {
    "product": PRODUCT,
    "version": VERSION,
    "device": device,
    "monitorId": transfers[device],
    "result": "done" if device in succeeded else "failed",
})
ItemDetails
Deployment FileTransferred package and version
TargetDevice and group
ResultSuccess or failure by device
RedeploymentTargets rerun and their results
HistoryRecent deployment records by device