smart-home-engine 0.19.8 → 0.20.1
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/web/assets/index-CZEb06G1.js +225 -0
- package/dist/web/assets/{index-CtuJWQQl.css → index-bwhJdsUd.css} +1 -1
- package/dist/web/assets/{tsMode-B_oQbMWr.js → tsMode-C7N40v-8.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
- package/service/install.sh +7 -3
- package/src/index.js +8 -1
- package/src/web/auth.js +10 -3
- package/src/web/server.js +36 -12
- package/dist/web/assets/index-ves1PGl3.js +0 -225
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-
|
|
1
|
+
import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-CZEb06G1.js";/*!-----------------------------------------------------------------------------
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
|
|
4
4
|
* Released under the MIT license
|
package/dist/web/index.html
CHANGED
|
@@ -155,10 +155,10 @@
|
|
|
155
155
|
}
|
|
156
156
|
})();
|
|
157
157
|
</script>
|
|
158
|
-
<script type="module" crossorigin src="/assets/index-
|
|
158
|
+
<script type="module" crossorigin src="/assets/index-CZEb06G1.js"></script>
|
|
159
159
|
<link rel="modulepreload" crossorigin href="/assets/monaco-langs-BW2J83t5.js">
|
|
160
160
|
<link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
|
|
161
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
161
|
+
<link rel="stylesheet" crossorigin href="/assets/index-bwhJdsUd.css">
|
|
162
162
|
</head>
|
|
163
163
|
<body>
|
|
164
164
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-home-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
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": {
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"author": "Sebastian 'hobbyquaker' Raff <hobbyquaker@gmail.com>",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"bcryptjs": "^2.4.3",
|
|
32
31
|
"@elastic/elasticsearch": "^9.4.2",
|
|
33
32
|
"@influxdata/influxdb-client": "^1.35.0",
|
|
34
33
|
"@matter/main": "^0.17.0",
|
|
34
|
+
"bcryptjs": "^2.4.3",
|
|
35
35
|
"chokidar": "^4.0.0",
|
|
36
36
|
"express": "^5.2.1",
|
|
37
37
|
"ioredis": "^5.11.0",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"node-schedule": "^2.0.0",
|
|
40
40
|
"pino": "^9.0.0",
|
|
41
41
|
"pino-pretty": "^13.0.0",
|
|
42
|
+
"semantic-compare": "^1.0.2",
|
|
42
43
|
"suncalc": "^1.9.0",
|
|
43
44
|
"ws": "^8.21.0",
|
|
44
45
|
"yargs": "^17.0.0"
|
package/service/install.sh
CHANGED
|
@@ -12,7 +12,7 @@ SHE_USER=she
|
|
|
12
12
|
SERVICE_SRC="$(npm root -g)/smart-home-engine/service/smart-home-engine.service"
|
|
13
13
|
SERVICE_DST=/etc/systemd/system/smart-home-engine.service
|
|
14
14
|
|
|
15
|
-
# --- ensure sudo is installed (required for web UI restart
|
|
15
|
+
# --- ensure sudo is installed (required for web UI restart/update buttons) -
|
|
16
16
|
if ! command -v sudo &>/dev/null; then
|
|
17
17
|
echo "sudo not found, installing..."
|
|
18
18
|
apt-get install -y sudo
|
|
@@ -35,9 +35,13 @@ fi
|
|
|
35
35
|
# --- state directory (required by ReadWritePaths before first start) ------
|
|
36
36
|
install -d -o "$SHE_USER" -g "$SHE_USER" -m 700 /home/she/.she
|
|
37
37
|
|
|
38
|
-
# --- sudoers
|
|
38
|
+
# --- sudoers rules -------------------------------------------------------
|
|
39
|
+
NPM_BIN="$(command -v npm)"
|
|
39
40
|
SUDOERS_FILE=/etc/sudoers.d/she
|
|
40
|
-
|
|
41
|
+
cat > "$SUDOERS_FILE" <<EOF
|
|
42
|
+
she ALL=(root) NOPASSWD: /usr/bin/systemctl restart smart-home-engine
|
|
43
|
+
she ALL=(root) NOPASSWD: $NPM_BIN install -g smart-home-engine
|
|
44
|
+
EOF
|
|
41
45
|
chmod 440 "$SUDOERS_FILE"
|
|
42
46
|
echo "created $SUDOERS_FILE"
|
|
43
47
|
|
package/src/index.js
CHANGED
|
@@ -79,6 +79,7 @@ if (typeof config.port !== 'undefined') {
|
|
|
79
79
|
auth: config.auth,
|
|
80
80
|
password: config.password || null,
|
|
81
81
|
proxyHeader: config.proxyHeader,
|
|
82
|
+
proxyLogoutUrl: config.proxyLogoutUrl || null,
|
|
82
83
|
bindAddress: config.bindAddress,
|
|
83
84
|
configPath: config.config,
|
|
84
85
|
scriptDir: config.dir || null,
|
|
@@ -283,7 +284,13 @@ if (!config.url) {
|
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
if (config.url) {
|
|
286
|
-
|
|
287
|
+
const _mqttOpts = { will: { topic: config.name + '/connected', payload: '0', retain: true } };
|
|
288
|
+
if (config.mqttUsername) _mqttOpts.username = config.mqttUsername;
|
|
289
|
+
if (config.mqttPassword) _mqttOpts.password = config.mqttPassword;
|
|
290
|
+
if (config.mqttCa) _mqttOpts.ca = config.mqttCa;
|
|
291
|
+
if (config.mqttCert) _mqttOpts.cert = config.mqttCert;
|
|
292
|
+
if (config.mqttKey) _mqttOpts.key = config.mqttKey;
|
|
293
|
+
mqtt = modules.mqtt.connect(config.url, _mqttOpts);
|
|
287
294
|
mqtt.publish(config.name + '/connected', '2', { retain: true });
|
|
288
295
|
|
|
289
296
|
mqtt.on('connect', () => {
|
package/src/web/auth.js
CHANGED
|
@@ -32,15 +32,17 @@ const _sessions = new Map(); // token (hex64) → { createdAt: number }
|
|
|
32
32
|
let _mode = 'none';
|
|
33
33
|
let _passwordHash = null; // bcrypt hash, only used in 'password' mode
|
|
34
34
|
let _proxyHeader = 'x-remote-user'; // lowercase for req.headers lookup
|
|
35
|
+
let _proxyLogoutUrl = null; // URL to redirect to on logout in proxy mode
|
|
35
36
|
let _configPath = null;
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Initialise auth state. Called once from startServer().
|
|
39
40
|
*/
|
|
40
|
-
function init({ auth = 'none', password = null, proxyHeader = 'X-Remote-User', configPath = null } = {}) {
|
|
41
|
+
function init({ auth = 'none', password = null, proxyHeader = 'X-Remote-User', proxyLogoutUrl = null, configPath = null } = {}) {
|
|
41
42
|
_mode = auth;
|
|
42
43
|
_passwordHash = password || null;
|
|
43
44
|
_proxyHeader = proxyHeader.toLowerCase();
|
|
45
|
+
_proxyLogoutUrl = proxyLogoutUrl || null;
|
|
44
46
|
_configPath = configPath;
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -95,7 +97,9 @@ const router = express.Router();
|
|
|
95
97
|
|
|
96
98
|
/** GET /she/auth/mode — always public */
|
|
97
99
|
router.get('/mode', (req, res) => {
|
|
98
|
-
|
|
100
|
+
const r = { mode: _mode };
|
|
101
|
+
if (_mode === 'proxy' && _proxyLogoutUrl) r.proxyLogoutUrl = _proxyLogoutUrl;
|
|
102
|
+
res.json(r);
|
|
99
103
|
});
|
|
100
104
|
|
|
101
105
|
/** POST /she/auth/login — always public; only meaningful in password mode */
|
|
@@ -135,7 +139,7 @@ router.post('/setup', async (req, res) => {
|
|
|
135
139
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
const { mode, password, proxyHeader } = req.body || {};
|
|
142
|
+
const { mode, password, proxyHeader, proxyLogoutUrl } = req.body || {};
|
|
139
143
|
|
|
140
144
|
if (!['none', 'password', 'proxy'].includes(mode)) {
|
|
141
145
|
return res.status(400).json({ error: 'Invalid auth mode. Must be none, password, or proxy.' });
|
|
@@ -157,12 +161,14 @@ router.post('/setup', async (req, res) => {
|
|
|
157
161
|
cfg.auth = mode;
|
|
158
162
|
delete cfg.password;
|
|
159
163
|
delete cfg.proxyHeader;
|
|
164
|
+
delete cfg.proxyLogoutUrl;
|
|
160
165
|
|
|
161
166
|
if (mode === 'password') {
|
|
162
167
|
cfg.password = await bcrypt.hash(password, BCRYPT_ROUNDS);
|
|
163
168
|
}
|
|
164
169
|
if (mode === 'proxy') {
|
|
165
170
|
cfg.proxyHeader = proxyHeader || 'X-Remote-User';
|
|
171
|
+
if (proxyLogoutUrl) cfg.proxyLogoutUrl = proxyLogoutUrl;
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
// Write back
|
|
@@ -173,6 +179,7 @@ router.post('/setup', async (req, res) => {
|
|
|
173
179
|
_mode = mode;
|
|
174
180
|
_passwordHash = cfg.password || null;
|
|
175
181
|
_proxyHeader = (cfg.proxyHeader || 'X-Remote-User').toLowerCase();
|
|
182
|
+
_proxyLogoutUrl = cfg.proxyLogoutUrl || null;
|
|
176
183
|
|
|
177
184
|
// Invalidate all existing sessions when switching away from password mode
|
|
178
185
|
if (mode !== 'password') _sessions.clear();
|
package/src/web/server.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const express = require('express');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { spawnSync, spawn } = require('child_process');
|
|
6
|
+
const semverCompare = require('semantic-compare');
|
|
7
|
+
const pkg = require('../../package.json');
|
|
5
8
|
const { router: configRouter } = require('./config-api');
|
|
6
9
|
const { router: scriptsRouter } = require('./scripts-api');
|
|
7
10
|
const { router: shedbRouter } = require('./shedb-api');
|
|
@@ -56,19 +59,37 @@ app.use('/she/ai', aiRouter);
|
|
|
56
59
|
// When running under systemd, delegate to `sudo systemctl restart` so the
|
|
57
60
|
// service actually comes back up. Otherwise fall back to exit(0) and let
|
|
58
61
|
// whatever process manager is in use handle it.
|
|
62
|
+
function _systemdRestart() {
|
|
63
|
+
if (process.env.INVOCATION_ID) {
|
|
64
|
+
spawn('sudo', ['systemctl', 'restart', 'smart-home-engine'], { detached: true, stdio: 'ignore' }).unref();
|
|
65
|
+
} else {
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
app.post('/she/restart', (req, res) => {
|
|
71
|
+
res.json({ ok: true });
|
|
72
|
+
setTimeout(_systemdRestart, 200);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// npm version check — poll once on startup and every hour
|
|
76
|
+
let _latestNpmVersion = null;
|
|
77
|
+
async function _checkNpmVersion() {
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch('https://registry.npmjs.org/smart-home-engine/latest');
|
|
80
|
+
const data = await res.json();
|
|
81
|
+
_latestNpmVersion = (data.version && semverCompare(pkg.version, data.version) < 0) ? data.version : null;
|
|
82
|
+
} catch { /* best-effort */ }
|
|
83
|
+
}
|
|
84
|
+
_checkNpmVersion();
|
|
85
|
+
setInterval(_checkNpmVersion, 60 * 60 * 1000);
|
|
86
|
+
|
|
87
|
+
// Update — install latest npm package, then restart
|
|
88
|
+
app.post('/she/update', (req, res) => {
|
|
60
89
|
res.json({ ok: true });
|
|
61
90
|
setTimeout(() => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
require('child_process').spawn(
|
|
65
|
-
'sudo',
|
|
66
|
-
['systemctl', 'restart', 'smart-home-engine'],
|
|
67
|
-
{ detached: true, stdio: 'ignore' },
|
|
68
|
-
).unref();
|
|
69
|
-
} else {
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
91
|
+
spawnSync('sudo', ['npm', 'install', '-g', 'smart-home-engine'], { stdio: 'inherit' });
|
|
92
|
+
_systemdRestart();
|
|
72
93
|
}, 200);
|
|
73
94
|
});
|
|
74
95
|
|
|
@@ -78,7 +99,9 @@ function setStatsProvider(fn) {
|
|
|
78
99
|
_getStats = fn;
|
|
79
100
|
}
|
|
80
101
|
app.get('/she/status', (req, res) => {
|
|
81
|
-
|
|
102
|
+
const s = _getStats ? _getStats() : { scripts: 0, topics: 0 };
|
|
103
|
+
if (_latestNpmVersion) s.latestVersion = _latestNpmVersion;
|
|
104
|
+
res.json(s);
|
|
82
105
|
});
|
|
83
106
|
|
|
84
107
|
// Serve the built Svelte SPA from dist/web/
|
|
@@ -167,7 +190,7 @@ let httpServer = null;
|
|
|
167
190
|
/**
|
|
168
191
|
* Start listening. Resolves with the actual port (useful when port 0 is given).
|
|
169
192
|
* @param {number} port
|
|
170
|
-
* @param {{ auth?: string, password?: string, proxyHeader?: string, bindAddress?: string, configPath?: string, scriptDir?: string }} [options]
|
|
193
|
+
* @param {{ auth?: string, password?: string, proxyHeader?: string, proxyLogoutUrl?: string, bindAddress?: string, configPath?: string, scriptDir?: string }} [options]
|
|
171
194
|
* @returns {Promise<number>}
|
|
172
195
|
*/
|
|
173
196
|
function startServer(port, options = {}) {
|
|
@@ -175,6 +198,7 @@ function startServer(port, options = {}) {
|
|
|
175
198
|
auth: options.auth || 'none',
|
|
176
199
|
password: options.password || null,
|
|
177
200
|
proxyHeader: options.proxyHeader || 'X-Remote-User',
|
|
201
|
+
proxyLogoutUrl: options.proxyLogoutUrl || null,
|
|
178
202
|
configPath: options.configPath || null,
|
|
179
203
|
});
|
|
180
204
|
if (options.configPath) {
|