smart-home-engine 1.34.3 → 1.35.0

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.
@@ -1,4 +1,4 @@
1
- import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-BtBtSJng.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-DfNXDuwc.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -172,10 +172,10 @@
172
172
  }
173
173
  })();
174
174
  </script>
175
- <script type="module" crossorigin src="/assets/index-BtBtSJng.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-DfNXDuwc.js"></script>
176
176
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-DKJI_o9J.js">
177
177
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
178
- <link rel="stylesheet" crossorigin href="/assets/index-CXLA5efM.css">
178
+ <link rel="stylesheet" crossorigin href="/assets/index-DwaZ1Sfz.css">
179
179
  </head>
180
180
  <body>
181
181
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "1.34.3",
3
+ "version": "1.35.0",
4
4
  "description": "Node.js based script runner for use in MQTT based Smart Home environments",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -24,7 +24,7 @@
24
24
  # she-servicectl schema <adapter>
25
25
  # she-servicectl install <adapter> <instance> (options as KEY=VALUE lines on stdin)
26
26
  # she-servicectl uninstall <adapter> <instance>
27
- # she-servicectl npm <adapter> version|origin|install|update
27
+ # she-servicectl npm <adapter> version|origin|install|update|uninstall [--purge] (uninstall: package, template unit; --purge also /etc and /var/lib)
28
28
  # she-servicectl files <adapter> <instance> JSON listing of /etc/<adapter>/ and /var/lib/<adapter>/<instance>/
29
29
  # she-servicectl file <adapter> <instance> read|write <path> a file inside those two directories (write: content on stdin)
30
30
  # she-servicectl asset <adapter> <relpath> a file shipped in the adapter package (example, schema)
@@ -40,7 +40,7 @@
40
40
 
41
41
  set -eu
42
42
 
43
- VERSION=8
43
+ VERSION=9
44
44
  BROKER_ENV=/etc/mqtt-interfaces/broker.env
45
45
  UNIT_DIR=/etc/systemd/system
46
46
 
@@ -599,9 +599,25 @@ cmd_teardown() {
599
599
  }
600
600
 
601
601
  cmd_npm() {
602
- [ $# -eq 2 ] || die "usage: npm <adapter> version|origin|install|update"
602
+ [ $# -ge 2 ] || die "usage: npm <adapter> version|origin|install|update|uninstall [--purge]"
603
603
  need_adapter "$1"
604
604
  case "$2" in
605
+ uninstall)
606
+ # the package and what it left behind; instances must be gone (she removes them first)
607
+ if [ -n "$(ls "/etc/$1"/*.env 2>/dev/null)" ]; then die "instances of $1 still exist — uninstall them first"; fi
608
+ npm=$(adapter_npm "$1")
609
+ node=$(adapter_node "$1")
610
+ PATH="$(dirname "$node"):$PATH" "$npm" uninstall -g "$1" --no-audit --no-fund 2>&1 || true
611
+ dir=$(adapter_dir "$1" 2>/dev/null || true)
612
+ if [ -n "$dir" ] && [ -d "$dir" ]; then case "$dir" in */node_modules/"$1") rm -rf "$dir"; printf 'removed %s\n' "$dir" ;; esac; fi
613
+ [ -L "/usr/local/bin/$1" ] && [ ! -e "/usr/local/bin/$1" ] && rm -f "/usr/local/bin/$1"
614
+ if [ -f "$UNIT_DIR/$1@.service" ]; then rm -f "$UNIT_DIR/$1@.service"; systemctl daemon-reload; printf 'removed %s\n' "$UNIT_DIR/$1@.service"; fi
615
+ if [ "${3:-}" = "--purge" ]; then
616
+ for d in "/etc/$1" "/var/lib/$1"; do [ -d "$d" ] && rm -rf "$d" && printf 'removed %s\n' "$d"; done
617
+ else
618
+ for d in "/etc/$1" "/var/lib/$1"; do [ -d "$d" ] && rmdir "$d" 2>/dev/null && printf 'removed empty %s\n' "$d"; done
619
+ fi
620
+ printf '%s uninstalled\n' "$1" ;;
605
621
  version) adapter_version "$1"; printf '\n' ;;
606
622
  origin) adapter_origin "$1"; printf '\n' ;;
607
623
  install|update)
@@ -26,7 +26,7 @@ const { execFile, spawn } = require('child_process');
26
26
  const sshDeploy = require('./ssh-deploy');
27
27
 
28
28
  const HELPER = '/usr/local/bin/she-servicectl';
29
- const HELPER_VERSION = 8; // must match VERSION in service/she-servicectl
29
+ const HELPER_VERSION = 9; // must match VERSION in service/she-servicectl
30
30
  /** Default identity for services.hosts[].ssh — one key for all managed hosts (I5). */
31
31
  const DEFAULT_SERVICES_IDENTITY = '~/.she/ssh/services_id_ed25519';
