@phystack/device-phyos 4.5.64-dev → 5.0.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/bin/index.js +12 -8
- package/dist/index.js +491 -0
- package/package.json +17 -20
- package/LICENSE +0 -21
- package/README.md +0 -71
package/bin/index.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { spawnSync } = require(
|
|
3
|
-
const path = require(
|
|
2
|
+
const { spawnSync } = require("child_process");
|
|
3
|
+
const path = require("path");
|
|
4
4
|
|
|
5
|
-
const BINARY =
|
|
5
|
+
const BINARY = "phydevice";
|
|
6
6
|
const key = `${process.platform}-${process.arch}`;
|
|
7
7
|
const pkg = `@phystack/${BINARY}-${key}`;
|
|
8
8
|
|
|
9
9
|
let binPath;
|
|
10
10
|
try {
|
|
11
|
-
binPath = path.join(
|
|
11
|
+
binPath = path.join(
|
|
12
|
+
path.dirname(require.resolve(`${pkg}/package.json`)),
|
|
13
|
+
"bin",
|
|
14
|
+
BINARY,
|
|
15
|
+
);
|
|
12
16
|
} catch {
|
|
13
17
|
console.error(
|
|
14
18
|
`Unsupported or missing platform package: ${pkg}\n` +
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
`Platform: ${key}\n\n` +
|
|
20
|
+
`Install manually: npm install ${pkg}\n` +
|
|
21
|
+
`Or download: bunx ${pkg}`,
|
|
18
22
|
);
|
|
19
23
|
process.exit(1);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
const result = spawnSync(binPath, process.argv.slice(2), { stdio:
|
|
26
|
+
const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
23
27
|
process.exit(result.status ?? 1);
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const hub_device_1 = __importDefault(require("@phystack/hub-device"));
|
|
9
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
10
|
+
const edge_hub_1 = require("./edge-hub");
|
|
11
|
+
const instances_1 = require("./utilities/instances");
|
|
12
|
+
const methods_1 = require("./methods");
|
|
13
|
+
const scheduler_service_1 = require("./services/scheduler.service");
|
|
14
|
+
const time_sync_1 = __importDefault(require("./time-sync"));
|
|
15
|
+
const environment_1 = require("./methods/environment");
|
|
16
|
+
const ca_1 = require("./methods/network/ca");
|
|
17
|
+
const twin_manager_1 = require("./utilities/twin-manager");
|
|
18
|
+
const { connectToPhyHub } = hub_device_1.default;
|
|
19
|
+
const phyosEnvPath = '/etc/phyos.env';
|
|
20
|
+
const phyosVersionPath = '/etc/phyos.version';
|
|
21
|
+
const credentialsMigrationPath = '/data/migration.json';
|
|
22
|
+
const environmentFilePath = '/etc/environment';
|
|
23
|
+
const regionFilePath = '/data/settings/phyhub/region.json';
|
|
24
|
+
const customProxyFilePath = '/data/environment-custom-proxy';
|
|
25
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
26
|
+
logToFile: false,
|
|
27
|
+
logToConsole: true,
|
|
28
|
+
includeTrace: true,
|
|
29
|
+
namespace: 'device-phyos',
|
|
30
|
+
});
|
|
31
|
+
function sourceEnvironment() {
|
|
32
|
+
let gridEnv = 'PROD';
|
|
33
|
+
try {
|
|
34
|
+
const symlinkFixed = (0, environment_1.ensureEnvironmentSymlink)();
|
|
35
|
+
try {
|
|
36
|
+
if (fs_1.default.existsSync(phyosEnvPath)) {
|
|
37
|
+
const envContent = fs_1.default.readFileSync(phyosEnvPath, 'utf8').trim().toUpperCase();
|
|
38
|
+
if (envContent === 'DEV' || envContent === 'QA') {
|
|
39
|
+
gridEnv = envContent;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.warn('Error reading grid environment, defaulting to prod:', error);
|
|
45
|
+
}
|
|
46
|
+
if (symlinkFixed || !fs_1.default.existsSync(environmentFilePath)) {
|
|
47
|
+
(0, environment_1.setDefaultEnvironmentConfiguration)(gridEnv);
|
|
48
|
+
}
|
|
49
|
+
const envContent = fs_1.default.readFileSync(environmentFilePath, 'utf8');
|
|
50
|
+
const envLines = envContent.split('\n');
|
|
51
|
+
for (const line of envLines) {
|
|
52
|
+
if (line.trim() && !line.startsWith('#')) {
|
|
53
|
+
const [key, value] = line.split('=');
|
|
54
|
+
if (key && value) {
|
|
55
|
+
process.env[key.trim()] = value.trim().replace(/["']/g, '');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.error('Failed to read /etc/environment:', error);
|
|
62
|
+
try {
|
|
63
|
+
(0, environment_1.setDefaultEnvironmentConfiguration)(gridEnv);
|
|
64
|
+
}
|
|
65
|
+
catch (innerError) {
|
|
66
|
+
logger.error('Failed to set default proxy configuration:', innerError);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getSystemdJournalGid() {
|
|
71
|
+
try {
|
|
72
|
+
const groupFile = fs_1.default.readFileSync('/etc/group', 'utf8');
|
|
73
|
+
const groups = groupFile.split('\n');
|
|
74
|
+
const journalGroup = groups.find(line => line.startsWith('systemd-journal:'));
|
|
75
|
+
if (!journalGroup) {
|
|
76
|
+
throw new Error('systemd-journal group not found');
|
|
77
|
+
}
|
|
78
|
+
const gid = parseInt(journalGroup.split(':')[2]);
|
|
79
|
+
if (isNaN(gid)) {
|
|
80
|
+
throw new Error('Invalid GID for systemd-journal');
|
|
81
|
+
}
|
|
82
|
+
return gid;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error('Failed to get systemd-journal GID:', error);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function initializeFiles() {
|
|
90
|
+
const PROVISIONING_CODE_PATH = '/tmp/provisioning_code';
|
|
91
|
+
const LOG_PATH = '/data/log';
|
|
92
|
+
try {
|
|
93
|
+
if (!fs_1.default.existsSync(PROVISIONING_CODE_PATH)) {
|
|
94
|
+
fs_1.default.writeFileSync(PROVISIONING_CODE_PATH, '');
|
|
95
|
+
}
|
|
96
|
+
fs_1.default.chmodSync(PROVISIONING_CODE_PATH, 0o666);
|
|
97
|
+
if (!fs_1.default.existsSync(LOG_PATH)) {
|
|
98
|
+
fs_1.default.mkdirSync(LOG_PATH, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const journalGid = getSystemdJournalGid();
|
|
102
|
+
fs_1.default.chownSync(LOG_PATH, 0, journalGid);
|
|
103
|
+
fs_1.default.chmodSync(LOG_PATH, 0o2775);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
logger.error('Failed to set log directory permissions:', error);
|
|
107
|
+
setTimeout(() => process.exit(1), 1000);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logger.error('Failed to initialize required files:', error);
|
|
112
|
+
setTimeout(() => process.exit(1), 1000);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function initializeProxy() {
|
|
116
|
+
try {
|
|
117
|
+
let needsDefaultConfig = true;
|
|
118
|
+
if (fs_1.default.existsSync(environmentFilePath)) {
|
|
119
|
+
const envContent = fs_1.default.readFileSync(environmentFilePath, 'utf8');
|
|
120
|
+
const hasHttpProxy = envContent.includes('http_proxy=');
|
|
121
|
+
const hasHttpsProxy = envContent.includes('https_proxy=');
|
|
122
|
+
needsDefaultConfig = !hasHttpProxy && !hasHttpsProxy;
|
|
123
|
+
}
|
|
124
|
+
let gridEnv = 'PROD';
|
|
125
|
+
try {
|
|
126
|
+
if (fs_1.default.existsSync(phyosEnvPath)) {
|
|
127
|
+
const envContent = fs_1.default.readFileSync(phyosEnvPath, 'utf8').trim().toUpperCase();
|
|
128
|
+
if (envContent === 'DEV' || envContent === 'QA') {
|
|
129
|
+
gridEnv = envContent;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
logger.warn('initializeProxy(): Error reading grid environment, defaulting to prod:', error);
|
|
135
|
+
}
|
|
136
|
+
if (needsDefaultConfig) {
|
|
137
|
+
logger.info('Proxy settings not found, setting default configuration for environment:', gridEnv);
|
|
138
|
+
(0, environment_1.setDefaultEnvironmentConfiguration)(gridEnv);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
logger.info('Proxy settings already exist, skipping default configuration');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
logger.error('Failed to check proxy settings:', error);
|
|
146
|
+
try {
|
|
147
|
+
(0, environment_1.setDefaultEnvironmentConfiguration)('PROD');
|
|
148
|
+
}
|
|
149
|
+
catch (innerError) {
|
|
150
|
+
logger.error('Failed to set default proxy configuration:', innerError);
|
|
151
|
+
setTimeout(() => process.exit(1), 1000);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
initializeFiles();
|
|
157
|
+
initializeProxy();
|
|
158
|
+
sourceEnvironment();
|
|
159
|
+
(0, environment_1.checkProxySettings)();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
logger.error('Failed to initialize environment:', error);
|
|
163
|
+
setTimeout(() => process.exit(1), 1000);
|
|
164
|
+
}
|
|
165
|
+
class DevicePhyos {
|
|
166
|
+
GRID_ENV;
|
|
167
|
+
PHYOS_VERSION;
|
|
168
|
+
firstConnect;
|
|
169
|
+
phyHubInterval;
|
|
170
|
+
logger;
|
|
171
|
+
scheduler = new scheduler_service_1.Scheduler();
|
|
172
|
+
twinManager;
|
|
173
|
+
constructor() {
|
|
174
|
+
this.GRID_ENV = 'PROD';
|
|
175
|
+
this.PHYOS_VERSION = 'N/A';
|
|
176
|
+
this.firstConnect = false;
|
|
177
|
+
this.phyHubInterval = null;
|
|
178
|
+
this.logger = new phy_logger_1.PhyLogger({
|
|
179
|
+
logToFile: false,
|
|
180
|
+
logToConsole: true,
|
|
181
|
+
includeTrace: true,
|
|
182
|
+
namespace: 'device-phyos',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async initialize() {
|
|
186
|
+
await this.initializeEnvironment();
|
|
187
|
+
await this.initializeTimeSync();
|
|
188
|
+
this.logger.info(`Starting device phyos service 🟢-⚪-⚪-⚪-⚪`, {
|
|
189
|
+
GRID_ENV: this.GRID_ENV,
|
|
190
|
+
PHYOS_VERSION: this.PHYOS_VERSION
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async initializeEnvironment() {
|
|
194
|
+
try {
|
|
195
|
+
this.GRID_ENV = await fs_1.default.promises.readFile(phyosEnvPath, 'utf8').then(data => data.trim() || 'LOCAL');
|
|
196
|
+
this.logger.info('initializeEnvironment(): Setting grid environment', {
|
|
197
|
+
GRID_ENV: this.GRID_ENV,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
this.logger.error('initializeEnvironment(): Error reading grid environment', error);
|
|
202
|
+
this.GRID_ENV = 'PROD';
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
this.PHYOS_VERSION = await fs_1.default.promises.readFile(phyosVersionPath, 'utf8').then(data => data.trim());
|
|
206
|
+
this.logger.info('initializeEnvironment(): Setting PhyOS version', {
|
|
207
|
+
PHYOS_VERSION: this.PHYOS_VERSION
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
this.logger.error('initializeEnvironment(): Error reading PhyOS version', error);
|
|
212
|
+
this.PHYOS_VERSION = 'N/A';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async initializeTimeSync() {
|
|
216
|
+
try {
|
|
217
|
+
this.logger.info('initializeTimeSync(): Performing initial time sync');
|
|
218
|
+
await (0, time_sync_1.default)();
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
this.logger.error('initializeTimeSync(): Initial time sync failed:', error);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async initializeCachedInstances(twinManager) {
|
|
225
|
+
const twinsInfoCache = await twinManager.getCachedTwins();
|
|
226
|
+
console.log('initializeCachedInstances(): twinsInfoCache', twinsInfoCache);
|
|
227
|
+
if (!twinsInfoCache || typeof twinsInfoCache !== 'object') {
|
|
228
|
+
this.logger.info('initializeCachedInstances(): No cached twins found');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const allTwins = [];
|
|
232
|
+
for (const type of Object.keys(twinsInfoCache)) {
|
|
233
|
+
const twinIds = twinsInfoCache[type];
|
|
234
|
+
if (!Array.isArray(twinIds))
|
|
235
|
+
continue;
|
|
236
|
+
const twins = await Promise.all(twinIds.map(async (twinId) => {
|
|
237
|
+
const twin = await twinManager.getCachedTwins(twinId);
|
|
238
|
+
return twin;
|
|
239
|
+
}));
|
|
240
|
+
allTwins.push(...twins.filter(Boolean));
|
|
241
|
+
}
|
|
242
|
+
if (allTwins.length > 0) {
|
|
243
|
+
this.logger.info(`initializeCachedInstances(): Found ${allTwins.length} cached twin(s), initializing...`);
|
|
244
|
+
await (0, instances_1.initInstances)(this.twinManager, allTwins);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.logger.info('initializeCachedInstances(): No valid twins to initialize');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async connectPhyHub(hubDevice) {
|
|
251
|
+
this.twinManager = await (0, twin_manager_1.getTwinManagerInstance)();
|
|
252
|
+
this.twinManager.setHubDevice(hubDevice);
|
|
253
|
+
this.scheduler.setHubDevice(hubDevice);
|
|
254
|
+
hubDevice.setScheduler(this.scheduler);
|
|
255
|
+
hubDevice.onAuthReconnect(async () => {
|
|
256
|
+
this.logger.info('connectPhyHub(): Device authenticated, resubscribing twins...');
|
|
257
|
+
await edge_hub_1.EdgeHubServer.getInstance().resubscribeAllTwins();
|
|
258
|
+
try {
|
|
259
|
+
const cachedTwinsInfo = await this.twinManager.getCachedTwins();
|
|
260
|
+
if (cachedTwinsInfo && typeof cachedTwinsInfo === 'object' && 'Device' in cachedTwinsInfo && Array.isArray(cachedTwinsInfo.Device) && cachedTwinsInfo.Device.length > 0) {
|
|
261
|
+
const deviceTwinId = cachedTwinsInfo.Device[0];
|
|
262
|
+
const deviceTwin = await this.twinManager.getCachedTwins(deviceTwinId);
|
|
263
|
+
if (deviceTwin && deviceTwin.type === 'Device' && 'properties' in deviceTwin && deviceTwin.properties) {
|
|
264
|
+
const deviceProps = deviceTwin.properties;
|
|
265
|
+
const jobs = deviceProps.reported?.jobs || [];
|
|
266
|
+
const timezone = deviceProps.desired?.timezone || 'UTC';
|
|
267
|
+
this.logger.info(`connectPhyHub(): Re-processing ${jobs.length} jobs after reconnection`);
|
|
268
|
+
this.scheduler.processJobs(jobs, timezone, deviceTwinId);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logger.error('connectPhyHub(): Failed to re-process scheduler jobs after reconnection', error);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
await hubDevice.connect();
|
|
277
|
+
this.logger.info('connectPhyHub(): Connected to PhyHub');
|
|
278
|
+
hubDevice.connectDevice(async (response) => {
|
|
279
|
+
const { twins } = response;
|
|
280
|
+
console.log('connectPhyHub(): response', response);
|
|
281
|
+
const deviceTwin = twins?.find((twin) => twin.type === 'Device');
|
|
282
|
+
const deviceId = deviceTwin?.deviceId;
|
|
283
|
+
if (!deviceId) {
|
|
284
|
+
this.logger.warn('connectPhyHub(): Missing required device ID in response');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.logger.info(`connectPhyHub(): Device connected with details:`, { deviceId });
|
|
288
|
+
if (deviceId) {
|
|
289
|
+
let shouldUpdateEnvironment = false;
|
|
290
|
+
try {
|
|
291
|
+
if (fs_1.default.existsSync(environmentFilePath)) {
|
|
292
|
+
const envContent = fs_1.default.readFileSync(environmentFilePath, 'utf8');
|
|
293
|
+
shouldUpdateEnvironment = envContent.includes('unprovisioned:device@');
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
shouldUpdateEnvironment = true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
this.logger.warn('connectPhyHub(): Failed to read environment file:', error);
|
|
301
|
+
shouldUpdateEnvironment = true;
|
|
302
|
+
}
|
|
303
|
+
if (shouldUpdateEnvironment) {
|
|
304
|
+
try {
|
|
305
|
+
const proxyConfig = {
|
|
306
|
+
region: hubDevice.region,
|
|
307
|
+
port: 443,
|
|
308
|
+
username: deviceId,
|
|
309
|
+
password: 'device',
|
|
310
|
+
timezone: deviceTwin?.properties?.desired?.timezone || 'Etc/UTC'
|
|
311
|
+
};
|
|
312
|
+
this.logger.info('connectPhyHub(): Environment contains unprovisioned username, configuring with device credentials:', proxyConfig);
|
|
313
|
+
await (0, environment_1.setEnvironmentConfiguration)(proxyConfig);
|
|
314
|
+
this.logger.info('connectPhyHub(): Environment configuration completed successfully');
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
this.logger.error('connectPhyHub(): Failed to configure provisioned environment, continuing with unprovisioned environment:', error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
this.logger.info('connectPhyHub(): Environment already provisioned with device credentials, skipping configuration');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!this.firstConnect) {
|
|
325
|
+
this.firstConnect = true;
|
|
326
|
+
}
|
|
327
|
+
if (twins?.length) {
|
|
328
|
+
this.logger.info('connectPhyHub(): Caching twins from server response...');
|
|
329
|
+
await Promise.all(twins.map((twin) => this.twinManager.cacheHubTwin(twin)));
|
|
330
|
+
this.logger.info(`connectPhyHub(): Cached ${twins.length} twins from hub`);
|
|
331
|
+
const twinsByType = await this.twinManager.getCachedTwins();
|
|
332
|
+
const allTwinIds = Object.values(twinsByType).flat();
|
|
333
|
+
this.logger.info(`connectPhyHub(): Found ${allTwinIds.length} total twins after caching`);
|
|
334
|
+
const allTwins = await Promise.all(allTwinIds.map(id => this.twinManager.getCachedTwins(id)));
|
|
335
|
+
console.log(`connectPhyHub(): allTwins ${allTwins.length}`);
|
|
336
|
+
await (0, instances_1.initInstances)(this.twinManager, allTwins);
|
|
337
|
+
await (0, methods_1.initMethods)(hubDevice);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
await this.initializeCachedInstances(this.twinManager);
|
|
341
|
+
hubDevice.setTwinUpdateHandler(async (twin) => {
|
|
342
|
+
this.logger.info('connectPhyHub() on setTwinUpdateHandler(): Twin update received, updating instance...');
|
|
343
|
+
await this.twinManager.cacheHubTwin(twin);
|
|
344
|
+
if (twin.type === 'Device' && twin.properties?.desired?.timezone) {
|
|
345
|
+
try {
|
|
346
|
+
const region = JSON.parse(fs_1.default.readFileSync(regionFilePath, 'utf8'));
|
|
347
|
+
const proxyConfig = {
|
|
348
|
+
region,
|
|
349
|
+
port: 443,
|
|
350
|
+
username: twin.deviceId,
|
|
351
|
+
password: 'device',
|
|
352
|
+
timezone: twin.properties.desired.timezone
|
|
353
|
+
};
|
|
354
|
+
this.logger.info('connectPhyHub(): Updating environment with:', proxyConfig);
|
|
355
|
+
await (0, environment_1.setTZinEnvFile)(twin.properties.desired.timezone);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
this.logger.error('connectPhyHub(): Failed to update environment with new timezone:', error);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
await (0, instances_1.updateInstances)(this.twinManager, [
|
|
362
|
+
(await this.twinManager.getCachedTwins(twin.id)),
|
|
363
|
+
]);
|
|
364
|
+
});
|
|
365
|
+
hubDevice.setTwinRemoveHandler(async (twin) => {
|
|
366
|
+
this.logger.info('connectPhyHub() on setTwinRemoveHandler(): Twin remove event received, removing instance twin...', twin);
|
|
367
|
+
await (0, instances_1.removeInstances)(this.twinManager, [twin]);
|
|
368
|
+
});
|
|
369
|
+
hubDevice.on('setCACertificate', async (payload, callback) => {
|
|
370
|
+
try {
|
|
371
|
+
const config = payload.data;
|
|
372
|
+
const result = await (0, ca_1.configureCACertificate)(config);
|
|
373
|
+
callback && callback(null, { status: result ? 'success' : 'error' });
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error('Error configuring CA certificate:', error);
|
|
377
|
+
callback && callback(error);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
hubDevice.on('twinMessage', async (payload, callback) => {
|
|
381
|
+
await edge_hub_1.EdgeHubServer.getInstance().handleTwinMessage(payload, callback, true);
|
|
382
|
+
});
|
|
383
|
+
hubDevice.on('twinUpdated', async (payload, callback) => {
|
|
384
|
+
this.logger.info('connectPhyHub() on twinUpdated(): Twin update received, forwarding to edge-hub...');
|
|
385
|
+
await edge_hub_1.EdgeHubServer.getInstance().handleTwinUpdated(payload, callback, true);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
migrateDeviceCredentialsToPhyOS() {
|
|
389
|
+
this.logger.info('migrateDeviceCredentialsToPhyOS(): Checking if legacy GridOS/PhyOS device');
|
|
390
|
+
try {
|
|
391
|
+
if (fs_1.default.existsSync(customProxyFilePath)) {
|
|
392
|
+
this.logger.info('migrateDeviceCredentialsToPhyOS(): Custom proxy file found, migrating proxy settings');
|
|
393
|
+
(0, child_process_1.execSync)('sudo mv /data/environment-custom-proxy /data/settings/environment');
|
|
394
|
+
(0, child_process_1.execSync)('sudo rm /etc/environment');
|
|
395
|
+
(0, child_process_1.execSync)('sudo ln -sf /data/settings/environment /etc/environment');
|
|
396
|
+
process.exit(0);
|
|
397
|
+
}
|
|
398
|
+
if (fs_1.default.existsSync(credentialsMigrationPath)) {
|
|
399
|
+
const data = fs_1.default.readFileSync(credentialsMigrationPath, 'utf-8');
|
|
400
|
+
const credentials = JSON.parse(data);
|
|
401
|
+
if (credentials.deviceId && credentials.accessKey && credentials.deviceSerial && credentials.region) {
|
|
402
|
+
fs_1.default.unlinkSync(credentialsMigrationPath);
|
|
403
|
+
return credentials;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
this.logger.error(`migrateDeviceCredentialsToPhyOS(): Error reading file: ${error}`);
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
async getPhyHub() {
|
|
413
|
+
this.logger.info('getPhyHub(): Attempting to connect to PhyHub');
|
|
414
|
+
let credentials = undefined;
|
|
415
|
+
this.logger.info('getPhyHub(): Checking if migrating legacy device');
|
|
416
|
+
credentials = this.migrateDeviceCredentialsToPhyOS();
|
|
417
|
+
if (credentials && credentials.deviceId && credentials.accessKey && credentials.deviceSerial) {
|
|
418
|
+
this.logger.info('getPhyHub(): Migrating credentials for legacy device');
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
this.logger.info('getPhyHub(): Not a legacy device, attempting to provision device');
|
|
422
|
+
}
|
|
423
|
+
const connectionStatus = await connectToPhyHub(this.GRID_ENV, this.PHYOS_VERSION, credentials);
|
|
424
|
+
if (!connectionStatus) {
|
|
425
|
+
this.logger.warn('getPhyHub(): No connection status received from PhyHub');
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.logger.info('getPhyHub(): PhyHub connection details', { connectionStatus });
|
|
429
|
+
const { phyHub: phyHubInstance } = connectionStatus;
|
|
430
|
+
if (phyHubInstance) {
|
|
431
|
+
console.log('found phyHubInstance', { phyHubInstance });
|
|
432
|
+
if (this.phyHubInterval) {
|
|
433
|
+
clearInterval(this.phyHubInterval);
|
|
434
|
+
}
|
|
435
|
+
phyHubInstance.setScheduler(this.scheduler);
|
|
436
|
+
await this.connectPhyHub(phyHubInstance);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async start() {
|
|
440
|
+
try {
|
|
441
|
+
await this.initialize();
|
|
442
|
+
this.logger.info('start(): Verifying scheduler initialization');
|
|
443
|
+
if (!this.scheduler) {
|
|
444
|
+
this.scheduler = new scheduler_service_1.Scheduler();
|
|
445
|
+
this.logger.info('start(): Created new scheduler instance');
|
|
446
|
+
}
|
|
447
|
+
(0, edge_hub_1.startEdgeHub)().catch(error => {
|
|
448
|
+
this.logger.error('start(): Error starting edgeHub messaging', error);
|
|
449
|
+
});
|
|
450
|
+
this.phyHubInterval = setInterval(() => this.getPhyHub(), 30000);
|
|
451
|
+
await this.getPhyHub();
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
this.logger.error('start(): Initialization failed:', error);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
stop() {
|
|
459
|
+
if (this.phyHubInterval) {
|
|
460
|
+
clearInterval(this.phyHubInterval);
|
|
461
|
+
this.phyHubInterval = null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
exports.default = DevicePhyos;
|
|
466
|
+
const initializeDevicePhyos = async () => {
|
|
467
|
+
try {
|
|
468
|
+
const devicePhyos = new DevicePhyos();
|
|
469
|
+
await devicePhyos.start();
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
console.error('initializeDevicePhyos(): Failed to initialize device phyos', error);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const HEARTBEAT_PATH = '/tmp/phydevice-heartbeat';
|
|
477
|
+
setInterval(() => {
|
|
478
|
+
fs_1.default.writeFileSync(HEARTBEAT_PATH, new Date().toISOString());
|
|
479
|
+
}, 1000);
|
|
480
|
+
process.on('SIGTERM', async () => {
|
|
481
|
+
console.log('Received SIGTERM, exiting');
|
|
482
|
+
try {
|
|
483
|
+
fs_1.default.unlinkSync(HEARTBEAT_PATH);
|
|
484
|
+
}
|
|
485
|
+
catch (e) {
|
|
486
|
+
console.error(e.toString());
|
|
487
|
+
}
|
|
488
|
+
process.exit(1);
|
|
489
|
+
});
|
|
490
|
+
initializeDevicePhyos();
|
|
491
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phystack/device-phyos",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
3
|
+
"version": "5.0.1",
|
|
4
|
+
"description": "PhyOS device agent",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"license": "UNLICENSED",
|
|
5
7
|
"publishConfig": {
|
|
6
8
|
"access": "public"
|
|
7
9
|
},
|
|
8
|
-
"scripts": {
|
|
9
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
10
|
-
"build": "rimraf dist && tsc",
|
|
11
|
-
"build:binary": "VERSION=$(node -p \"require('./package.json').version\") && npx bun build --compile --minify --define \"__PKG_VERSION__=\\\"$VERSION\\\"\" ./src/index.ts --outfile phydevice",
|
|
12
|
-
"start": "yarn build && node --inspect --enable-source-maps dist/index",
|
|
13
|
-
"dev": "NODE_ENV=development npx nodemon",
|
|
14
|
-
"format": "prettier --write \"src/**/*.{ts,js,json,md}\"",
|
|
15
|
-
"format:check": "prettier --check \"src/**/*.{ts,js,json,md}\""
|
|
16
|
-
},
|
|
17
10
|
"bin": {
|
|
18
11
|
"phydevice": "./bin/index.js"
|
|
19
12
|
},
|
|
@@ -23,14 +16,18 @@
|
|
|
23
16
|
"engines": {
|
|
24
17
|
"node": ">=20.0.0"
|
|
25
18
|
},
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"@phystack/phydevice-linux-x64": "4.5.64-dev"
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"build:binary": "VERSION=$(bun -e \"console.log(require('./package.json').version)\") && bun build --compile --minify --external cpu-features --define \"__PKG_VERSION__=\\\"$VERSION\\\"\" ./src/index.ts --outfile phydevice",
|
|
22
|
+
"dev": "bun --watch src/index.ts",
|
|
23
|
+
"lint": "tsc --noEmit",
|
|
24
|
+
"test:ci": "echo 'no tests' && exit 0",
|
|
25
|
+
"format": "prettier --write \"src/**/*.{ts,js,json,md}\""
|
|
34
26
|
},
|
|
35
|
-
"
|
|
27
|
+
"optionalDependencies": {
|
|
28
|
+
"@phystack/phydevice-darwin-arm64": "5.0.1",
|
|
29
|
+
"@phystack/phydevice-darwin-x64": "5.0.1",
|
|
30
|
+
"@phystack/phydevice-linux-arm64": "5.0.1",
|
|
31
|
+
"@phystack/phydevice-linux-x64": "5.0.1"
|
|
32
|
+
}
|
|
36
33
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 PhyStack.com
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.md
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# phydevice
|
|
2
|
-
|
|
3
|
-
PhyStack device agent for PhyOS. Compiled as a standalone Bun binary.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
### Via npm (recommended)
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -g @phystack/device-phyos
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
npm automatically downloads the correct binary for your platform.
|
|
14
|
-
|
|
15
|
-
### Direct binary download (no npm required)
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
VERSION=4.5.62-dev # replace with desired version
|
|
19
|
-
|
|
20
|
-
# Linux ARM64
|
|
21
|
-
sudo curl -sL "https://registry.npmjs.org/@phystack/phydevice-linux-arm64/-/phydevice-linux-arm64-${VERSION}.tgz" | sudo tar xz --strip-components=2 -C /usr/bin package/bin/phydevice
|
|
22
|
-
|
|
23
|
-
# Linux x86_64
|
|
24
|
-
sudo curl -sL "https://registry.npmjs.org/@phystack/phydevice-linux-x64/-/phydevice-linux-x64-${VERSION}.tgz" | sudo tar xz --strip-components=2 -C /usr/bin package/bin/phydevice
|
|
25
|
-
|
|
26
|
-
# macOS Apple Silicon
|
|
27
|
-
curl -sL "https://registry.npmjs.org/@phystack/phydevice-darwin-arm64/-/phydevice-darwin-arm64-${VERSION}.tgz" | tar xz --strip-components=2 -C /usr/local/bin package/bin/phydevice
|
|
28
|
-
|
|
29
|
-
# macOS Intel
|
|
30
|
-
curl -sL "https://registry.npmjs.org/@phystack/phydevice-darwin-x64/-/phydevice-darwin-x64-${VERSION}.tgz" | tar xz --strip-components=2 -C /usr/local/bin package/bin/phydevice
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
To check available versions:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm view @phystack/phydevice-linux-arm64 versions --json
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Supported platforms
|
|
40
|
-
|
|
41
|
-
| Platform | Architecture | npm package |
|
|
42
|
-
|----------|-------------|-------------|
|
|
43
|
-
| Linux | ARM64 | `@phystack/phydevice-linux-arm64` |
|
|
44
|
-
| Linux | x86_64 | `@phystack/phydevice-linux-x64` |
|
|
45
|
-
| macOS | ARM64 | `@phystack/phydevice-darwin-arm64` |
|
|
46
|
-
| macOS | x86_64 | `@phystack/phydevice-darwin-x64` |
|
|
47
|
-
|
|
48
|
-
## Building from source
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
yarn build:binary # compile for current platform
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Or use the central build script for cross-compilation:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
# from repo root
|
|
58
|
-
yarn build:binaries # current platform
|
|
59
|
-
yarn build:binaries:cross # all platforms
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Output: `dist-binaries/<platform>/phydevice`
|
|
63
|
-
|
|
64
|
-
## Local development
|
|
65
|
-
|
|
66
|
-
Ensure the process can write to `/etc/phyos.env`. On macOS:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
sudo sh -c 'echo LOCAL > /etc/phyos.env'
|
|
70
|
-
sudo chmod 666 /etc/phyos.env
|
|
71
|
-
```
|