@fastybird/smart-panel-backend 1.1.0-alpha.2 → 1.1.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/dist/app.module.js +8 -0
  2. package/dist/app.module.js.map +1 -1
  3. package/dist/common/filters/conflict-exception.filter.d.ts +6 -0
  4. package/dist/common/filters/conflict-exception.filter.js +59 -0
  5. package/dist/common/filters/conflict-exception.filter.js.map +1 -0
  6. package/dist/common/filters/global-error.filter.js +1 -4
  7. package/dist/common/filters/global-error.filter.js.map +1 -1
  8. package/dist/common/filters/global-filters.d.ts +3 -0
  9. package/dist/common/filters/global-filters.js +22 -0
  10. package/dist/common/filters/global-filters.js.map +1 -0
  11. package/dist/common/filters/unprocessable-entity-exception.filter.d.ts +1 -0
  12. package/dist/common/filters/unprocessable-entity-exception.filter.js +15 -3
  13. package/dist/common/filters/unprocessable-entity-exception.filter.js.map +1 -1
  14. package/dist/common/utils/transform.utils.js +3 -0
  15. package/dist/common/utils/transform.utils.js.map +1 -1
  16. package/dist/dataSource.js +1 -0
  17. package/dist/dataSource.js.map +1 -1
  18. package/dist/main.js +2 -7
  19. package/dist/main.js.map +1 -1
  20. package/dist/migrations/1000000000026-AddHardwareInputCategories.d.ts +7 -0
  21. package/dist/migrations/1000000000026-AddHardwareInputCategories.js +364 -0
  22. package/dist/migrations/1000000000026-AddHardwareInputCategories.js.map +1 -0
  23. package/dist/modules/devices/controllers/channels.controller.d.ts +5 -1
  24. package/dist/modules/devices/controllers/channels.controller.js +34 -2
  25. package/dist/modules/devices/controllers/channels.controller.js.map +1 -1
  26. package/dist/modules/devices/controllers/channels.properties.controller.js +21 -4
  27. package/dist/modules/devices/controllers/channels.properties.controller.js.map +1 -1
  28. package/dist/modules/devices/controllers/devices.channels.controller.d.ts +5 -1
  29. package/dist/modules/devices/controllers/devices.channels.controller.js +38 -2
  30. package/dist/modules/devices/controllers/devices.channels.controller.js.map +1 -1
  31. package/dist/modules/devices/controllers/devices.channels.properties.controller.js +21 -4
  32. package/dist/modules/devices/controllers/devices.channels.properties.controller.js.map +1 -1
  33. package/dist/modules/devices/devices.constants.d.ts +6 -0
  34. package/dist/modules/devices/devices.constants.js +6 -0
  35. package/dist/modules/devices/devices.constants.js.map +1 -1
  36. package/dist/modules/devices/devices.module.js +18 -0
  37. package/dist/modules/devices/devices.module.js.map +1 -1
  38. package/dist/modules/devices/devices.openapi.d.ts +3 -1
  39. package/dist/modules/devices/devices.openapi.js +7 -0
  40. package/dist/modules/devices/devices.openapi.js.map +1 -1
  41. package/dist/modules/devices/dto/channel-input-occurrence.dto.d.ts +10 -0
  42. package/dist/modules/devices/dto/channel-input-occurrence.dto.js +99 -0
  43. package/dist/modules/devices/dto/channel-input-occurrence.dto.js.map +1 -0
  44. package/dist/modules/devices/models/channel-input-capabilities.model.d.ts +20 -0
  45. package/dist/modules/devices/models/channel-input-capabilities.model.js +111 -0
  46. package/dist/modules/devices/models/channel-input-capabilities.model.js.map +1 -0
  47. package/dist/modules/devices/models/channel-input-occurrence.model.d.ts +22 -0
  48. package/dist/modules/devices/models/channel-input-occurrence.model.js +154 -0
  49. package/dist/modules/devices/models/channel-input-occurrence.model.js.map +1 -0
  50. package/dist/modules/devices/services/channel-input-deduplication.service.d.ts +17 -0
  51. package/dist/modules/devices/services/channel-input-deduplication.service.js +88 -0
  52. package/dist/modules/devices/services/channel-input-deduplication.service.js.map +1 -0
  53. package/dist/modules/devices/services/channel-input-occurrences.service.d.ts +21 -0
  54. package/dist/modules/devices/services/channel-input-occurrences.service.js +163 -0
  55. package/dist/modules/devices/services/channel-input-occurrences.service.js.map +1 -0
  56. package/dist/modules/devices/services/channels.properties.service.d.ts +27 -3
  57. package/dist/modules/devices/services/channels.properties.service.js +304 -28
  58. package/dist/modules/devices/services/channels.properties.service.js.map +1 -1
  59. package/dist/modules/devices/services/channels.service.js +42 -38
  60. package/dist/modules/devices/services/channels.service.js.map +1 -1
  61. package/dist/modules/devices/services/command-latency-trace-collector.service.d.ts +101 -0
  62. package/dist/modules/devices/services/command-latency-trace-collector.service.js +451 -0
  63. package/dist/modules/devices/services/command-latency-trace-collector.service.js.map +1 -0
  64. package/dist/modules/devices/services/device-structure-lock.service.d.ts +10 -2
  65. package/dist/modules/devices/services/device-structure-lock.service.js +86 -7
  66. package/dist/modules/devices/services/device-structure-lock.service.js.map +1 -1
  67. package/dist/modules/devices/services/devices.service.js +37 -33
  68. package/dist/modules/devices/services/devices.service.js.map +1 -1
  69. package/dist/modules/devices/services/module-reset.service.d.ts +3 -1
  70. package/dist/modules/devices/services/module-reset.service.js +21 -16
  71. package/dist/modules/devices/services/module-reset.service.js.map +1 -1
  72. package/dist/modules/devices/services/property-command-dispatch.service.d.ts +45 -0
  73. package/dist/modules/devices/services/property-command-dispatch.service.js +227 -0
  74. package/dist/modules/devices/services/property-command-dispatch.service.js.map +1 -0
  75. package/dist/modules/devices/services/property-command-window.service.d.ts +71 -0
  76. package/dist/modules/devices/services/property-command-window.service.js +204 -0
  77. package/dist/modules/devices/services/property-command-window.service.js.map +1 -0
  78. package/dist/modules/devices/services/property-command.service.d.ts +20 -3
  79. package/dist/modules/devices/services/property-command.service.js +56 -10
  80. package/dist/modules/devices/services/property-command.service.js.map +1 -1
  81. package/dist/modules/devices/services/property-state-coordinator.service.d.ts +4 -0
  82. package/dist/modules/devices/services/property-state-coordinator.service.js +35 -0
  83. package/dist/modules/devices/services/property-state-coordinator.service.js.map +1 -0
  84. package/dist/modules/devices/services/property-value.service.d.ts +1 -0
  85. package/dist/modules/devices/services/property-value.service.js +4 -0
  86. package/dist/modules/devices/services/property-value.service.js.map +1 -1
  87. package/dist/modules/devices/utils/schema.utils.js +1 -1
  88. package/dist/modules/devices/utils/schema.utils.js.map +1 -1
  89. package/dist/modules/platform/services/platform.service.d.ts +13 -4
  90. package/dist/modules/platform/services/platform.service.js +57 -35
  91. package/dist/modules/platform/services/platform.service.js.map +1 -1
  92. package/dist/modules/remote-access/dto/update-config.dto.js +5 -0
  93. package/dist/modules/remote-access/dto/update-config.dto.js.map +1 -1
  94. package/dist/modules/remote-access/validators/is-remote-access-url.validator.js +5 -2
  95. package/dist/modules/remote-access/validators/is-remote-access-url.validator.js.map +1 -1
  96. package/dist/modules/system/scripts/update-worker.sh +668 -59
  97. package/dist/modules/system/services/privileged-worker.service.d.ts +10 -0
  98. package/dist/modules/system/services/privileged-worker.service.js +137 -11
  99. package/dist/modules/system/services/privileged-worker.service.js.map +1 -1
  100. package/dist/modules/system/services/update-executor.service.d.ts +20 -2
  101. package/dist/modules/system/services/update-executor.service.js +256 -25
  102. package/dist/modules/system/services/update-executor.service.js.map +1 -1
  103. package/dist/modules/websocket/gateway/websocket.gateway.d.ts +4 -1
  104. package/dist/modules/websocket/gateway/websocket.gateway.js +28 -5
  105. package/dist/modules/websocket/gateway/websocket.gateway.js.map +1 -1
  106. package/dist/modules/websocket/services/command-acknowledgement-trace.service.d.ts +143 -0
  107. package/dist/modules/websocket/services/command-acknowledgement-trace.service.js +523 -0
  108. package/dist/modules/websocket/services/command-acknowledgement-trace.service.js.map +1 -0
  109. package/dist/modules/websocket/websocket.module.js +9 -1
  110. package/dist/modules/websocket/websocket.module.js.map +1 -1
  111. package/dist/plugins/devices-home-assistant/devices-home-assistant.openapi.d.ts +2 -2
  112. package/dist/plugins/devices-home-assistant/devices-home-assistant.plugin.d.ts +6 -1
  113. package/dist/plugins/devices-home-assistant/devices-home-assistant.plugin.js +16 -1
  114. package/dist/plugins/devices-home-assistant/devices-home-assistant.plugin.js.map +1 -1
  115. package/dist/plugins/devices-home-assistant/dto/mapping-preview.dto.d.ts +1 -0
  116. package/dist/plugins/devices-home-assistant/dto/mapping-preview.dto.js +11 -0
  117. package/dist/plugins/devices-home-assistant/dto/mapping-preview.dto.js.map +1 -1
  118. package/dist/plugins/devices-home-assistant/mappers/button.entity.mapper.service.d.ts +19 -0
  119. package/dist/plugins/devices-home-assistant/mappers/button.entity.mapper.service.js +51 -0
  120. package/dist/plugins/devices-home-assistant/mappers/button.entity.mapper.service.js.map +1 -0
  121. package/dist/plugins/devices-home-assistant/mappings/definitions/entity-mappings.yaml +56 -7
  122. package/dist/plugins/devices-home-assistant/models/mapping-preview.model.d.ts +1 -0
  123. package/dist/plugins/devices-home-assistant/models/mapping-preview.model.js +11 -0
  124. package/dist/plugins/devices-home-assistant/models/mapping-preview.model.js.map +1 -1
  125. package/dist/plugins/devices-home-assistant/services/device-adoption.service.js +17 -0
  126. package/dist/plugins/devices-home-assistant/services/device-adoption.service.js.map +1 -1
  127. package/dist/plugins/devices-home-assistant/services/home-assistant-raw-event.service.d.ts +27 -0
  128. package/dist/plugins/devices-home-assistant/services/home-assistant-raw-event.service.js +271 -0
  129. package/dist/plugins/devices-home-assistant/services/home-assistant-raw-event.service.js.map +1 -0
  130. package/dist/plugins/devices-home-assistant/services/home-assistant.ws.service.d.ts +2 -2
  131. package/dist/plugins/devices-home-assistant/services/home-assistant.ws.service.js +33 -14
  132. package/dist/plugins/devices-home-assistant/services/home-assistant.ws.service.js.map +1 -1
  133. package/dist/plugins/devices-home-assistant/services/mapping-preview.service.js +24 -4
  134. package/dist/plugins/devices-home-assistant/services/mapping-preview.service.js.map +1 -1
  135. package/dist/plugins/devices-home-assistant/services/state-changed.event.service.d.ts +4 -1
  136. package/dist/plugins/devices-home-assistant/services/state-changed.event.service.js +84 -5
  137. package/dist/plugins/devices-home-assistant/services/state-changed.event.service.js.map +1 -1
  138. package/dist/plugins/devices-home-assistant/services/wizard.service.js +1 -0
  139. package/dist/plugins/devices-home-assistant/services/wizard.service.js.map +1 -1
  140. package/dist/plugins/devices-home-assistant/utils/ha-event.utils.d.ts +2 -0
  141. package/dist/plugins/devices-home-assistant/utils/ha-event.utils.js +50 -0
  142. package/dist/plugins/devices-home-assistant/utils/ha-event.utils.js.map +1 -0
  143. package/dist/plugins/devices-homekit/listeners/homekit-event.listener.d.ts +2 -0
  144. package/dist/plugins/devices-homekit/listeners/homekit-event.listener.js +28 -1
  145. package/dist/plugins/devices-homekit/listeners/homekit-event.listener.js.map +1 -1
  146. package/dist/plugins/devices-homekit/mappers/base.mapper.js +27 -4
  147. package/dist/plugins/devices-homekit/mappers/base.mapper.js.map +1 -1
  148. package/dist/plugins/devices-homekit/mappers/button.mapper.d.ts +15 -0
  149. package/dist/plugins/devices-homekit/mappers/button.mapper.js +143 -0
  150. package/dist/plugins/devices-homekit/mappers/button.mapper.js.map +1 -0
  151. package/dist/plugins/devices-homekit/mappers/homekit-mapper.interface.d.ts +16 -0
  152. package/dist/plugins/devices-homekit/mappers/thermostat-coordinator.d.ts +7 -0
  153. package/dist/plugins/devices-homekit/mappers/thermostat-coordinator.js +84 -73
  154. package/dist/plugins/devices-homekit/mappers/thermostat-coordinator.js.map +1 -1
  155. package/dist/plugins/devices-homekit/services/homekit-mapper-registry.service.d.ts +5 -1
  156. package/dist/plugins/devices-homekit/services/homekit-mapper-registry.service.js +42 -0
  157. package/dist/plugins/devices-homekit/services/homekit-mapper-registry.service.js.map +1 -1
  158. package/dist/plugins/devices-homey/mappings/definitions/channels.yaml +50 -0
  159. package/dist/plugins/devices-homey/mappings/definitions/devices.yaml +18 -0
  160. package/dist/plugins/devices-homey/mappings/definitions/properties.yaml +55 -0
  161. package/dist/plugins/devices-homey/mappings/device-support.js +10 -0
  162. package/dist/plugins/devices-homey/mappings/device-support.js.map +1 -1
  163. package/dist/plugins/devices-homey/mappings/mapping-loader.service.d.ts +1 -0
  164. package/dist/plugins/devices-homey/mappings/mapping-loader.service.js +66 -8
  165. package/dist/plugins/devices-homey/mappings/mapping-loader.service.js.map +1 -1
  166. package/dist/plugins/devices-homey/models/inventory.model.d.ts +2 -1
  167. package/dist/plugins/devices-homey/models/inventory.model.js +1 -0
  168. package/dist/plugins/devices-homey/models/inventory.model.js.map +1 -1
  169. package/dist/plugins/devices-homey/models/mapping-preview.model.d.ts +1 -0
  170. package/dist/plugins/devices-homey/models/mapping-preview.model.js +1 -0
  171. package/dist/plugins/devices-homey/models/mapping-preview.model.js.map +1 -1
  172. package/dist/plugins/devices-homey/services/homey-mapping-preview.service.js +7 -0
  173. package/dist/plugins/devices-homey/services/homey-mapping-preview.service.js.map +1 -1
  174. package/dist/plugins/devices-homey/services/homey-synchronizer.service.d.ts +3 -1
  175. package/dist/plugins/devices-homey/services/homey-synchronizer.service.js +37 -6
  176. package/dist/plugins/devices-homey/services/homey-synchronizer.service.js.map +1 -1
  177. package/dist/plugins/devices-reterminal/services/reterminal-button.service.d.ts +7 -3
  178. package/dist/plugins/devices-reterminal/services/reterminal-button.service.js +63 -17
  179. package/dist/plugins/devices-reterminal/services/reterminal-button.service.js.map +1 -1
  180. package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.d.ts +38 -3
  181. package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.js +840 -134
  182. package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.js.map +1 -1
  183. package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.d.ts +15 -1
  184. package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.js +117 -10
  185. package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.js.map +1 -1
  186. package/dist/plugins/devices-shelly-ng/devices-shelly-ng.constants.js +11 -13
  187. package/dist/plugins/devices-shelly-ng/devices-shelly-ng.constants.js.map +1 -1
  188. package/dist/plugins/devices-shelly-ng/devices-shelly-ng.plugin.js +2 -0
  189. package/dist/plugins/devices-shelly-ng/devices-shelly-ng.plugin.js.map +1 -1
  190. package/dist/plugins/devices-shelly-ng/mappings/definitions/inputs.yaml +80 -0
  191. package/dist/plugins/devices-shelly-ng/mappings/mapping-loader.service.js +11 -1
  192. package/dist/plugins/devices-shelly-ng/mappings/mapping-loader.service.js.map +1 -1
  193. package/dist/plugins/devices-shelly-ng/mappings/mapping.types.d.ts +6 -3
  194. package/dist/plugins/devices-shelly-ng/mappings/schema/mapping-schema.json +5 -0
  195. package/dist/plugins/devices-shelly-ng/services/device-manager.service.js +130 -7
  196. package/dist/plugins/devices-shelly-ng/services/device-manager.service.js.map +1 -1
  197. package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.d.ts +48 -0
  198. package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.js +457 -0
  199. package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.js.map +1 -0
  200. package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.d.ts +12 -2
  201. package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.js +86 -28
  202. package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.js.map +1 -1
  203. package/dist/plugins/devices-shelly-ng/services/shelly-rpc-client.service.js +3 -0
  204. package/dist/plugins/devices-shelly-ng/services/shelly-rpc-client.service.js.map +1 -1
  205. package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.d.ts +1 -1
  206. package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.js +18 -13
  207. package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.js.map +1 -1
  208. package/dist/plugins/devices-shelly-ng/utils/transform.utils.d.ts +2 -0
  209. package/dist/plugins/devices-shelly-ng/utils/transform.utils.js +14 -1
  210. package/dist/plugins/devices-shelly-ng/utils/transform.utils.js.map +1 -1
  211. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.d.ts +2 -0
  212. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.js +66 -4
  213. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.js.map +1 -1
  214. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.plugin.js +2 -0
  215. package/dist/plugins/devices-shelly-v1/devices-shelly-v1.plugin.js.map +1 -1
  216. package/dist/plugins/devices-shelly-v1/platforms/shelly-v1.device.platform.js +7 -0
  217. package/dist/plugins/devices-shelly-v1/platforms/shelly-v1.device.platform.js.map +1 -1
  218. package/dist/plugins/devices-shelly-v1/services/device-mapper.service.d.ts +3 -1
  219. package/dist/plugins/devices-shelly-v1/services/device-mapper.service.js +20 -2
  220. package/dist/plugins/devices-shelly-v1/services/device-mapper.service.js.map +1 -1
  221. package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.d.ts +16 -0
  222. package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.js +107 -0
  223. package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.js.map +1 -0
  224. package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.d.ts +7 -1
  225. package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.js +98 -3
  226. package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.js.map +1 -1
  227. package/dist/plugins/devices-third-party/controllers/third-party-inputs.controller.d.ts +17 -0
  228. package/dist/plugins/devices-third-party/controllers/third-party-inputs.controller.js +205 -0
  229. package/dist/plugins/devices-third-party/controllers/third-party-inputs.controller.js.map +1 -0
  230. package/dist/plugins/devices-third-party/devices-third-party.openapi.d.ts +1 -1
  231. package/dist/plugins/devices-third-party/devices-third-party.plugin.js +2 -1
  232. package/dist/plugins/devices-third-party/devices-third-party.plugin.js.map +1 -1
  233. package/dist/plugins/devices-third-party/dto/report-input-occurrence-response.dto.d.ts +5 -0
  234. package/dist/plugins/devices-third-party/dto/report-input-occurrence-response.dto.js +50 -0
  235. package/dist/plugins/devices-third-party/dto/report-input-occurrence-response.dto.js.map +1 -0
  236. package/dist/plugins/devices-third-party/dto/report-input-occurrence.dto.d.ts +8 -0
  237. package/dist/plugins/devices-third-party/dto/report-input-occurrence.dto.js +81 -0
  238. package/dist/plugins/devices-third-party/dto/report-input-occurrence.dto.js.map +1 -0
  239. package/dist/plugins/devices-third-party/platforms/third-party-device.platform.js +19 -4
  240. package/dist/plugins/devices-third-party/platforms/third-party-device.platform.js.map +1 -1
  241. package/dist/plugins/devices-virtual/listeners/virtual-projection.listener.d.ts +2 -0
  242. package/dist/plugins/devices-virtual/listeners/virtual-projection.listener.js +57 -0
  243. package/dist/plugins/devices-virtual/listeners/virtual-projection.listener.js.map +1 -1
  244. package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.d.ts +3 -0
  245. package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.js +41 -5
  246. package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.js.map +1 -1
  247. package/dist/plugins/devices-zigbee2mqtt/interfaces/zigbee2mqtt.interface.d.ts +7 -1
  248. package/dist/plugins/devices-zigbee2mqtt/mappings/config-driven.converter.js +16 -15
  249. package/dist/plugins/devices-zigbee2mqtt/mappings/config-driven.converter.js.map +1 -1
  250. package/dist/plugins/devices-zigbee2mqtt/mappings/definitions/remotes.yaml +505 -74
  251. package/dist/plugins/devices-zigbee2mqtt/mappings/schema/mapping-schema.json +3 -3
  252. package/dist/plugins/devices-zigbee2mqtt/mappings/transformers/transformer.types.d.ts +1 -1
  253. package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.d.ts +8 -2
  254. package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.js +6 -5
  255. package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.js.map +1 -1
  256. package/dist/plugins/devices-zigbee2mqtt/services/device-adoption.service.js +18 -11
  257. package/dist/plugins/devices-zigbee2mqtt/services/device-adoption.service.js.map +1 -1
  258. package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.d.ts +9 -3
  259. package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.js +166 -3
  260. package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.js.map +1 -1
  261. package/dist/plugins/devices-zigbee2mqtt/services/mqtt-client-adapter.service.js +5 -2
  262. package/dist/plugins/devices-zigbee2mqtt/services/mqtt-client-adapter.service.js.map +1 -1
  263. package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.d.ts +1 -0
  264. package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.js +44 -4
  265. package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.js.map +1 -1
  266. package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.d.ts +17 -0
  267. package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.js +147 -0
  268. package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.js.map +1 -0
  269. package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.d.ts +15 -0
  270. package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.js +98 -0
  271. package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.js.map +1 -0
  272. package/dist/plugins/remote-access-cloudflare-tunnel/dto/update-config.dto.d.ts +9 -0
  273. package/dist/plugins/remote-access-cloudflare-tunnel/dto/update-config.dto.js +94 -0
  274. package/dist/plugins/remote-access-cloudflare-tunnel/dto/update-config.dto.js.map +1 -0
  275. package/dist/plugins/remote-access-cloudflare-tunnel/models/config.model.d.ts +9 -0
  276. package/dist/plugins/remote-access-cloudflare-tunnel/models/config.model.js +92 -0
  277. package/dist/plugins/remote-access-cloudflare-tunnel/models/config.model.js.map +1 -0
  278. package/dist/plugins/remote-access-cloudflare-tunnel/models/install.model.d.ts +7 -0
  279. package/dist/plugins/remote-access-cloudflare-tunnel/models/install.model.js +48 -0
  280. package/dist/plugins/remote-access-cloudflare-tunnel/models/install.model.js.map +1 -0
  281. package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.d.ts +42 -0
  282. package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.js +352 -0
  283. package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.js.map +1 -0
  284. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.constants.d.ts +24 -0
  285. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.constants.js +27 -0
  286. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.constants.js.map +1 -0
  287. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.exceptions.d.ts +8 -0
  288. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.exceptions.js +19 -0
  289. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.exceptions.js.map +1 -0
  290. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.openapi.d.ts +4 -0
  291. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.openapi.js +20 -0
  292. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.openapi.js.map +1 -0
  293. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.plugin.d.ts +21 -0
  294. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.plugin.js +135 -0
  295. package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.plugin.js.map +1 -0
  296. package/dist/plugins/remote-access-cloudflare-tunnel/scripts/cloudflared-setup.sh +185 -0
  297. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.d.ts +82 -0
  298. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.js +507 -0
  299. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.js.map +1 -0
  300. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.d.ts +10 -0
  301. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.js +37 -0
  302. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.js.map +1 -0
  303. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.d.ts +38 -0
  304. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.js +147 -0
  305. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.js.map +1 -0
  306. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.d.ts +15 -0
  307. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.js +67 -0
  308. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.js.map +1 -0
  309. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.d.ts +8 -0
  310. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.js +46 -0
  311. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.js.map +1 -0
  312. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-process.service.d.ts +27 -0
  313. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-process.service.js +139 -0
  314. package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-process.service.js.map +1 -0
  315. package/dist/plugins/remote-access-tailscale/controllers/setup.controller.d.ts +1 -0
  316. package/dist/plugins/remote-access-tailscale/controllers/setup.controller.js +26 -10
  317. package/dist/plugins/remote-access-tailscale/controllers/setup.controller.js.map +1 -1
  318. package/dist/plugins/remote-access-tailscale/controllers/status.controller.d.ts +6 -1
  319. package/dist/plugins/remote-access-tailscale/controllers/status.controller.js +28 -3
  320. package/dist/plugins/remote-access-tailscale/controllers/status.controller.js.map +1 -1
  321. package/dist/plugins/remote-access-tailscale/models/status.model.d.ts +19 -0
  322. package/dist/plugins/remote-access-tailscale/models/status.model.js +158 -1
  323. package/dist/plugins/remote-access-tailscale/models/status.model.js.map +1 -1
  324. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.d.ts +3 -0
  325. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.js +4 -1
  326. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.js.map +1 -1
  327. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.d.ts +13 -0
  328. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.js +27 -0
  329. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.js.map +1 -0
  330. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.d.ts +2 -2
  331. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.js +2 -0
  332. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.js.map +1 -1
  333. package/dist/plugins/remote-access-tailscale/remote-access-tailscale.plugin.js +1 -1
  334. package/dist/plugins/remote-access-tailscale/scripts/tailscale-setup.sh +61 -0
  335. package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.d.ts +9 -0
  336. package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.js +12 -0
  337. package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.js.map +1 -1
  338. package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.d.ts +1 -0
  339. package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js +27 -2
  340. package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js.map +1 -1
  341. package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.d.ts +31 -1
  342. package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.js +316 -30
  343. package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.js.map +1 -1
  344. package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.d.ts +8 -3
  345. package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.js +39 -12
  346. package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.js.map +1 -1
  347. package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.d.ts +14 -5
  348. package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js +51 -16
  349. package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js.map +1 -1
  350. package/dist/plugins/scenes-local/platforms/local-scene.platform.d.ts +3 -1
  351. package/dist/plugins/scenes-local/platforms/local-scene.platform.js +12 -7
  352. package/dist/plugins/scenes-local/platforms/local-scene.platform.js.map +1 -1
  353. package/dist/plugins/simulator/controllers/simulator.controller.d.ts +8 -2
  354. package/dist/plugins/simulator/controllers/simulator.controller.js +102 -2
  355. package/dist/plugins/simulator/controllers/simulator.controller.js.map +1 -1
  356. package/dist/plugins/simulator/dto/simulate-occurrence.dto.d.ts +12 -0
  357. package/dist/plugins/simulator/dto/simulate-occurrence.dto.js +122 -0
  358. package/dist/plugins/simulator/dto/simulate-occurrence.dto.js.map +1 -0
  359. package/dist/plugins/simulator/models/simulator-response.model.d.ts +12 -0
  360. package/dist/plugins/simulator/models/simulator-response.model.js +84 -1
  361. package/dist/plugins/simulator/models/simulator-response.model.js.map +1 -1
  362. package/dist/plugins/simulator/scenarios/definitions/small-apartment.yaml +78 -0
  363. package/dist/plugins/simulator/services/scenario-executor.service.js +3 -1
  364. package/dist/plugins/simulator/services/scenario-executor.service.js.map +1 -1
  365. package/dist/plugins/simulator/services/simulation.service.js +10 -1
  366. package/dist/plugins/simulator/services/simulation.service.js.map +1 -1
  367. package/dist/plugins/simulator/simulator.openapi.d.ts +3 -2
  368. package/dist/plugins/simulator/simulator.openapi.js +5 -0
  369. package/dist/plugins/simulator/simulator.openapi.js.map +1 -1
  370. package/dist/plugins/simulator/simulators/device-simulator.interface.d.ts +2 -0
  371. package/dist/plugins/simulator/simulators/device-simulator.interface.js +83 -0
  372. package/dist/plugins/simulator/simulators/device-simulator.interface.js.map +1 -1
  373. package/dist/plugins/simulator/simulators/index.d.ts +1 -0
  374. package/dist/plugins/simulator/simulators/index.js +1 -0
  375. package/dist/plugins/simulator/simulators/index.js.map +1 -1
  376. package/dist/plugins/simulator/simulators/input-controller.simulator.d.ts +10 -0
  377. package/dist/plugins/simulator/simulators/input-controller.simulator.js +57 -0
  378. package/dist/plugins/simulator/simulators/input-controller.simulator.js.map +1 -0
  379. package/dist/plugins/simulator/simulators/lighting.simulator.js +1 -0
  380. package/dist/plugins/simulator/simulators/lighting.simulator.js.map +1 -1
  381. package/dist/plugins/spaces-home-control/services/climate-intent.service.d.ts +3 -1
  382. package/dist/plugins/spaces-home-control/services/climate-intent.service.js +6 -3
  383. package/dist/plugins/spaces-home-control/services/climate-intent.service.js.map +1 -1
  384. package/dist/plugins/spaces-home-control/services/covers-intent.service.d.ts +3 -1
  385. package/dist/plugins/spaces-home-control/services/covers-intent.service.js +6 -3
  386. package/dist/plugins/spaces-home-control/services/covers-intent.service.js.map +1 -1
  387. package/dist/plugins/spaces-home-control/services/lighting-intent.service.d.ts +3 -1
  388. package/dist/plugins/spaces-home-control/services/lighting-intent.service.js +7 -4
  389. package/dist/plugins/spaces-home-control/services/lighting-intent.service.js.map +1 -1
  390. package/dist/plugins/spaces-home-control/services/space-media-activity.service.d.ts +3 -1
  391. package/dist/plugins/spaces-home-control/services/space-media-activity.service.js +11 -5
  392. package/dist/plugins/spaces-home-control/services/space-media-activity.service.js.map +1 -1
  393. package/dist/plugins/spaces-home-control/services/space-undo-history.service.d.ts +3 -1
  394. package/dist/plugins/spaces-home-control/services/space-undo-history.service.js +8 -5
  395. package/dist/plugins/spaces-home-control/services/space-undo-history.service.js.map +1 -1
  396. package/dist/spec/channels.d.ts +51 -1
  397. package/dist/spec/channels.js +64 -1
  398. package/dist/spec/channels.js.map +1 -1
  399. package/dist/spec/devices.d.ts +155 -2
  400. package/dist/spec/devices.js +161 -2
  401. package/dist/spec/devices.js.map +1 -1
  402. package/dist/tsconfig.build.tsbuildinfo +1 -1
  403. package/package.json +2 -2