32
32
  /** The helper as shipped with this she version (copied to remote hosts by the deploy route). */
@@ -18,6 +18,7 @@
18
18
  * POST /she/services/hosts/:host/adapters/:adapter/update { force? } → npm install -g, restart instances
19
19
  * POST /she/services/hosts/:host/units/:adapter/:instance/:action start|stop|restart|enable|disable
20
20
  * DELETE /she/services/hosts/:host/units/:adapter/:instance <adapter> --uninstall
21
+ * POST /she/services/hosts/:host/adapters/:adapter/uninstall every instance (--uninstall, dynsec identity), then the package, /etc and /var/lib
21
22
  * GET /she/services/hosts/:host/units/:adapter/:instance/logs journal tail (?n=)
22
23
  * POST /she/services/hosts/:host/units/:adapter/:instance/logs/follow start/renew a journal follower (serviceLog WS)
23
24
  * DELETE /she/services/hosts/:host/units/:adapter/:instance/logs/follow stop it
@@ -885,22 +886,52 @@ router.post('/hosts/:host/units/:adapter/:instance/:action', async (req, res) =>
885
886
  });
886
887
 
887
888
  // DELETE /she/services/hosts/:host/units/:adapter/:instance — uninstall
889
+ /** <adapter> --uninstall --name <instance> on the host, plus the instance's dynsec identity if it has one. */
890
+ async function uninstallInstance(entry, adapter, instance) {
891
+ stopFollower(followKey(entry.driver.name, adapter, instance));
892
+ let dynsecClient = null;
893
+ try {
894
+ dynsecClient = parseEnvFile((await entry.driver.exec(['env', adapter, instance, 'read'])).stdout)[DYNSEC_MARKER] || null;
895
+ } catch {
896
+ /* no env file */
897
+ }
898
+ const { stdout } = await entry.driver.exec(['uninstall', adapter, instance], { timeout: 60000 });
899
+ if (dynsecClient) await deleteInstanceCredentials(dynsecClient);
900
+ return { output: stdout, dynsecRemoved: Boolean(dynsecClient) };
901
+ }
902
+
888
903
  router.delete('/hosts/:host/units/:adapter/:instance', async (req, res) => {
889
904
  const entry = resolve(req, res, { adapter: true, instance: true });
890
905
  if (!entry) return;
891
906
  if (req.params.instance === LEGACY) return res.status(400).json({ error: 'a legacy unit is not uninstalled from here — migrate it to an instance first', code: 'LEGACY' });
892
907
  try {
893
- stopFollower(followKey(entry.driver.name, req.params.adapter, req.params.instance));
894
- let dynsecClient = null;
895
- try {
896
- dynsecClient = parseEnvFile((await entry.driver.exec(['env', req.params.adapter, req.params.instance, 'read'])).stdout)[DYNSEC_MARKER] || null;
897
- } catch {
898
- /* no env file */
908
+ res.json({ ok: true, ...(await uninstallInstance(entry, req.params.adapter, req.params.instance)) });
909
+ } catch (err) {
910
+ hostError(res, err);
911
+ }
912
+ });
913
+
914
+ // POST /she/services/hosts/:host/adapters/:adapter/uninstall — the adapter with all its instances
915
+ router.post('/hosts/:host/adapters/:adapter/uninstall', async (req, res) => {
916
+ const entry = resolve(req, res, { adapter: true });
917
+ if (!entry) return;
918
+ const adapter = req.params.adapter;
919
+ try {
920
+ const list = parseList((await entry.driver.exec(['list'])).stdout);
921
+ if ((list.legacy || []).some((l) => l.adapter === adapter)) {
922
+ return res.status(409).json({ error: `${adapter} still runs as a pre-core unit (${adapter}.service) — migrate or stop it by hand first`, code: 'LEGACY' });
923
+ }
924
+ const instances = (list.instances || []).filter((i) => i.adapter === adapter).map((i) => i.instance);
925
+ const removed = [];
926
+ for (const instance of instances) {
927
+ await uninstallInstance(entry, adapter, instance);
928
+ removed.push(instance);
899
929
  }
900
- const { stdout } = await entry.driver.exec(['uninstall', req.params.adapter, req.params.instance], { timeout: 60000 });
901
- if (dynsecClient) await deleteInstanceCredentials(dynsecClient);
902
- res.json({ ok: true, output: stdout, dynsecRemoved: Boolean(dynsecClient) });
930
+ const { stdout } = await entry.driver.exec(['npm', adapter, 'uninstall', '--purge'], { timeout: 120000 });
931
+ invalidateHosts();
932
+ res.json({ ok: true, removedInstances: removed, output: stdout });
903
933
  } catch (err) {
934
+ invalidateHosts();
904
935
  hostError(res, err);
905
936
  }
906
937
  });