Network Server migration

Hello,

about 100 end-devices are handled by the embedded network server of a gateway. I want to migrate them on a new ChirpStack network server.

How to proceed in a way that do not rely on the end-device build-in feature (Rejoin Mode) that sends a Join Request after (by default) 32 detection signals failure? Because this feature can not be set by any downlink frame and I can’t afford to set it through a NFC mobile application…

What is the best way to proceed?

Regards

You can copy the databases (including device-session keys) to your V4 instance.

Follow this guide:

https://www.chirpstack.io/docs/v3-v4-migration.html

I have the same challenge at the moment where I need to migrate some devices from a dev LNS over to the prod LNS.

There is an Activation API we can use to copy over the frame counters and keys to the new LNS. See here https://www.chirpstack.io/docs/chirpstack/api/api.html#deviceactivation
I believe the steps are:

  1. create the device in the new LNS - either via API or UI
  2. get the device activation details from the current LNS
  3. post the device activation details to the new LNS

Now that you’ve brought this back up, I think I misread the question. The v3-v4 migration does give the general backup process but OP doesn’t have a v3 instance (to my knowledge).

Your method is definitely optimal if you only want to move a portion of devices over, but if you want to move all devices, device profiles, session keys, etc… you could do something along these lines:

0) ssh to machine and enter the chirpstack-docker folder
$ ssh <user>@<chirpstack-server>
$ cd chirpstack-docker

1) Ensure Docker is down so no containers try to write while backup occurs. 
$ sudo docker compose down

2) Copy redis data dump
$ sudo cp /var/lib/docker/volumes/chirpstack-docker_redisdata/_data/dump.rdb redisbackup.rdb

3) Create a file for psql backup dump
$ touch postgresbackup

4) Start only the postgres container
$ sudo docker compose up postgres -d

5) dump all sql backup commands from container and pipe into backup file
$ sudo docker exec -it chirpstack-docker-postgres-1 pg_dumpall -c --no-password -h localhost -U postgres > postgresbackup


Restoral process

0) ssh to machine and enter chirpstack-docker folder
$ ssh <user>@<chirpstack-server>
$ cd chirpstack-docker

1) Ensure Docker is down so no containers try to write while backup occurs. 
$ sudo docker compose down

2) Make a copy of current redis database (As a safe measure)
$ sudo cp /var/lib/docker/volumes/chirpstack-docker_redisdata/_data/dump.rdb redisdatareplaced.rdb

3) Delete old data dump and copy backup into volume
$ sudo rm /var/lib/docker/volumes/chirpstack-docker_redisdata/_data/dump.rdb
$ sudo cp redisbackup.rdb /var/lib/docker/volumes/chirpstack-docker_redisdata/_data/dump.rdb

4) Change permissions of new dump file
$ sudo chown lxd:<user>l /var/lib/docker/volumes/chirpstack-docker_redisdata/_data/dump.rdb
$ sudo chmod 600 /var/lib/docker/volumes/chirpstack-docker_redisdata/_data/dump.rdb

5) Start only postgres containers
$ sudo docker compose up postgres -d

6) Rebuild SQL database from backup
$ sudo docker compose exec -T postgres psql -h localhost -U postgres < postgresbackup

I wrote this a while ago and it’s slightly sloppy, but that’s the idea.

My context was not well detailed:

The end-devices are activated on the embedded Network Server of the gateway.
So I have to use its HTTP API ro retrieve them.

For each device, the available information are:

{
    "devEUI": "181558ffae187614",
    "fCntUp": 70,
    "fCntDown": 3,
    "appKey": "f81123b135f7db3edb51b9af69f8a060",
    "devAddr": "0631ab87",
    "appSKey": "239c311da72eedde20e4333465066c51",
    "nwkSKey": "19de7fefe996bb45cad1993a9ce3e78f",
 }

Then I use the gRPC ChirpStack v4 API to add and activate the devices on the new ChirpStack v4 Network Server.

The purpose is to migrate from the Gateway NS to ChirpStark v4 NS with no (Re)JOIN.

The LoRaWAN frames are received on the ChirpStack NS (I can check that inside the Gateway panel).

But the frames don’t appear in the Events / LoRaWAN frames panels of the device.

In the ChirpStack logs appears this kind of message:

None of the device-sessions for dev_addr resulted in valid MIC

I have checked the network and application session keys by adding a device in ABP rather than OTAA and it works.

What can I do to make this migration work, using only API?

Are you using an activateDeviceRequest for the fCnts, devAddr, and session keys, and an createDeviceKeysRequest / updateDeviceKeysRequest for the appKey?

Did you take heed of the note for LoRaWAN 1.0.x devices?

Here comes the NodeJS function I use to add the devices:

import grpc from "@grpc/grpc-js";

