@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.
- package/dist/app.module.js +8 -0
- package/dist/app.module.js.map +1 -1
- package/dist/common/filters/conflict-exception.filter.d.ts +6 -0
- package/dist/common/filters/conflict-exception.filter.js +59 -0
- package/dist/common/filters/conflict-exception.filter.js.map +1 -0
- package/dist/common/filters/global-error.filter.js +1 -4
- package/dist/common/filters/global-error.filter.js.map +1 -1
- package/dist/common/filters/global-filters.d.ts +3 -0
- package/dist/common/filters/global-filters.js +22 -0
- package/dist/common/filters/global-filters.js.map +1 -0
- package/dist/common/filters/unprocessable-entity-exception.filter.d.ts +1 -0
- package/dist/common/filters/unprocessable-entity-exception.filter.js +15 -3
- package/dist/common/filters/unprocessable-entity-exception.filter.js.map +1 -1
- package/dist/common/utils/transform.utils.js +3 -0
- package/dist/common/utils/transform.utils.js.map +1 -1
- package/dist/dataSource.js +1 -0
- package/dist/dataSource.js.map +1 -1
- package/dist/main.js +2 -7
- package/dist/main.js.map +1 -1
- package/dist/migrations/1000000000026-AddHardwareInputCategories.d.ts +7 -0
- package/dist/migrations/1000000000026-AddHardwareInputCategories.js +364 -0
- package/dist/migrations/1000000000026-AddHardwareInputCategories.js.map +1 -0
- package/dist/modules/devices/controllers/channels.controller.d.ts +5 -1
- package/dist/modules/devices/controllers/channels.controller.js +34 -2
- package/dist/modules/devices/controllers/channels.controller.js.map +1 -1
- package/dist/modules/devices/controllers/channels.properties.controller.js +21 -4
- package/dist/modules/devices/controllers/channels.properties.controller.js.map +1 -1
- package/dist/modules/devices/controllers/devices.channels.controller.d.ts +5 -1
- package/dist/modules/devices/controllers/devices.channels.controller.js +38 -2
- package/dist/modules/devices/controllers/devices.channels.controller.js.map +1 -1
- package/dist/modules/devices/controllers/devices.channels.properties.controller.js +21 -4
- package/dist/modules/devices/controllers/devices.channels.properties.controller.js.map +1 -1
- package/dist/modules/devices/devices.constants.d.ts +6 -0
- package/dist/modules/devices/devices.constants.js +6 -0
- package/dist/modules/devices/devices.constants.js.map +1 -1
- package/dist/modules/devices/devices.module.js +18 -0
- package/dist/modules/devices/devices.module.js.map +1 -1
- package/dist/modules/devices/devices.openapi.d.ts +3 -1
- package/dist/modules/devices/devices.openapi.js +7 -0
- package/dist/modules/devices/devices.openapi.js.map +1 -1
- package/dist/modules/devices/dto/channel-input-occurrence.dto.d.ts +10 -0
- package/dist/modules/devices/dto/channel-input-occurrence.dto.js +99 -0
- package/dist/modules/devices/dto/channel-input-occurrence.dto.js.map +1 -0
- package/dist/modules/devices/models/channel-input-capabilities.model.d.ts +20 -0
- package/dist/modules/devices/models/channel-input-capabilities.model.js +111 -0
- package/dist/modules/devices/models/channel-input-capabilities.model.js.map +1 -0
- package/dist/modules/devices/models/channel-input-occurrence.model.d.ts +22 -0
- package/dist/modules/devices/models/channel-input-occurrence.model.js +154 -0
- package/dist/modules/devices/models/channel-input-occurrence.model.js.map +1 -0
- package/dist/modules/devices/services/channel-input-deduplication.service.d.ts +17 -0
- package/dist/modules/devices/services/channel-input-deduplication.service.js +88 -0
- package/dist/modules/devices/services/channel-input-deduplication.service.js.map +1 -0
- package/dist/modules/devices/services/channel-input-occurrences.service.d.ts +21 -0
- package/dist/modules/devices/services/channel-input-occurrences.service.js +163 -0
- package/dist/modules/devices/services/channel-input-occurrences.service.js.map +1 -0
- package/dist/modules/devices/services/channels.properties.service.d.ts +27 -3
- package/dist/modules/devices/services/channels.properties.service.js +304 -28
- package/dist/modules/devices/services/channels.properties.service.js.map +1 -1
- package/dist/modules/devices/services/channels.service.js +42 -38
- package/dist/modules/devices/services/channels.service.js.map +1 -1
- package/dist/modules/devices/services/command-latency-trace-collector.service.d.ts +101 -0
- package/dist/modules/devices/services/command-latency-trace-collector.service.js +451 -0
- package/dist/modules/devices/services/command-latency-trace-collector.service.js.map +1 -0
- package/dist/modules/devices/services/device-structure-lock.service.d.ts +10 -2
- package/dist/modules/devices/services/device-structure-lock.service.js +86 -7
- package/dist/modules/devices/services/device-structure-lock.service.js.map +1 -1
- package/dist/modules/devices/services/devices.service.js +37 -33
- package/dist/modules/devices/services/devices.service.js.map +1 -1
- package/dist/modules/devices/services/module-reset.service.d.ts +3 -1
- package/dist/modules/devices/services/module-reset.service.js +21 -16
- package/dist/modules/devices/services/module-reset.service.js.map +1 -1
- package/dist/modules/devices/services/property-command-dispatch.service.d.ts +45 -0
- package/dist/modules/devices/services/property-command-dispatch.service.js +227 -0
- package/dist/modules/devices/services/property-command-dispatch.service.js.map +1 -0
- package/dist/modules/devices/services/property-command-window.service.d.ts +71 -0
- package/dist/modules/devices/services/property-command-window.service.js +204 -0
- package/dist/modules/devices/services/property-command-window.service.js.map +1 -0
- package/dist/modules/devices/services/property-command.service.d.ts +20 -3
- package/dist/modules/devices/services/property-command.service.js +56 -10
- package/dist/modules/devices/services/property-command.service.js.map +1 -1
- package/dist/modules/devices/services/property-state-coordinator.service.d.ts +4 -0
- package/dist/modules/devices/services/property-state-coordinator.service.js +35 -0
- package/dist/modules/devices/services/property-state-coordinator.service.js.map +1 -0
- package/dist/modules/devices/services/property-value.service.d.ts +1 -0
- package/dist/modules/devices/services/property-value.service.js +4 -0
- package/dist/modules/devices/services/property-value.service.js.map +1 -1
- package/dist/modules/devices/utils/schema.utils.js +1 -1
- package/dist/modules/devices/utils/schema.utils.js.map +1 -1
- package/dist/modules/platform/services/platform.service.d.ts +13 -4
- package/dist/modules/platform/services/platform.service.js +57 -35
- package/dist/modules/platform/services/platform.service.js.map +1 -1
- package/dist/modules/remote-access/dto/update-config.dto.js +5 -0
- package/dist/modules/remote-access/dto/update-config.dto.js.map +1 -1
- package/dist/modules/remote-access/validators/is-remote-access-url.validator.js +5 -2
- package/dist/modules/remote-access/validators/is-remote-access-url.validator.js.map +1 -1
- package/dist/modules/system/scripts/update-worker.sh +668 -59
- package/dist/modules/system/services/privileged-worker.service.d.ts +10 -0
- package/dist/modules/system/services/privileged-worker.service.js +137 -11
- package/dist/modules/system/services/privileged-worker.service.js.map +1 -1
- package/dist/modules/system/services/update-executor.service.d.ts +20 -2
- package/dist/modules/system/services/update-executor.service.js +256 -25
- package/dist/modules/system/services/update-executor.service.js.map +1 -1
- package/dist/modules/websocket/gateway/websocket.gateway.d.ts +4 -1
- package/dist/modules/websocket/gateway/websocket.gateway.js +28 -5
- package/dist/modules/websocket/gateway/websocket.gateway.js.map +1 -1
- package/dist/modules/websocket/services/command-acknowledgement-trace.service.d.ts +143 -0
- package/dist/modules/websocket/services/command-acknowledgement-trace.service.js +523 -0
- package/dist/modules/websocket/services/command-acknowledgement-trace.service.js.map +1 -0
- package/dist/modules/websocket/websocket.module.js +9 -1
- package/dist/modules/websocket/websocket.module.js.map +1 -1
- package/dist/plugins/devices-home-assistant/devices-home-assistant.openapi.d.ts +2 -2
- package/dist/plugins/devices-home-assistant/devices-home-assistant.plugin.d.ts +6 -1
- package/dist/plugins/devices-home-assistant/devices-home-assistant.plugin.js +16 -1
- package/dist/plugins/devices-home-assistant/devices-home-assistant.plugin.js.map +1 -1
- package/dist/plugins/devices-home-assistant/dto/mapping-preview.dto.d.ts +1 -0
- package/dist/plugins/devices-home-assistant/dto/mapping-preview.dto.js +11 -0
- package/dist/plugins/devices-home-assistant/dto/mapping-preview.dto.js.map +1 -1
- package/dist/plugins/devices-home-assistant/mappers/button.entity.mapper.service.d.ts +19 -0
- package/dist/plugins/devices-home-assistant/mappers/button.entity.mapper.service.js +51 -0
- package/dist/plugins/devices-home-assistant/mappers/button.entity.mapper.service.js.map +1 -0
- package/dist/plugins/devices-home-assistant/mappings/definitions/entity-mappings.yaml +56 -7
- package/dist/plugins/devices-home-assistant/models/mapping-preview.model.d.ts +1 -0
- package/dist/plugins/devices-home-assistant/models/mapping-preview.model.js +11 -0
- package/dist/plugins/devices-home-assistant/models/mapping-preview.model.js.map +1 -1
- package/dist/plugins/devices-home-assistant/services/device-adoption.service.js +17 -0
- package/dist/plugins/devices-home-assistant/services/device-adoption.service.js.map +1 -1
- package/dist/plugins/devices-home-assistant/services/home-assistant-raw-event.service.d.ts +27 -0
- package/dist/plugins/devices-home-assistant/services/home-assistant-raw-event.service.js +271 -0
- package/dist/plugins/devices-home-assistant/services/home-assistant-raw-event.service.js.map +1 -0
- package/dist/plugins/devices-home-assistant/services/home-assistant.ws.service.d.ts +2 -2
- package/dist/plugins/devices-home-assistant/services/home-assistant.ws.service.js +33 -14
- package/dist/plugins/devices-home-assistant/services/home-assistant.ws.service.js.map +1 -1
- package/dist/plugins/devices-home-assistant/services/mapping-preview.service.js +24 -4
- package/dist/plugins/devices-home-assistant/services/mapping-preview.service.js.map +1 -1
- package/dist/plugins/devices-home-assistant/services/state-changed.event.service.d.ts +4 -1
- package/dist/plugins/devices-home-assistant/services/state-changed.event.service.js +84 -5
- package/dist/plugins/devices-home-assistant/services/state-changed.event.service.js.map +1 -1
- package/dist/plugins/devices-home-assistant/services/wizard.service.js +1 -0
- package/dist/plugins/devices-home-assistant/services/wizard.service.js.map +1 -1
- package/dist/plugins/devices-home-assistant/utils/ha-event.utils.d.ts +2 -0
- package/dist/plugins/devices-home-assistant/utils/ha-event.utils.js +50 -0
- package/dist/plugins/devices-home-assistant/utils/ha-event.utils.js.map +1 -0
- package/dist/plugins/devices-homekit/listeners/homekit-event.listener.d.ts +2 -0
- package/dist/plugins/devices-homekit/listeners/homekit-event.listener.js +28 -1
- package/dist/plugins/devices-homekit/listeners/homekit-event.listener.js.map +1 -1
- package/dist/plugins/devices-homekit/mappers/base.mapper.js +27 -4
- package/dist/plugins/devices-homekit/mappers/base.mapper.js.map +1 -1
- package/dist/plugins/devices-homekit/mappers/button.mapper.d.ts +15 -0
- package/dist/plugins/devices-homekit/mappers/button.mapper.js +143 -0
- package/dist/plugins/devices-homekit/mappers/button.mapper.js.map +1 -0
- package/dist/plugins/devices-homekit/mappers/homekit-mapper.interface.d.ts +16 -0
- package/dist/plugins/devices-homekit/mappers/thermostat-coordinator.d.ts +7 -0
- package/dist/plugins/devices-homekit/mappers/thermostat-coordinator.js +84 -73
- package/dist/plugins/devices-homekit/mappers/thermostat-coordinator.js.map +1 -1
- package/dist/plugins/devices-homekit/services/homekit-mapper-registry.service.d.ts +5 -1
- package/dist/plugins/devices-homekit/services/homekit-mapper-registry.service.js +42 -0
- package/dist/plugins/devices-homekit/services/homekit-mapper-registry.service.js.map +1 -1
- package/dist/plugins/devices-homey/mappings/definitions/channels.yaml +50 -0
- package/dist/plugins/devices-homey/mappings/definitions/devices.yaml +18 -0
- package/dist/plugins/devices-homey/mappings/definitions/properties.yaml +55 -0
- package/dist/plugins/devices-homey/mappings/device-support.js +10 -0
- package/dist/plugins/devices-homey/mappings/device-support.js.map +1 -1
- package/dist/plugins/devices-homey/mappings/mapping-loader.service.d.ts +1 -0
- package/dist/plugins/devices-homey/mappings/mapping-loader.service.js +66 -8
- package/dist/plugins/devices-homey/mappings/mapping-loader.service.js.map +1 -1
- package/dist/plugins/devices-homey/models/inventory.model.d.ts +2 -1
- package/dist/plugins/devices-homey/models/inventory.model.js +1 -0
- package/dist/plugins/devices-homey/models/inventory.model.js.map +1 -1
- package/dist/plugins/devices-homey/models/mapping-preview.model.d.ts +1 -0
- package/dist/plugins/devices-homey/models/mapping-preview.model.js +1 -0
- package/dist/plugins/devices-homey/models/mapping-preview.model.js.map +1 -1
- package/dist/plugins/devices-homey/services/homey-mapping-preview.service.js +7 -0
- package/dist/plugins/devices-homey/services/homey-mapping-preview.service.js.map +1 -1
- package/dist/plugins/devices-homey/services/homey-synchronizer.service.d.ts +3 -1
- package/dist/plugins/devices-homey/services/homey-synchronizer.service.js +37 -6
- package/dist/plugins/devices-homey/services/homey-synchronizer.service.js.map +1 -1
- package/dist/plugins/devices-reterminal/services/reterminal-button.service.d.ts +7 -3
- package/dist/plugins/devices-reterminal/services/reterminal-button.service.js +63 -17
- package/dist/plugins/devices-reterminal/services/reterminal-button.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.d.ts +38 -3
- package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.js +840 -134
- package/dist/plugins/devices-shelly-ng/delegates/delegates-manager.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.d.ts +15 -1
- package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.js +117 -10
- package/dist/plugins/devices-shelly-ng/delegates/shelly-device.delegate.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/devices-shelly-ng.constants.js +11 -13
- package/dist/plugins/devices-shelly-ng/devices-shelly-ng.constants.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/devices-shelly-ng.plugin.js +2 -0
- package/dist/plugins/devices-shelly-ng/devices-shelly-ng.plugin.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/mappings/definitions/inputs.yaml +80 -0
- package/dist/plugins/devices-shelly-ng/mappings/mapping-loader.service.js +11 -1
- package/dist/plugins/devices-shelly-ng/mappings/mapping-loader.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/mappings/mapping.types.d.ts +6 -3
- package/dist/plugins/devices-shelly-ng/mappings/schema/mapping-schema.json +5 -0
- package/dist/plugins/devices-shelly-ng/services/device-manager.service.js +130 -7
- package/dist/plugins/devices-shelly-ng/services/device-manager.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.d.ts +48 -0
- package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.js +457 -0
- package/dist/plugins/devices-shelly-ng/services/poll-placement-diagnostics.service.js.map +1 -0
- package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.d.ts +12 -2
- package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.js +86 -28
- package/dist/plugins/devices-shelly-ng/services/shelly-ng.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/services/shelly-rpc-client.service.js +3 -0
- package/dist/plugins/devices-shelly-ng/services/shelly-rpc-client.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.d.ts +1 -1
- package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.js +18 -13
- package/dist/plugins/devices-shelly-ng/services/shelly-ws-server.service.js.map +1 -1
- package/dist/plugins/devices-shelly-ng/utils/transform.utils.d.ts +2 -0
- package/dist/plugins/devices-shelly-ng/utils/transform.utils.js +14 -1
- package/dist/plugins/devices-shelly-ng/utils/transform.utils.js.map +1 -1
- package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.d.ts +2 -0
- package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.js +66 -4
- package/dist/plugins/devices-shelly-v1/devices-shelly-v1.constants.js.map +1 -1
- package/dist/plugins/devices-shelly-v1/devices-shelly-v1.plugin.js +2 -0
- package/dist/plugins/devices-shelly-v1/devices-shelly-v1.plugin.js.map +1 -1
- package/dist/plugins/devices-shelly-v1/platforms/shelly-v1.device.platform.js +7 -0
- package/dist/plugins/devices-shelly-v1/platforms/shelly-v1.device.platform.js.map +1 -1
- package/dist/plugins/devices-shelly-v1/services/device-mapper.service.d.ts +3 -1
- package/dist/plugins/devices-shelly-v1/services/device-mapper.service.js +20 -2
- package/dist/plugins/devices-shelly-v1/services/device-mapper.service.js.map +1 -1
- package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.d.ts +16 -0
- package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.js +107 -0
- package/dist/plugins/devices-shelly-v1/services/shelly-v1-input-tracker.service.js.map +1 -0
- package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.d.ts +7 -1
- package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.js +98 -3
- package/dist/plugins/devices-shelly-v1/services/shelly-v1.service.js.map +1 -1
- package/dist/plugins/devices-third-party/controllers/third-party-inputs.controller.d.ts +17 -0
- package/dist/plugins/devices-third-party/controllers/third-party-inputs.controller.js +205 -0
- package/dist/plugins/devices-third-party/controllers/third-party-inputs.controller.js.map +1 -0
- package/dist/plugins/devices-third-party/devices-third-party.openapi.d.ts +1 -1
- package/dist/plugins/devices-third-party/devices-third-party.plugin.js +2 -1
- package/dist/plugins/devices-third-party/devices-third-party.plugin.js.map +1 -1
- package/dist/plugins/devices-third-party/dto/report-input-occurrence-response.dto.d.ts +5 -0
- package/dist/plugins/devices-third-party/dto/report-input-occurrence-response.dto.js +50 -0
- package/dist/plugins/devices-third-party/dto/report-input-occurrence-response.dto.js.map +1 -0
- package/dist/plugins/devices-third-party/dto/report-input-occurrence.dto.d.ts +8 -0
- package/dist/plugins/devices-third-party/dto/report-input-occurrence.dto.js +81 -0
- package/dist/plugins/devices-third-party/dto/report-input-occurrence.dto.js.map +1 -0
- package/dist/plugins/devices-third-party/platforms/third-party-device.platform.js +19 -4
- package/dist/plugins/devices-third-party/platforms/third-party-device.platform.js.map +1 -1
- package/dist/plugins/devices-virtual/listeners/virtual-projection.listener.d.ts +2 -0
- package/dist/plugins/devices-virtual/listeners/virtual-projection.listener.js +57 -0
- package/dist/plugins/devices-virtual/listeners/virtual-projection.listener.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.d.ts +3 -0
- package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.js +41 -5
- package/dist/plugins/devices-zigbee2mqtt/devices-zigbee2mqtt.constants.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/interfaces/zigbee2mqtt.interface.d.ts +7 -1
- package/dist/plugins/devices-zigbee2mqtt/mappings/config-driven.converter.js +16 -15
- package/dist/plugins/devices-zigbee2mqtt/mappings/config-driven.converter.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/mappings/definitions/remotes.yaml +505 -74
- package/dist/plugins/devices-zigbee2mqtt/mappings/schema/mapping-schema.json +3 -3
- package/dist/plugins/devices-zigbee2mqtt/mappings/transformers/transformer.types.d.ts +1 -1
- package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.d.ts +8 -2
- package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.js +6 -5
- package/dist/plugins/devices-zigbee2mqtt/services/base-client-adapter.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/services/device-adoption.service.js +18 -11
- package/dist/plugins/devices-zigbee2mqtt/services/device-adoption.service.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.d.ts +9 -3
- package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.js +166 -3
- package/dist/plugins/devices-zigbee2mqtt/services/device-mapper.service.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/services/mqtt-client-adapter.service.js +5 -2
- package/dist/plugins/devices-zigbee2mqtt/services/mqtt-client-adapter.service.js.map +1 -1
- package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.d.ts +1 -0
- package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.js +44 -4
- package/dist/plugins/devices-zigbee2mqtt/services/zigbee2mqtt.service.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.d.ts +17 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.js +147 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.d.ts +15 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.js +98 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/dto/update-config.dto.d.ts +9 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/dto/update-config.dto.js +94 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/dto/update-config.dto.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/config.model.d.ts +9 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/config.model.js +92 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/config.model.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/install.model.d.ts +7 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/install.model.js +48 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/install.model.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.d.ts +42 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.js +352 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.constants.d.ts +24 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.constants.js +27 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.constants.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.exceptions.d.ts +8 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.exceptions.js +19 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.exceptions.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.openapi.d.ts +4 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.openapi.js +20 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.openapi.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.plugin.d.ts +21 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.plugin.js +135 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/remote-access-cloudflare-tunnel.plugin.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/scripts/cloudflared-setup.sh +185 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.d.ts +82 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.js +507 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.d.ts +10 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.js +37 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.d.ts +38 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.js +147 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.d.ts +15 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.js +67 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.d.ts +8 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.js +46 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.js.map +1 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-process.service.d.ts +27 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-process.service.js +139 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-process.service.js.map +1 -0
- package/dist/plugins/remote-access-tailscale/controllers/setup.controller.d.ts +1 -0
- package/dist/plugins/remote-access-tailscale/controllers/setup.controller.js +26 -10
- package/dist/plugins/remote-access-tailscale/controllers/setup.controller.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/controllers/status.controller.d.ts +6 -1
- package/dist/plugins/remote-access-tailscale/controllers/status.controller.js +28 -3
- package/dist/plugins/remote-access-tailscale/controllers/status.controller.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/models/status.model.d.ts +19 -0
- package/dist/plugins/remote-access-tailscale/models/status.model.js +158 -1
- package/dist/plugins/remote-access-tailscale/models/status.model.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.d.ts +3 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.js +4 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.d.ts +13 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.js +27 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.js.map +1 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.d.ts +2 -2
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.js +2 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.plugin.js +1 -1
- package/dist/plugins/remote-access-tailscale/scripts/tailscale-setup.sh +61 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.d.ts +9 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.js +12 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.d.ts +1 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js +27 -2
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.d.ts +31 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.js +316 -30
- package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.d.ts +8 -3
- package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.js +39 -12
- package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.d.ts +14 -5
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js +51 -16
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js.map +1 -1
- package/dist/plugins/scenes-local/platforms/local-scene.platform.d.ts +3 -1
- package/dist/plugins/scenes-local/platforms/local-scene.platform.js +12 -7
- package/dist/plugins/scenes-local/platforms/local-scene.platform.js.map +1 -1
- package/dist/plugins/simulator/controllers/simulator.controller.d.ts +8 -2
- package/dist/plugins/simulator/controllers/simulator.controller.js +102 -2
- package/dist/plugins/simulator/controllers/simulator.controller.js.map +1 -1
- package/dist/plugins/simulator/dto/simulate-occurrence.dto.d.ts +12 -0
- package/dist/plugins/simulator/dto/simulate-occurrence.dto.js +122 -0
- package/dist/plugins/simulator/dto/simulate-occurrence.dto.js.map +1 -0
- package/dist/plugins/simulator/models/simulator-response.model.d.ts +12 -0
- package/dist/plugins/simulator/models/simulator-response.model.js +84 -1
- package/dist/plugins/simulator/models/simulator-response.model.js.map +1 -1
- package/dist/plugins/simulator/scenarios/definitions/small-apartment.yaml +78 -0
- package/dist/plugins/simulator/services/scenario-executor.service.js +3 -1
- package/dist/plugins/simulator/services/scenario-executor.service.js.map +1 -1
- package/dist/plugins/simulator/services/simulation.service.js +10 -1
- package/dist/plugins/simulator/services/simulation.service.js.map +1 -1
- package/dist/plugins/simulator/simulator.openapi.d.ts +3 -2
- package/dist/plugins/simulator/simulator.openapi.js +5 -0
- package/dist/plugins/simulator/simulator.openapi.js.map +1 -1
- package/dist/plugins/simulator/simulators/device-simulator.interface.d.ts +2 -0
- package/dist/plugins/simulator/simulators/device-simulator.interface.js +83 -0
- package/dist/plugins/simulator/simulators/device-simulator.interface.js.map +1 -1
- package/dist/plugins/simulator/simulators/index.d.ts +1 -0
- package/dist/plugins/simulator/simulators/index.js +1 -0
- package/dist/plugins/simulator/simulators/index.js.map +1 -1
- package/dist/plugins/simulator/simulators/input-controller.simulator.d.ts +10 -0
- package/dist/plugins/simulator/simulators/input-controller.simulator.js +57 -0
- package/dist/plugins/simulator/simulators/input-controller.simulator.js.map +1 -0
- package/dist/plugins/simulator/simulators/lighting.simulator.js +1 -0
- package/dist/plugins/simulator/simulators/lighting.simulator.js.map +1 -1
- package/dist/plugins/spaces-home-control/services/climate-intent.service.d.ts +3 -1
- package/dist/plugins/spaces-home-control/services/climate-intent.service.js +6 -3
- package/dist/plugins/spaces-home-control/services/climate-intent.service.js.map +1 -1
- package/dist/plugins/spaces-home-control/services/covers-intent.service.d.ts +3 -1
- package/dist/plugins/spaces-home-control/services/covers-intent.service.js +6 -3
- package/dist/plugins/spaces-home-control/services/covers-intent.service.js.map +1 -1
- package/dist/plugins/spaces-home-control/services/lighting-intent.service.d.ts +3 -1
- package/dist/plugins/spaces-home-control/services/lighting-intent.service.js +7 -4
- package/dist/plugins/spaces-home-control/services/lighting-intent.service.js.map +1 -1
- package/dist/plugins/spaces-home-control/services/space-media-activity.service.d.ts +3 -1
- package/dist/plugins/spaces-home-control/services/space-media-activity.service.js +11 -5
- package/dist/plugins/spaces-home-control/services/space-media-activity.service.js.map +1 -1
- package/dist/plugins/spaces-home-control/services/space-undo-history.service.d.ts +3 -1
- package/dist/plugins/spaces-home-control/services/space-undo-history.service.js +8 -5
- package/dist/plugins/spaces-home-control/services/space-undo-history.service.js.map +1 -1
- package/dist/spec/channels.d.ts +51 -1
- package/dist/spec/channels.js +64 -1
- package/dist/spec/channels.js.map +1 -1
- package/dist/spec/devices.d.ts +155 -2
- package/dist/spec/devices.js +161 -2
- package/dist/spec/devices.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
167
|
-
if
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|