@@ -6,6 +6,524 @@ STATUS_FILE="${STATUS_FILE:-/var/lib/smart-panel/update-status.json}"
6
6
  INSTALL_TYPE="${INSTALL_TYPE:-npm}"
7
7
  IMAGE_BASE_DIR="${IMAGE_BASE_DIR:-/opt/smart-panel}"
8
8
  DOWNLOAD_URL="${DOWNLOAD_URL:-}"
9
+ HEALTH_URL="${HEALTH_URL:-}"
10
+ HEALTH_EXPECTED_VERSION="${HEALTH_EXPECTED_VERSION:-$VERSION}"
11
+
12
+ # The attempt record and lock live beside the public status file, never inside a versioned image
13
+ # directory. This lets the record survive a backend restart, target-directory cleanup or a
14
+ # failed migration, while keeping it outside the release tarball and private to the service user.
15
+ ATTEMPT_DIR="${ATTEMPT_DIR:-$(dirname "$STATUS_FILE")/update-attempt}"
16
+ ATTEMPT_FILE="${ATTEMPT_DIR}/attempt.json"
17
+ ATTEMPT_LOCK="${ATTEMPT_DIR}/lock"
18
+ ATTEMPT_ID=""
19
+ MIGRATION_ENTERED="false"
20
+ FINALIZED="false"
21
+ MIGRATION_LOG=""
22
+ CURRENT_PHASE="preparing"
23
+ SERVICE_NAME="smart-panel"
24
+ QUIESCENCE_TIMEOUT_SECONDS="${QUIESCENCE_TIMEOUT_SECONDS:-30}"
25
+ START_TIMEOUT_SECONDS="${START_TIMEOUT_SECONDS:-60}"
26
+ CAPTURED_SERVICE_CGROUP_PROCS=""
27
+ CAPTURED_SERVICE_CGROUP_DIR=""
28
+ CAPTURED_SERVICE_CGROUP_EVENTS=""
29
+ CAPTURED_SERVICE_MEMBER_IDENTITIES=""
30
+ CGROUP_MEMBERS=""
31
+ CGROUP_POPULATED=""
32
+
33
+ json_escape() {
34
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/[[:cntrl:]]//g'
35
+ }
36
+
37
+ write_attempt() {
38
+ local state="$1"
39
+ local phase="$2"
40
+ local error="${3:-}"
41
+ local recovery_required="${4:-false}"
42
+ local migration_entered="${5:-$MIGRATION_ENTERED}"
43
+ local tmp_file="${ATTEMPT_FILE}.tmp.$$"
44
+ CURRENT_PHASE="$phase"
45
+
46
+ mkdir -p "$ATTEMPT_DIR"
47
+ chmod 700 "$ATTEMPT_DIR"
48
+
49
+ {
50
+ printf '{\n'
51
+ printf '\t"attemptId": "%s",\n' "$(json_escape "$ATTEMPT_ID")"
52
+ printf '\t"ownerPid": %s,\n' "$$"
53
+ printf '\t"targetVersion": "%s",\n' "$(json_escape "$VERSION")"
54
+ printf '\t"state": "%s",\n' "$(json_escape "$state")"
55
+ printf '\t"phase": "%s",\n' "$(json_escape "$phase")"
56
+ printf '\t"migrationEntered": %s,\n' "$migration_entered"
57
+ printf '\t"recoveryRequired": %s,\n' "$recovery_required"
58
+ printf '\t"migrationLog": "%s",\n' "$(json_escape "$MIGRATION_LOG")"
59
+ printf '\t"updatedAt": "%s",\n' "$(date -Iseconds)"
60
+ printf '\t"error": "%s"\n' "$(json_escape "$error")"
61
+ printf '}\n'
62
+ } > "$tmp_file"
63
+
64
+ chmod 600 "$tmp_file"
65
+ mv -f "$tmp_file" "$ATTEMPT_FILE"
66
+ }
67
+
68
+ attempt_is_unresolved() {
69
+ if [ -f "$ATTEMPT_FILE" ] && grep -q '"state": "complete"' "$ATTEMPT_FILE"; then
70
+ # A crash after the durable completion record but before rmdir must not strand a clean
71
+ # attempt forever; completion is the evidence that makes this lock removable.
72
+ rmdir "$ATTEMPT_LOCK" 2>/dev/null || true
73
+
74
+ return 1
75
+ fi
76
+
77
+ if [ -d "$ATTEMPT_LOCK" ]; then
78
+ return 0
79
+ fi
80
+
81
+ if [ ! -f "$ATTEMPT_FILE" ]; then
82
+ return 1
83
+ fi
84
+
85
+ grep -q '"state": "active"' "$ATTEMPT_FILE" || grep -q '"recoveryRequired": true' "$ATTEMPT_FILE"
86
+ }
87
+
88
+ acquire_attempt() {
89
+ mkdir -p "$ATTEMPT_DIR"
90
+ chmod 700 "$ATTEMPT_DIR"
91
+
92
+ if attempt_is_unresolved; then
93
+ return 1
94
+ fi
95
+
96
+ # mkdir is the portable atomic OS-level lock used by the legacy launcher. An orphaned lock is
97
+ # deliberately retained; an expired timestamp cannot prove that a migration did not commit.
98
+ if ! mkdir "$ATTEMPT_LOCK" 2>/dev/null; then
99
+ return 1
100
+ fi
101
+ chmod 700 "$ATTEMPT_LOCK"
102
+
103
+ ATTEMPT_ID="$(date +%s)-$$"
104
+ write_attempt "active" "preparing" "" "false" "false"
105
+ }
106
+
107
+ release_attempt() {
108
+ if [ -n "$ATTEMPT_ID" ]; then
109
+ write_attempt "complete" "complete" "" "false" "$MIGRATION_ENTERED"
110
+ fi
111
+
112
+ rmdir "$ATTEMPT_LOCK" 2>/dev/null || true
113
+ }
114
+
115
+ hold_attempt() {
116
+ local phase="$1"
117
+ local error="$2"
118
+
119
+ write_attempt "recovery_required" "$phase" "$error" "true" "$MIGRATION_ENTERED"
120
+ }
121
+
122
+ finish_before_migration() {
123
+ local phase="$1"
124
+ local error="$2"
125
+
126
+ if [ -n "${TMP_TARBALL:-}" ]; then
127
+ rm -f "$TMP_TARBALL" 2>/dev/null || true
128
+ fi
129
+
130
+ write_attempt "failed_preflight" "$phase" "$error" "false" "false"
131
+ rmdir "$ATTEMPT_LOCK" 2>/dev/null || true
132
+ }
133
+
134
+ phase_requires_hold() {
135
+ case "$CURRENT_PHASE" in
136
+ stopping|quiesced|switching) return 0 ;;
137
+ *) return 1 ;;
138
+ esac
139
+ }
140
+
141
+ fail_before_migration() {
142
+ local phase="$1"
143
+ local error="$2"
144
+
145
+ if [ -n "$ATTEMPT_ID" ]; then
146
+ finish_before_migration "$phase" "$error"
147
+ fi
148
+
149
+ update_status "failed" "failed" "$error"
150
+ FINALIZED="true"
151
+ exit 1
152
+ }
153
+
154
+ restore_before_migration() {
155
+ local previous_target="$1"
156
+ local current_link="$2"
157
+ local new_version_dir="$3"
158
+ local resolved_previous resolved_current
159
+
160
+ [ -n "$previous_target" ] || return 1
161
+ sudo -n ln -sfn "$previous_target" "$current_link" || return 1
162
+
163
+ resolved_previous="$(realpath "$previous_target" 2>/dev/null || true)"
164
+ resolved_current="$(realpath "$current_link" 2>/dev/null || true)"
165
+ [ -n "$resolved_previous" ] && [ "$resolved_previous" = "$resolved_current" ] || return 1
166
+
167
+ rm -rf "$new_version_dir" || return 1
168
+ sudo -n systemctl start "$SERVICE_NAME" 2>/dev/null || return 1
169
+ wait_for_started_service
170
+ }
171
+
172
+ service_property() {
173
+ local property="$1"
174
+
175
+ systemctl show "$SERVICE_NAME" --property="$property" --value
176
+ }
177
+
178
+ target_health_is_valid() {
179
+ local payload
180
+
181
+ [ -n "$HEALTH_URL" ] || return 1
182
+ payload="$(curl -fsS --max-time 5 "$HEALTH_URL" 2>/dev/null)" || return 1
183
+
184
+ printf '%s' "$payload" | HEALTH_EXPECTED_VERSION="$HEALTH_EXPECTED_VERSION" node -e '
185
+ let input = "";
186
+ process.stdin.setEncoding("utf8");
187
+ process.stdin.on("data", (chunk) => { input += chunk; });
188
+ process.stdin.on("end", () => {
189
+ try {
190
+ const body = JSON.parse(input);
191
+ const data = body && body.data;
192
+ process.exit(data?.status === "ok" && data.version === process.env.HEALTH_EXPECTED_VERSION ? 0 : 1);
193
+ } catch {
194
+ process.exit(1);
195
+ }
196
+ });
197
+ '
198
+ }
199
+
200
+ target_process_is_stable() {
201
+ local state main_pid start_identity resolved_target expected_target
202
+
203
+ state="$(service_property ActiveState 2>/dev/null || true)"
204
+ main_pid="$(service_property MainPID 2>/dev/null || true)"
205
+ start_identity="$(service_property ExecMainStartTimestampMonotonic 2>/dev/null || true)"
206
+ resolved_target="$(readlink -f "$CURRENT_LINK" 2>/dev/null || true)"
207
+ expected_target="$(realpath "$NEW_VERSION_DIR" 2>/dev/null || true)"
208
+
209
+ [ "$state" = "active" ] || return 1
210
+ [ "$main_pid" != "0" ] && [ -n "$main_pid" ] || return 1
211
+ [ -n "$start_identity" ] || return 1
212
+ [ -n "$CURRENT_LINK" ] && [ -n "$NEW_VERSION_DIR" ] || return 1
213
+ [ -n "$expected_target" ] && [ "$resolved_target" = "$expected_target" ] || return 1
214
+ }
215
+
216
+ process_identity() {
217
+ local pid="$1"
218
+ local proc_root="${SERVICE_PROCESS_ROOT:-/proc}"
219
+ local proc_dir="${proc_root}/${pid}"
220
+ local stat_line start_time executable command
221
+
222
+ [ "$pid" != "0" ] && [ -n "$pid" ] || return 1
223
+
224
+ # SERVICE_PROCESS_ROOT is a private fixture hook; the production default is /proc.
225
+ if [ -r "${proc_dir}/identity" ]; then
226
+ printf '%s|%s\n' "$pid" "$(cat "${proc_dir}/identity")"
227
+ return 0
228
+ fi
229
+
230
+ if [ -r "${proc_dir}/stat" ]; then
231
+ stat_line="$(cat "${proc_dir}/stat")" || return 2
232
+ start_time="$(printf '%s\n' "$stat_line" | awk '{ sub(/^.*\\) /, ""); print $20 }')" || return 2
233
+ [ -n "$start_time" ] || return 2
234
+ executable="$(readlink "${proc_dir}/exe" 2>/dev/null)" || return 2
235
+ [ -n "$executable" ] || return 2
236
+ printf '%s|%s|%s\n' "$pid" "$start_time" "$executable"
237
+ return 0
238
+ fi
239
+
240
+ if ! kill -0 "$pid" 2>/dev/null; then
241
+ return 1
242
+ fi
243
+
244
+ command="$(ps -p "$pid" -o command= 2>/dev/null)" || return 2
245
+ [ -n "$command" ] || return 2
246
+ printf '%s|%s\n' "$pid" "$command"
247
+ }
248
+
249
+ read_cgroup_members() {
250
+ local cgroup_procs="$1"
251
+ local contents members
252
+
253
+ CGROUP_MEMBERS=""
254
+ if ! contents="$(cat "$cgroup_procs" 2>/dev/null)"; then
255
+ return 2
256
+ fi
257
+
258
+ if ! members="$(printf '%s\n' "$contents" | awk '
259
+ length($0) > 0 {
260
+ if ($0 !~ /^[0-9]+$/) {
261
+ exit 2
262
+ }
263
+ print
264
+ }
265
+ ')"; then
266
+ return 2
267
+ fi
268
+
269
+ CGROUP_MEMBERS="$members"
270
+ }
271
+
272
+ read_cgroup_population() {
273
+ local cgroup_events="$1"
274
+ local contents populated
275
+
276
+ CGROUP_POPULATED=""
277
+ if ! contents="$(cat "$cgroup_events" 2>/dev/null)"; then
278
+ return 2
279
+ fi
280
+
281
+ if ! populated="$(printf '%s\n' "$contents" | awk '
282
+ $1 == "populated" {
283
+ count++
284
+ if (NF != 2 || $2 !~ /^[01]$/) {
285
+ invalid=1
286
+ } else {
287
+ value=$2
288
+ }
289
+ }
290
+ END {
291
+ if (count != 1 || invalid) {
292
+ exit 2
293
+ }
294
+ print value
295
+ }
296
+ ')"; then
297
+ return 2
298
+ fi
299
+
300
+ CGROUP_POPULATED="$populated"
301
+ }
302
+
303
+ probe_cgroup_directory() {
304
+ local cgroup_dir="$1"
305
+ local gnu_stat bsd_stat
306
+
307
+ if gnu_stat="$(LC_ALL=C stat -c '%F' "$cgroup_dir" 2>&1)"; then
308
+ case "$gnu_stat" in
309
+ directory) return 0 ;;
310
+ *) return 2 ;;
311
+ esac
312
+ fi
313
+
314
+ if bsd_stat="$(LC_ALL=C stat -f '%HT' "$cgroup_dir" 2>&1)"; then
315
+ case "$bsd_stat" in
316
+ Directory|directory) return 0 ;;
317
+ *) return 2 ;;
318
+ esac
319
+ fi
320
+
321
+ case "$gnu_stat\n$bsd_stat" in
322
+ *"No such file or directory"*|*"no such file or directory"*) return 1 ;;
323
+ *) return 2 ;;
324
+ esac
325
+ }
326
+
327
+ capture_service_identity() {
328
+ local active_state main_pid control_pid control_group cgroup_procs cgroup_dir cgroup_events members pid identity member_status
329
+
330
+ active_state="$(service_property ActiveState 2>/dev/null)" || return 2
331
+ main_pid="$(service_property MainPID 2>/dev/null)" || return 2
332
+ control_pid="$(service_property ControlPID 2>/dev/null)" || return 2
333
+ control_group="$(service_property ControlGroup 2>/dev/null)" || return 2
334
+
335
+ case "$active_state" in
336
+ active|activating) ;;
337
+ *) return 1 ;;
338
+ esac
339
+
340
+ [ "$main_pid" -gt 0 ] 2>/dev/null || return 1
341
+ [ "$control_pid" = "0" ] || [ "$control_pid" -gt 0 ] 2>/dev/null || return 2
342
+ [ -n "$control_group" ] || return 2
343
+
344
+ cgroup_procs="${SERVICE_CGROUP_PROCS_FILE:-/sys/fs/cgroup${control_group}/cgroup.procs}"
345
+ cgroup_dir="${SERVICE_CGROUP_DIR:-${cgroup_procs%/cgroup.procs}}"
346
+ cgroup_events="${SERVICE_CGROUP_EVENTS_FILE:-${cgroup_dir}/cgroup.events}"
347
+ if probe_cgroup_directory "$cgroup_dir"; then :; else return 2; fi
348
+ [ -r "$cgroup_procs" ] || return 2
349
+ [ -r "$cgroup_events" ] || return 2
350
+ if read_cgroup_members "$cgroup_procs"; then
351
+ member_status=0
352
+ else
353
+ member_status=$?
354
+ fi
355
+ [ "$member_status" -eq 0 ] || return "$member_status"
356
+ members="$CGROUP_MEMBERS"
357
+ [ -n "$members" ] || return 1
358
+
359
+ CAPTURED_SERVICE_MEMBER_IDENTITIES=""
360
+ while IFS= read -r pid; do
361
+ [ -n "$pid" ] || continue
362
+ identity="$(process_identity "$pid")" || return 2
363
+ CAPTURED_SERVICE_MEMBER_IDENTITIES="${CAPTURED_SERVICE_MEMBER_IDENTITIES}${identity}\n"
364
+ done <<EOF
365
+ $members
366
+ EOF
367
+
368
+ printf '%s\n' "$members" | grep -qx "$main_pid" || return 1
369
+ CAPTURED_SERVICE_CGROUP_PROCS="$cgroup_procs"
370
+ CAPTURED_SERVICE_CGROUP_DIR="$cgroup_dir"
371
+ CAPTURED_SERVICE_CGROUP_EVENTS="$cgroup_events"
372
+ }
373
+
374
+ service_quiesced() {
375
+ local active_state main_pid control_pid cgroup_dir_status member_status population_status probe_status pid captured_identity current_identity identity_status
376
+
377
+ [ -n "$CAPTURED_SERVICE_CGROUP_PROCS" ] || return 2
378
+ active_state="$(service_property ActiveState 2>/dev/null)" || return 2
379
+ main_pid="$(service_property MainPID 2>/dev/null)" || return 2
380
+ control_pid="$(service_property ControlPID 2>/dev/null)" || return 2
381
+
382
+ case "$active_state" in
383
+ inactive|failed) ;;
384
+ *) return 1 ;;
385
+ esac
386
+
387
+ [ "$main_pid" = "0" ] || return 1
388
+ [ "$control_pid" = "0" ] || return 1
389
+
390
+ # Check every member captured before stop independently. This catches KillMode=process
391
+ # survivors even when systemd has already removed the original cgroup directory.
392
+ while IFS= read -r captured_identity; do
393
+ [ -n "$captured_identity" ] || continue
394
+ pid="${captured_identity%%|*}"
395
+ current_identity=""
396
+ if current_identity="$(process_identity "$pid")"; then
397
+ identity_status=0
398
+ else
399
+ identity_status=$?
400
+ fi
401
+ case "$identity_status" in
402
+ 0) [ "$current_identity" != "$captured_identity" ] || return 1 ;;
403
+ 1) ;;
404
+ *) return 2 ;;
405
+ esac
406
+ done <<EOF
407
+ $(printf '%b' "$CAPTURED_SERVICE_MEMBER_IDENTITIES")
408
+ EOF
409
+
410
+ # The original cgroup may be empty or removed after the service reaches inactive/failed with
411
+ # zero MainPID/ControlPID. A missing membership file while its directory remains is an
412
+ # unavailable probe, not proof that the subtree is empty.
413
+ if probe_cgroup_directory "$CAPTURED_SERVICE_CGROUP_DIR"; then
414
+ if read_cgroup_members "$CAPTURED_SERVICE_CGROUP_PROCS"; then
415
+ member_status=0
416
+ else
417
+ member_status=$?
418
+ fi
419
+ if [ "$member_status" -eq 2 ]; then
420
+ if probe_cgroup_directory "$CAPTURED_SERVICE_CGROUP_DIR"; then
421
+ return 2
422
+ else
423
+ probe_status=$?
424
+ fi
425
+ [ "$probe_status" -eq 1 ] || return 2
426
+ return 0
427
+ fi
428
+ [ "$member_status" -eq 0 ] || return "$member_status"
429
+ [ -z "$CGROUP_MEMBERS" ] || return 1
430
+
431
+ if read_cgroup_population "$CAPTURED_SERVICE_CGROUP_EVENTS"; then
432
+ population_status=0
433
+ else
434
+ population_status=$?
435
+ fi
436
+ if [ "$population_status" -eq 2 ]; then
437
+ if probe_cgroup_directory "$CAPTURED_SERVICE_CGROUP_DIR"; then
438
+ return 2
439
+ else
440
+ probe_status=$?
441
+ fi
442
+ [ "$probe_status" -eq 1 ] || return 2
443
+ return 0
444
+ fi
445
+ [ "$population_status" -eq 0 ] || return "$population_status"
446
+ [ "$CGROUP_POPULATED" = "0" ] || return 1
447
+ else
448
+ cgroup_dir_status=$?
449
+ case "$cgroup_dir_status" in
450
+ 1) ;;
451
+ *) return 2 ;;
452
+ esac
453
+ fi
454
+
455
+ return 0
456
+ }
457
+
458
+ wait_for_quiescence() {
459
+ local deadline=$((SECONDS + QUIESCENCE_TIMEOUT_SECONDS))
460
+ local result
461
+
462
+ while [ "$SECONDS" -le "$deadline" ]; do
463
+ if service_quiesced; then
464
+ return 0
465
+ else
466
+ result=$?
467
+ fi
468
+ if [ "$result" -eq 2 ]; then
469
+ return 2
470
+ fi
471
+
472
+ sleep 1
473
+ done
474
+
475
+ return 1
476
+ }
477
+
478
+ wait_for_started_service() {
479
+ local deadline=$((SECONDS + START_TIMEOUT_SECONDS))
480
+ local previous_main_pid=""
481
+ local previous_start_identity=""
482
+ local before_main_pid before_start_identity main_pid start_identity
483
+
484
+ while [ "$SECONDS" -le "$deadline" ]; do
485
+ if target_process_is_stable; then
486
+ before_main_pid="$(service_property MainPID 2>/dev/null || true)"
487
+ before_start_identity="$(service_property ExecMainStartTimestampMonotonic 2>/dev/null || true)"
488
+
489
+ if [ "$before_main_pid" = "0" ] || [ -z "$before_main_pid" ] || [ -z "$before_start_identity" ]; then
490
+ previous_main_pid=""
491
+ previous_start_identity=""
492
+ sleep 1
493
+ continue
494
+ fi
495
+
496
+ if target_health_is_valid && target_process_is_stable; then
497
+ main_pid="$(service_property MainPID 2>/dev/null || true)"
498
+ start_identity="$(service_property ExecMainStartTimestampMonotonic 2>/dev/null || true)"
499
+
500
+ if [ "$before_main_pid" = "$main_pid" ] &&
501
+ [ "$before_start_identity" = "$start_identity" ]; then
502
+ if [ "$main_pid" = "$previous_main_pid" ] &&
503
+ [ "$start_identity" = "$previous_start_identity" ]; then
504
+ return 0
505
+ fi
506
+
507
+ previous_main_pid="$main_pid"
508
+ previous_start_identity="$start_identity"
509
+ else
510
+ previous_main_pid=""
511
+ previous_start_identity=""
512
+ fi
513
+ else
514
+ previous_main_pid=""
515
+ previous_start_identity=""
516
+ fi
517
+ else
518
+ previous_main_pid=""
519
+ previous_start_identity=""
520
+ fi
521
+
522
+ sleep 1
523
+ done
524
+
525
+ return 1
526
+ }
9
527
 