import { createRequire } from "module";

import fs from "fs";

const require = createRequire(import.meta.url);

const { TenantServiceClient } = require("@chirpstack/chirpstack-api/api/tenant_grpc_pb");

const { ListTenantsRequest } = require("@chirpstack/chirpstack-api/api/tenant_pb");

const { ApplicationServiceClient } = require("@chirpstack/chirpstack-api/api/application_grpc_pb");

const { ListApplicationsRequest } = require("@chirpstack/chirpstack-api/api/application_pb");

const { DeviceProfileServiceClient } = require("@chirpstack/chirpstack-api/api/device_profile_grpc_pb");

const { ListDeviceProfilesRequest } = require("@chirpstack/chirpstack-api/api/device_profile_pb");

const { DeviceServiceClient } = require("@chirpstack/chirpstack-api/api/device_grpc_pb");

const { CreateDeviceRequest, Device } = require("@chirpstack/chirpstack-api/api/device_pb");

const { CreateDeviceKeysRequest, DeviceKeys } = require("@chirpstack/chirpstack-api/api/device_pb");

const { ActivateDeviceRequest, DeviceActivation } = require("@chirpstack/chirpstack-api/api/device_pb");

async function addDevices(applicationId, deviceProfiles) {
  for (const device of devicesData.devices) {
    try {
      const modelName = device.name.split("_")[0];
      const brand = brandModelMap[modelName] || "Unknown";
      const newName = `${brand}_${device.name}`;
      const deviceProfileId = deviceProfiles[`${brand}_${modelName}`];

      if (!deviceProfileId) {
        console.warn(`⚠️ Skipping device ${newName}: Device profile not found for ${brand}_${modelName}`);
        continue;
      }

      console.log(`🟢 Adding device: ${newName}`);

      // Create device
      const createDeviceRequest = new CreateDeviceRequest();
      const newDevice = new Device();
      newDevice.setDevEui(device.devEUI);
      newDevice.setName(newName);
      newDevice.setApplicationId(applicationId);
      newDevice.setDeviceProfileId(deviceProfileId);
      createDeviceRequest.setDevice(newDevice);

      await new Promise((resolve, reject) => {
        deviceClient.create(createDeviceRequest, metadata, (err) => {
          if (err) return reject(new Error(`Failed to create device: ${err.message}`));
          console.log(`✅ Device ${newName} added`);
          resolve();
        });
      });

      // Add DeviceKeys
      const createDeviceKeysRequest = new CreateDeviceKeysRequest();
      const deviceKeys = new DeviceKeys();
      deviceKeys.setDevEui(device.devEUI);
      deviceKeys.setNwkKey(device.appKey);
      createDeviceKeysRequest.setDeviceKeys(deviceKeys);

      await new Promise((resolve, reject) => {
        deviceClient.createKeys(createDeviceKeysRequest, metadata, (err) => {
          if (err) return reject(new Error(`Failed to set device keys: ${err.message}`));
          console.log(`🔑 DeviceKeys set for ${newName}`);
          resolve();
        });
      });

      // Perform Device Activation
      const activateDeviceRequest = new ActivateDeviceRequest();
      const deviceActivation = new DeviceActivation();
      deviceActivation.setDevEui(device.devEUI);
      deviceActivation.setDevAddr(device.devAddr);
      deviceActivation.setAppSKey(device.appSKey);
      deviceActivation.setNwkSEncKey(device.nwkSKey);
      deviceActivation.setFCntUp(device.fCntUp);
      deviceActivation.setNFCntDown(device.fCntDown);
      activateDeviceRequest.setDeviceActivation(deviceActivation);

      await new Promise((resolve, reject) => {
        deviceClient.activate(activateDeviceRequest, metadata, (err) => {
          if (err) return reject(new Error(`Failed to activate device: ${err.message}`));
          console.log(`🚀 Device ${newName} activated successfully`);
          resolve();
        });
      });

    } catch (error) {
      console.error(`❌ Error processing device ${device.name}: ${error.message}`);
    }
  }
}

Did you take heed of the note for LoRaWAN 1.0.x devices?

Yes

The solution is there:

      const deviceActivation = new DeviceActivation();
      deviceActivation.setDevEui(device.devEUI);
      deviceActivation.setDevAddr(device.devAddr);
      deviceActivation.setAppSKey(device.appSKey);
      deviceActivation.setNwkSEncKey(device.nwkSKey);
      deviceActivation.setSNwkSIntKey(device.nwkSKey);
      deviceActivation.setFNwkSIntKey(device.nwkSKey);
      deviceActivation.setFCntUp(device.fCntUp);
      deviceActivation.setNFCntDown(device.fCntDown);
      activateDeviceRequest.setDeviceActivation(deviceActivation);