Procedure to allow existing devices to download release with new service

Briefly: I had to insert a “service install” record to allow a device to download a release with a new service. Is there a documented way (via OpenBalena API or other documented tools) to allow new services to download?

I recently created a release with balena deploy that includes the app’s 4 existing services (here, app1, app2, wifi-connect, tunnel) and a new one (browser) that was added to docker-compose.yml. While the release was successfully added to the cloud and ran correctly when preloaded, pinning any existing device (raspberrypi3 versions 2.47-2.98) to the new release caused that device’s state checks to OpenBalena to receive 500 responses, blocking them from downloading the new release (updates between all prior releases continued to work). The log from open-balena-api showed:

Error getting device state Could not find service install for device: 'undefined', image: '69', service: '{"__id":71}', service installs: '[{"service":[{"service_environment_variable":[],"service_label":[],"id":1,"service_name":"app1"}],"device_service_environment_variable":[],"id":689},{"service":[{"service_environment_variable":[],"service_label":[],"id":2,"service_name":"app2"}],"device_service_environment_variable":[],"id":690},{"service":[{"service_environment_variable":[],"service_label":[],"id":3,"service_name":"wifi-connect"}],"device_service_environment_variable":[],"id":691},{"service":[{"service_environment_variable":[],"service_label":[],"id":4,"service_name":"tunnel"}],"device_service_environment_variable":[],"id":692}]' Error: [same message again]
    at /usr/src/app/src/features/device-state/routes/state-get-v2.ts:117:10
    at Array.forEach (<anonymous>)
    at buildAppFromRelease (/usr/src/app/src/features/device-state/routes/state-get-v2.ts:111:43)
    at getUserAppForState (/usr/src/app/src/features/device-state/routes/state-get-v2.ts:404:5)
    at getStateV2 (/usr/src/app/src/features/device-state/routes/state-get-v2.ts:318:18)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async stateV2 (/usr/src/app/src/features/device-state/routes/state-get-v2.ts:346:12)
2023-03-24T00:45:23.948Z a/7623 GET /device/v2/7c32c01b255b25e28d84105032de2127/state 500 53.931ms -

The ‘undefined’ in place of the fleet uuid above appears misleading, as DB queries show that OpenBalena does know the uuid associated with those requests. Image 69 indeed corresponds to service 71, which is the first container based on the browser block. OpenBalena is aware that the device is pinned to a release that includes this service alongside the usual services 1-4. The logged service_installs variable shows a service install for each of those but not for the new service. Based on the date columns in the service install table, these records represent prior installations of services on the device, so it makes sense for there to be no record of a prior installation of a new service. I tried addressing the immediate error with:

INSERT INTO "service install" ("created at", "modified at", device, "installs-service") VALUES (NOW(), NOW(), 124, 71);

This appeared to work. The pinned device (124) shortly downloaded and launched a browser container. Is there a better way to allow all devices to download the new service? I could of course insert a new service install record for every device, but this does not appear to be the intended method. Available tutorials make it sound like just adding the service to docker-compose.yml is enough.

Update: not sure if a setting exists, but the bulk equivalent of the above query enabled a fleet-wide upgrade.

INSERT INTO "service install" (device, "installs-service", "created at", "modified at")
SELECT DISTINCT device, 71, NOW(), NOW()
FROM "service install"
WHERE device NOT IN (124)
GROUP BY device
ORDER BY device;