10
528
  # Capture once — used for all status writes so timeout detection works correctly
11
529
  STARTED_AT="$(date -Iseconds)"
@@ -45,7 +563,13 @@ update_status() {
45
563
  cleanup() {
46
564
  local exit_code=$?
47
565
 
48
- if [ $exit_code -ne 0 ]; then
566
+ if [ "$exit_code" -ne 0 ] && [ "$FINALIZED" != "true" ]; then
567
+ if [ -n "$ATTEMPT_ID" ] && { [ "$MIGRATION_ENTERED" = "true" ] || phase_requires_hold; }; then
568
+ hold_attempt "interrupted" "Update worker exited with code $exit_code during a state-changing phase"
569
+ elif [ -n "$ATTEMPT_ID" ]; then
570
+ finish_before_migration "failed" "Update process exited with code $exit_code"
571
+ fi
572
+
49
573
  # Only write a generic failure if a specific error wasn't already recorded
50
574
  if [ -f "$STATUS_FILE" ] && grep -q '"status": "failed"' "$STATUS_FILE" 2>/dev/null; then
51
575
  return
@@ -56,6 +580,27 @@ cleanup() {
56
580
 
57
581
  trap cleanup EXIT
58
582
 
583
+ handle_signal() {
584
+ local signal_name="$1"
585
+ local signal_code="$2"
586
+
587
+ if [ -n "$ATTEMPT_ID" ]; then
588
+ if [ "$MIGRATION_ENTERED" = "true" ] || phase_requires_hold; then
589
+ hold_attempt "interrupted" "Update worker received SIG${signal_name} after migration entry"
590
+ else
591
+ finish_before_migration "interrupted" "Update worker received SIG${signal_name} before migration entry"
592
+ fi
593
+ fi
594
+
595
+ update_status "failed" "failed" "Update worker received SIG${signal_name}"
596
+ FINALIZED="true"
597
+ exit "$signal_code"
598
+ }
599
+
600
+ trap 'handle_signal TERM 143' TERM
601
+ trap 'handle_signal INT 130' INT
602
+ trap 'handle_signal HUP 129' HUP
603
+
59
604
  # ──────────────────────────────────────────────────────────────
60
605
  # Image-based update (Raspbian image installs)
61
606
  # ──────────────────────────────────────────────────────────────
@@ -65,6 +610,21 @@ if [ "$INSTALL_TYPE" = "image" ]; then
65
610
  NEW_VERSION_DIR="${IMAGE_BASE_DIR}/v${CLEAN_VERSION}"
66
611
  CURRENT_LINK="${IMAGE_BASE_DIR}/current"
67
612
 
613
+ # Image updates must verify the target release through the existing local health route. A
614
+ # missing route would otherwise silently reduce readiness to a PID check and reintroduce the
615
+ # startup/worker circular wait that the updater is designed to avoid.
616
+ if [ -z "$HEALTH_URL" ]; then
617
+ update_status "failed" "failed" "No local health URL configured for image update"
618
+ FINALIZED="true"
619
+ exit 1
620
+ fi
621
+
622
+ if ! acquire_attempt; then
623
+ update_status "failed" "failed" "An unresolved update attempt already owns the updater"
624
+ FINALIZED="true"
625
+ exit 1
626
+ fi
627
+
68
628
  # Verify passwordless sudo is available for all required commands.
69
629
  # Without -n, sudo may hang waiting for a password on a detached process.
70
630
  SUDO_CMDS=(
@@ -77,8 +637,7 @@ if [ "$INSTALL_TYPE" = "image" ]; then
77
637
 
78
638
  for cmd in "${SUDO_CMDS[@]}"; do
79
639
  if ! sudo -n -l $cmd >/dev/null 2>&1; then
80
- update_status "failed" "failed" "Passwordless sudo not available for: $cmd"
81
- exit 1
640
+ fail_before_migration "preflight" "Passwordless sudo not available for: $cmd"
82
641
  fi
83
642
  done
84
643
  PREVIOUS_TARGET=""
@@ -86,6 +645,10 @@ if [ "$INSTALL_TYPE" = "image" ]; then
86
645
  # Save the current version for rollback
87
646
  if [ -L "$CURRENT_LINK" ]; then
88
647
  PREVIOUS_TARGET=$(readlink "$CURRENT_LINK")
648
+ case "$PREVIOUS_TARGET" in
649
+ /*) ;;
650
+ *) PREVIOUS_TARGET="${IMAGE_BASE_DIR}/${PREVIOUS_TARGET}" ;;
651
+ esac
89
652
  fi
90
653
 
91
654
  # Guard: refuse to overwrite the currently running version
@@ -94,23 +657,20 @@ if [ "$INSTALL_TYPE" = "image" ]; then
94
657
  RESOLVED_NEW=$(cd "$IMAGE_BASE_DIR" && realpath "$NEW_VERSION_DIR" 2>/dev/null || true)
95
658
 
96
659
  if [ -n "$PREVIOUS_TARGET" ] && [ -n "$RESOLVED_PREV" ] && [ -n "$RESOLVED_NEW" ] && [ "$RESOLVED_PREV" = "$RESOLVED_NEW" ]; then
97
- update_status "failed" "failed" "Target version v${CLEAN_VERSION} is already the active version"
98
- exit 1
660
+ fail_before_migration "preflight" "Target version v${CLEAN_VERSION} is already the active version"
99
661
  fi
100
662
 
101
663
  # ── Download ──
102
664
  update_status "downloading" "downloading"
103
665
 
104
666
  if [ -z "$DOWNLOAD_URL" ]; then
105
- update_status "failed" "failed" "No download URL provided for image update"
106
- exit 1
667
+ fail_before_migration "downloading" "No download URL provided for image update"
107
668
  fi
108
669
 
109
670
  TMP_TARBALL="/tmp/smart-panel-backend-v${CLEAN_VERSION}.tar.gz"
110
671
 
111
672
  curl -fSL -o "$TMP_TARBALL" "$DOWNLOAD_URL" 2>&1 || {
112
- update_status "failed" "failed" "Download failed from ${DOWNLOAD_URL}"
113
- exit 1
673
+ fail_before_migration "downloading" "Download failed from ${DOWNLOAD_URL}"
114
674
  }
115
675
 
116
676
  # ── Extract ──
@@ -121,8 +681,7 @@ if [ "$INSTALL_TYPE" = "image" ]; then
121
681
  tar -xzf "$TMP_TARBALL" -C "$NEW_VERSION_DIR" 2>&1 || {
122
682
  rm -rf "$NEW_VERSION_DIR"
123
683
  rm -f "$TMP_TARBALL"
124
- update_status "failed" "failed" "Failed to extract update archive"
125
- exit 1
684
+ fail_before_migration "installing" "Failed to extract update archive"
126
685
  }
127
686
 
128
687
  rm -f "$TMP_TARBALL"
@@ -130,8 +689,7 @@ if [ "$INSTALL_TYPE" = "image" ]; then
130
689
  # Create the image-install marker in the new version
131
690
  touch "${NEW_VERSION_DIR}/.image-install" || {
132
691
  rm -rf "$NEW_VERSION_DIR"
133
- update_status "failed" "failed" "Failed to create image-install marker"
134
- exit 1
692
+ fail_before_migration "installing" "Failed to create image-install marker"
135
693
  }
136
694
 
137
695
  # ── Verify dependencies ──
@@ -143,8 +701,7 @@ if [ "$INSTALL_TYPE" = "image" ]; then
143
701
  if [ ! -d "node_modules" ]; then
144
702
  npm install --omit=dev 2>&1 || {
145
703
  rm -rf "$NEW_VERSION_DIR"
146
- update_status "failed" "failed" "npm install failed"
147
- exit 1
704
+ fail_before_migration "installing" "npm install failed"
148
705
  }
149
706
  fi
150
707
 
@@ -153,72 +710,122 @@ if [ "$INSTALL_TYPE" = "image" ]; then
153
710
  # hanging on password prompt when running as a detached process.
154
711
  sudo -n chown -R smart-panel:smart-panel "$NEW_VERSION_DIR" || {
155
712
  rm -rf "$NEW_VERSION_DIR"
156
- update_status "failed" "failed" "Failed to set ownership on ${NEW_VERSION_DIR}"
157
- exit 1
713
+ fail_before_migration "installing" "Failed to set ownership on ${NEW_VERSION_DIR}"
158
714
  }
159
715
 
160
716
  # ── Stop service ──
161
717
  update_status "stopping" "stopping"
162
- sudo -n systemctl stop smart-panel 2>/dev/null || true
718
+ write_attempt "active" "stopping" "" "false" "$MIGRATION_ENTERED"
719
+ if ! capture_service_identity; then
720
+ hold_attempt "stopping" "Could not capture service process/cgroup identity before stop"
721
+ update_status "failed" "failed" "Service process/cgroup identity could not be captured; update is held for recovery"
722
+ FINALIZED="true"
723
+ exit 1
724
+ fi
725
+
726
+ if ! sudo -n systemctl stop "$SERVICE_NAME" 2>/dev/null; then
727
+ hold_attempt "stopping" "Service stop failed; writer quiescence is unknown"
728
+ update_status "failed" "failed" "Could not stop ${SERVICE_NAME}; update is held for recovery"
729
+ FINALIZED="true"
730
+ exit 1
731
+ fi
732
+
733
+ if ! wait_for_quiescence; then
734
+ hold_attempt "stopping" "Service did not reach verified quiescence before migration"
735
+ update_status "failed" "failed" "Service quiescence could not be verified; update is held for recovery"
736
+ FINALIZED="true"
737
+ exit 1
738
+ fi
739
+
740
+ write_attempt "active" "quiesced" "" "false" "$MIGRATION_ENTERED"
163
741
 
164
742
  # ── Switch symlink (atomic on same filesystem) ──
165
743
  sudo -n ln -sfn "$NEW_VERSION_DIR" "$CURRENT_LINK" || {
166
- # Revert on failure
167
- if [ -n "$PREVIOUS_TARGET" ]; then
168
- sudo -n ln -sfn "$PREVIOUS_TARGET" "$CURRENT_LINK"
744
+ # Revert only when the old link, target cleanup and old-service health are all verified.
745
+ if restore_before_migration "$PREVIOUS_TARGET" "$CURRENT_LINK" "$NEW_VERSION_DIR"; then
746
+ fail_before_migration "switching" "Failed to switch version symlink"
169
747
  fi
170
- sudo -n systemctl start smart-panel 2>/dev/null || true
171
- update_status "failed" "failed" "Failed to switch version symlink"
748
+
749
+ hold_attempt "switching" "Failed to switch version symlink and recovery start was not verified"
750
+ update_status "failed" "failed" "Failed to switch version symlink; update is held for recovery"
751
+ FINALIZED="true"
172
752
  exit 1
173
753
  }
174
754
 
175
755
  # ── Run database migrations ──
176
756
  update_status "migrating" "migrating"
177
-
178
757
  ENV_FILE="/etc/smart-panel/environment"
179
758
 
180
- if [ -f "${NEW_VERSION_DIR}/dist/dataSource.js" ]; then
181
- (
182
- set -a
183
- # shellcheck source=/dev/null
184
- [ -f "$ENV_FILE" ] && . "$ENV_FILE"
185
- set +a
186
- cd "$NEW_VERSION_DIR"
187
- node node_modules/typeorm/cli.js migration:run -d dist/dataSource.js
188
- ) 2>&1 || {
189
- # Migration failed — revert symlink if possible
190
- update_status "stopping" "stopping"
191
- sudo -n systemctl stop smart-panel 2>/dev/null || true
192
-
193
- if [ -n "$PREVIOUS_TARGET" ]; then
194
- if sudo -n ln -sfn "$PREVIOUS_TARGET" "$CURRENT_LINK"; then
195
- rm -rf "$NEW_VERSION_DIR"
196
- update_status "failed" "failed" "Database migration failed — reverted to previous version"
197
- else
198
- # Symlink revert failed — keep new version so system stays bootable
199
- update_status "failed" "failed" "Database migration failed — symlink revert also failed"
200
- fi
201
- else
202
- # No previous version — keep current in place so system stays bootable
203
- update_status "failed" "failed" "Database migration failed — no previous version to revert to"
204
- fi
759
+ if [ ! -f "${NEW_VERSION_DIR}/dist/dataSource.js" ]; then
760
+ hold_attempt "migrating" "Target data source is missing; update is held for recovery"
761
+ update_status "failed" "failed" "Target data source is missing; update is held for recovery"
762
+ FINALIZED="true"
763
+ exit 1
764
+ fi
765
+
766
+ MIGRATION_ENTERED="true"
767
+ if ! write_attempt "active" "migrating" "" "false" "true"; then
768
+ hold_attempt "migrating" "Could not persist migration-entry checkpoint"
769
+ update_status "failed" "failed" "Could not persist migration-entry checkpoint; update is held for recovery"
770
+ FINALIZED="true"
771
+ exit 1
772
+ fi
205
773
 
206
- sudo -n systemctl start smart-panel 2>/dev/null || true
207
- exit 1
774
+ MIGRATION_LOG="${ATTEMPT_DIR}/${ATTEMPT_ID}.migration.log"
775
+ : > "$MIGRATION_LOG"
776
+ chmod 600 "$MIGRATION_LOG"
777
+ write_attempt "active" "migrating" "" "false" "true"
778
+
779
+ set +e
780
+ (
781
+ set -a
782
+ # shellcheck source=/dev/null
783
+ [ -f "$ENV_FILE" ] && . "$ENV_FILE"
784
+ set +a
785
+ cd "$NEW_VERSION_DIR"
786
+ node node_modules/typeorm/cli.js migration:run -d dist/dataSource.js
787
+ ) 2>&1 | awk -v output="$MIGRATION_LOG" '
788
+ BEGIN { limit = 65536; bytes = 0 }
789
+ {
790
+ if (bytes < limit) {
791
+ line = $0 ORS;
792
+ remaining = limit - bytes;
793
+ if (length(line) > remaining) line = substr(line, 1, remaining);
794
+ printf "%s", line >> output;
795
+ bytes += length(line);
796
+ }
208
797
  }
798
+ '
799
+ pipeline_status=("${PIPESTATUS[@]}")
800
+ migration_exit_code="${pipeline_status[0]}"
801
+ capture_exit_code="${pipeline_status[1]}"
802
+ set -e
803
+
804
+ if [ "$capture_exit_code" -ne 0 ]; then
805
+ hold_attempt "migration_capture_failed" "Could not retain bounded migration diagnostics"
806
+ update_status "failed" "failed" "Migration diagnostics could not be retained; update is held for recovery"
807
+ FINALIZED="true"
808
+ exit 1
809
+ fi
810
+
811
+ if [ "$migration_exit_code" -eq 0 ]; then
812
+ write_attempt "active" "migrated" "" "false" "true"
813
+ else
814
+ hold_attempt "migration_failed" "Database migration failed with exit code ${migration_exit_code}"
815
+ update_status "failed" "failed" "Database migration failed; update is held for recovery"
816
+ FINALIZED="true"
817
+ exit "$migration_exit_code"
209
818
  fi
210
819
 
211
820
  # ── Start service ──
212
821
  update_status "starting" "starting"
213
- sudo -n systemctl start smart-panel 2>&1 || {
214
- # Try to revert if start fails
215
- if [ -n "$PREVIOUS_TARGET" ]; then
216
- sudo -n ln -sfn "$PREVIOUS_TARGET" "$CURRENT_LINK"
217
- sudo -n systemctl start smart-panel 2>/dev/null || true
218
- fi
219
- update_status "failed" "failed" "Failed to start service after update"
822
+ write_attempt "active" "starting" "" "false" "true"
823
+ if ! sudo -n systemctl start "$SERVICE_NAME" 2>&1 || ! wait_for_started_service; then
824
+ hold_attempt "start_failed" "Target service did not reach verified health after migration"
825
+ update_status "failed" "failed" "Target service failed to start or become healthy; update is held for recovery"
826
+ FINALIZED="true"
220
827
  exit 1
221
- }
828
+ fi
222
829
 
223
830
  # ── Cleanup old versions (keep max 2 previous) ──
224
831
  cd "$IMAGE_BASE_DIR"
@@ -239,7 +846,9 @@ if [ "$INSTALL_TYPE" = "image" ]; then
239
846
  fi
240
847
 
241
848
  # ── Mark complete ──
849
+ release_attempt
242
850
  update_status "complete" "complete"
851
+ FINALIZED="true"
243
852
  trap - EXIT
244
853
  exit 0
245
854
  fi