Compare commits
24 Commits
580513997d
...
#17-lintin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
911142a8aa | ||
| 0ff11589e9 | |||
| a2abe7e71d | |||
| b23a0768e3 | |||
| 27516a0a25 | |||
|
|
bc0fc23fb0 | ||
|
|
821e4f6a64 | ||
| 0f5ee3b3fb | |||
|
|
54aca6e401 | ||
|
|
851c0cfc47 | ||
|
|
e4ad4f412c | ||
|
|
e6181d51aa | ||
|
|
f706ac89b4 | ||
|
|
e54c80a95b | ||
|
|
c240007c2b | ||
|
|
aa4de9f326 | ||
|
|
1de895c973 | ||
|
|
d773323beb | ||
|
|
5d32a5131a | ||
|
|
c51190f6b6 | ||
|
|
3e7b387092 | ||
|
|
7fb67f6ddf | ||
|
|
3950d57b7f | ||
|
|
15b0650550 |
47
client.js
47
client.js
@@ -1,47 +0,0 @@
|
||||
import { generateUniqueID } from './modules/baseUtils.mjs';
|
||||
import { updateId } from './modules/updateConfig.mjs';
|
||||
import { ClientNodeConfig } from './modules/clientObjectDefinitions.mjs';
|
||||
import { initSocketConnection } from './modules/socketClient.mjs';
|
||||
import { checkForUpdates } from './modules/selfUpdater.mjs'
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
var localNodeConfig = new ClientNodeConfig({})
|
||||
|
||||
async function boot() {
|
||||
// Check if there have been any updates
|
||||
await checkForUpdates();
|
||||
|
||||
if (localNodeConfig.node.nuid === undefined || localNodeConfig.node.nuid === '' || localNodeConfig.node.nuid === '0' || localNodeConfig.node.nuid === 0) {
|
||||
// Run the first time boot sequence
|
||||
await firstTimeBoot();
|
||||
}
|
||||
|
||||
// Initialize the socket connection with the server
|
||||
return initSocketConnection(localNodeConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the first time the client boots on a pc
|
||||
* @returns {any}
|
||||
*/
|
||||
async function firstTimeBoot() {
|
||||
// Generate a new ID for the node
|
||||
localNodeConfig.node.nuid = await generateUniqueID();
|
||||
console.log(`Generated a new unique ID for this node: '${localNodeConfig.node.nuid}'`);
|
||||
|
||||
// Update the config with the new ID
|
||||
await updateId(localNodeConfig.node.nuid);
|
||||
console.log("Updated the config with the new node ID");
|
||||
// TODO - Create the config file with the ID given and replace the update above
|
||||
// TODO - Implement web server so users can update radio systems easily
|
||||
// TODO - Implement logic to check if the presets are set
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Boot the client application
|
||||
boot().then((openSocket) => {
|
||||
console.log(openSocket, "Booted Sucessfully");
|
||||
})
|
||||
28
gitea/workflows/DRBv3_linting.yaml
Normal file
28
gitea/workflows/DRBv3_linting.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Lint JavaScript/Node.js
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
lint-js:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22' # Use your preferred Node.js version
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Lint JavaScript/Node.js
|
||||
run: npm run lint
|
||||
@@ -1,57 +0,0 @@
|
||||
import simpleGit from 'simple-git';
|
||||
import { restartService } from './serviceHandler.mjs'
|
||||
import { launchProcess } from './subprocessHandler.mjs'
|
||||
|
||||
const git = simpleGit();
|
||||
|
||||
// Function to check for updates
|
||||
export const checkForUpdates = async () => {
|
||||
try {
|
||||
// Fetch remote changes
|
||||
await git.fetch();
|
||||
|
||||
// Get the latest commit hash
|
||||
const latestCommitHash = await git.revparse(['@{u}']);
|
||||
|
||||
// Compare with the local commit hash
|
||||
const localCommitHash = await git.revparse(['HEAD']);
|
||||
|
||||
if (latestCommitHash !== localCommitHash) {
|
||||
console.log('An update is available. Updating...');
|
||||
|
||||
// Check if there have been any changes to the code
|
||||
const gitStatus = await git.status()
|
||||
console.log(gitStatus);
|
||||
if (gitStatus.modified.length > 0){
|
||||
// There is locally modified code
|
||||
console.log("There is locally modified code, resetting...");
|
||||
await git.stash();
|
||||
await git.reset('hard', ['origin/master']);
|
||||
}
|
||||
|
||||
// Pull the latest changes from the remote repository
|
||||
await git.pull();
|
||||
|
||||
// Run the post-update script
|
||||
console.log('Running post-update script...');
|
||||
await launchProcess("bash", ['./post-update.sh'], true);
|
||||
|
||||
// Restart the application to apply the updates
|
||||
console.log('Update completed successfully. Restarting the application...');
|
||||
restartApplication();
|
||||
|
||||
return true
|
||||
} else {
|
||||
console.log('The application is up to date.');
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to restart the application
|
||||
const restartApplication = () => {
|
||||
console.log('Restarting the application...');
|
||||
restartService('discord-radio-bot');
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { P25ConfigGenerator, NBFMConfigGenerator } from './modules/op25ConfigGenerators.mjs';
|
||||
import { getAllPresets } from '../modules/radioPresetHandler.mjs';
|
||||
import { startService, stopService } from '../modules/serviceHandler.mjs';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
let currentSystem = undefined;
|
||||
|
||||
/**
|
||||
* Creates configuration based on the preset and restarts the OP25 service.
|
||||
* @param {Object} preset The preset object containing system configuration.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const createConfigAndRestartService = async (systemName, preset) => {
|
||||
const { mode, frequencies, trunkFile, whitelistFile } = preset;
|
||||
|
||||
let generator;
|
||||
if (mode === 'p25') {
|
||||
console.log("Using P25 Config Generator based on preset mode", systemName, mode);
|
||||
generator = new P25ConfigGenerator({
|
||||
systemName,
|
||||
controlChannels: frequencies,
|
||||
tagsFile: trunkFile,
|
||||
whitelistFile: whitelistFile !== 'none' ? whitelistFile : undefined
|
||||
});
|
||||
} else if (mode === 'nbfm') {
|
||||
console.log("Using NBFM Config Generator based on preset mode", systemName, mode);
|
||||
generator = new NBFMConfigGenerator({
|
||||
systemName,
|
||||
frequencies,
|
||||
tagsFile: trunkFile
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unsupported mode: ${mode}`);
|
||||
}
|
||||
|
||||
const op25FilePath = process.env.OP25_FULL_PATH || './'; // Default to current directory if OP25_FULL_PATH is not set
|
||||
const op25ConfigPath = `${op25FilePath}${op25FilePath.endsWith('/') ? 'active.cfg.json' : '/active.cfg.json'}`;
|
||||
await generator.exportToFile(op25ConfigPath);
|
||||
|
||||
// Restart the service
|
||||
await stopService('op25-multi_rx');
|
||||
await startService('op25-multi_rx');
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the OP25 service for the specified system.
|
||||
* @param {string} systemName The name of the system to open.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const openOP25 = async (systemName) => {
|
||||
currentSystem = systemName;
|
||||
|
||||
// Retrieve preset for the specified system name
|
||||
const presets = await getAllPresets();
|
||||
const preset = presets[systemName];
|
||||
|
||||
console.log("Found preset:", preset);
|
||||
|
||||
if (!preset) {
|
||||
throw new Error(`Preset for system "${systemName}" not found.`);
|
||||
}
|
||||
|
||||
await createConfigAndRestartService(systemName, preset);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Closes the OP25 service.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const closeOP25 = async () => {
|
||||
currentSystem = undefined;
|
||||
await stopService('op25-multi_rx');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the current system.
|
||||
* @returns {Promise<string | undefined>} The name of the current system.
|
||||
*/
|
||||
export const getCurrentSystem = async () => {
|
||||
return currentSystem;
|
||||
};
|
||||
110
package-lock.json
generated
110
package-lock.json
generated
@@ -10,10 +10,10 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.16.1",
|
||||
"chai-http": "^4.4.0",
|
||||
"convert-units": "^2.3.4",
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"libsodium-wrappers": "^0.7.13",
|
||||
"prism-media": "^1.3.5",
|
||||
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^5.1.0",
|
||||
"chai-http": "^4.4.0",
|
||||
"mocha": "^10.4.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
@@ -240,7 +241,8 @@
|
||||
"node_modules/@types/chai": {
|
||||
"version": "4.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz",
|
||||
"integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w=="
|
||||
"integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
@@ -250,7 +252,8 @@
|
||||
"node_modules/@types/cookiejar": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
||||
"integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q=="
|
||||
"integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.17",
|
||||
@@ -272,6 +275,7 @@
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz",
|
||||
"integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/cookiejar": "*",
|
||||
"@types/node": "*"
|
||||
@@ -364,7 +368,8 @@
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
@@ -375,10 +380,16 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
@@ -525,6 +536,7 @@
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.4.0.tgz",
|
||||
"integrity": "sha512-uswN3rZpawlRaa5NiDUHcDZ3v2dw5QgLyAwnQ2tnVNuP7CwIsOFuYJ0xR1WiR7ymD4roBnJIzOUep7w9jQMFJA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "4",
|
||||
"@types/superagent": "4.1.13",
|
||||
@@ -543,6 +555,7 @@
|
||||
"version": "6.12.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
|
||||
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
@@ -572,6 +585,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz",
|
||||
"integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
@@ -645,6 +659,7 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -656,10 +671,16 @@
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@@ -704,7 +725,8 @@
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
@@ -775,6 +797,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -800,6 +823,7 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
@@ -887,6 +911,20 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
"bin": {
|
||||
"ejs": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -1062,7 +1100,16 @@
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
@@ -1135,6 +1182,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
@@ -1148,6 +1196,7 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
@@ -1342,6 +1391,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1390,6 +1440,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
|
||||
"integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -1447,6 +1498,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz",
|
||||
"integrity": "sha512-9MTn0dteHETtyUx8pxqMwg5hMBi3pvlyglJ+b79KOCca0po23337LbVV2Hl4xmMvfw++ljnO0/+5G6G+0Szh6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ip-regex": "^2.0.0"
|
||||
},
|
||||
@@ -1484,6 +1536,43 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
|
||||
"integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
"filelist": "^1.0.4",
|
||||
"minimatch": "^3.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"jake": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jake/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jake/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
@@ -1717,6 +1806,7 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@@ -2173,6 +2263,7 @@
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -2400,6 +2491,7 @@
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
|
||||
"integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
|
||||
"deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
@@ -2420,6 +2512,7 @@
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
@@ -2599,7 +2692,8 @@
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
|
||||
24
package.json
24
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "drb-client",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "client.js",
|
||||
"main": "src/client.js",
|
||||
"scripts": {
|
||||
"test": "mocha --timeout 10000"
|
||||
},
|
||||
@@ -11,22 +11,24 @@
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.16.1",
|
||||
"@discordjs/voice": "^0.17.0",
|
||||
"axios": "^1.7.7",
|
||||
"convert-units": "^2.3.4",
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"discord.js": "^14.15.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"libsodium-wrappers": "^0.7.13",
|
||||
"prism-media": "^1.3.5",
|
||||
"replace-in-file": "^7.1.0",
|
||||
"simple-git": "^3.22.0",
|
||||
"replace-in-file": "^7.2.0",
|
||||
"simple-git": "^3.25.0",
|
||||
"socket.io": "^4.7.5",
|
||||
"socket.io-client": "^4.7.2"
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai-http": "^4.4.0",
|
||||
"chai": "^5.1.0",
|
||||
"chai": "^5.1.1",
|
||||
"chai-http": "^5.0.0",
|
||||
"mocha": "^10.4.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
142
setup.sh
142
setup.sh
@@ -22,70 +22,6 @@ if ! id "pi" &>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
####------------------- Functions
|
||||
# Function to prompt user for input with a specific message and store the result in a variable
|
||||
prompt_user() {
|
||||
if [[ "$TEST_MODE" == "true" ]]; then
|
||||
echo "TESTING" # Use the pre-set value
|
||||
else
|
||||
read -p "$1: " input
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to prompt user for capabilities options and store the result in a variable
|
||||
prompt_capabilities() {
|
||||
if [[ "$TEST_MODE" == "true" ]]; then
|
||||
echo "radio" # Use the pre-set value
|
||||
else
|
||||
default_capabilities="radio" # Default value
|
||||
read -p "Select CLIENT_CAPABILITIES (comma-separated, default: $default_capabilities): " capabilities
|
||||
capabilities="${capabilities:-$default_capabilities}" # Use default value if input is empty
|
||||
echo "$capabilities"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to prompt user for nearby systems details
|
||||
prompt_nearby_system() {
|
||||
if [[ "$TEST_MODE" == "true" ]]; then
|
||||
echo "\"TESTING-Node\": {
|
||||
\"frequencies\": [\"$(echo "155750000,154750000,156555550" | sed 's/,/","/g')\"],
|
||||
\"mode\": \"p25\",
|
||||
\"trunkFile\": \"testing_trunk.tsv\",
|
||||
\"whitelistFile\": \"testing_whitelist.tsv\"
|
||||
}," # Use the pre-set value
|
||||
else
|
||||
local system_name=""
|
||||
local frequencies=""
|
||||
local mode=""
|
||||
local trunk_file=""
|
||||
local whitelist_file=""
|
||||
|
||||
read -p "Enter system name: " system_name
|
||||
read -p "Enter frequencies (comma-separated): " frequencies
|
||||
read -p "Enter mode (p25/nbfm): " mode
|
||||
|
||||
if [[ "$mode" == "p25" ]]; then
|
||||
read -p "Enter trunk file: " trunk_file
|
||||
read -p "Enter whitelist file: " whitelist_file
|
||||
fi
|
||||
|
||||
echo "\"$system_name\": {
|
||||
\"frequencies\": [\"$(echo "$frequencies" | sed 's/,/","/g')\"],
|
||||
\"mode\": \"$mode\",
|
||||
\"trunkFile\": \"$trunk_file\",
|
||||
\"whitelistFile\": \"$whitelist_file\"
|
||||
},"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if test mode is enabled
|
||||
if [[ "$1" == "--test" ]]; then
|
||||
TEST_MODE="true"
|
||||
else
|
||||
TEST_MODE="false"
|
||||
fi
|
||||
|
||||
# Install Node Repo
|
||||
# Get the CPU architecture
|
||||
cpu_arch=$(uname -m)
|
||||
@@ -119,7 +55,7 @@ apt install -y \
|
||||
git \
|
||||
ffmpeg \
|
||||
python3 \
|
||||
python3-pip
|
||||
python3-pip
|
||||
|
||||
echo "Setting up Pulse Audio"
|
||||
|
||||
@@ -158,63 +94,10 @@ rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED
|
||||
|
||||
# Getting the Python DAB
|
||||
echo "Installing PDAB and Dependencies"
|
||||
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./discordAudioBot/pdab
|
||||
pip3 install -r ./discordAudioBot/pdab/requirements.txt
|
||||
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./pdab
|
||||
pip3 install -r ./pdab/requirements.txt
|
||||
|
||||
# Generate .env file
|
||||
echo "Creating the config .env file..."
|
||||
echo "# Client Config" > .env
|
||||
echo "CLIENT_NUID=0" >> .env
|
||||
client_name=$(prompt_user "Enter the name for this node")
|
||||
echo "CLIENT_NAME=$client_name" >> .env
|
||||
client_location=$(prompt_user "Enter the location of this node")
|
||||
echo "CLIENT_LOCATION=$client_location" >> .env
|
||||
client_capabilities=$(prompt_capabilities)
|
||||
echo "CLIENT_CAPABILITIES=$client_capabilities" >> .env
|
||||
|
||||
# Server configuration (preset values)
|
||||
echo "" >> .env
|
||||
echo "# Configuration for the connection to the server" >> .env
|
||||
echo "SERVER_IP=vpn.cusano.net" >> .env
|
||||
echo "SERVER_PORT=3000" >> .env
|
||||
|
||||
# OP25 configuration (preset values)
|
||||
echo "" >> .env
|
||||
echo "# Configuration for OP25" >> .env
|
||||
op25_full_path="$(pwd)/op25/op25/gr-op25_repeater/apps" # Update this with the actual path
|
||||
echo "OP25_FULL_PATH=$op25_full_path" >> .env
|
||||
|
||||
# Core configuration (preset value)
|
||||
echo "" >> .env
|
||||
echo "# Core config, DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING" >> .env
|
||||
echo "CONFIG_PATH=./config/radioPresets.json" >> .env
|
||||
runuser -l pi -c 'python3 ./discordAudioBot/pdab/getDevices.py'
|
||||
audio_device_id=$(prompt_user "Enter the ID of the 'input' audio device you would like to use (most often 'default')")
|
||||
echo "AUDIO_DEVICE_ID=$audio_device_id" >> .env
|
||||
echo "PDAB_PORT=3110" >> .env
|
||||
echo "NODE_ENV=production" >> .env
|
||||
|
||||
echo ".env file generated successfully."
|
||||
|
||||
# Create a JSON object to store nearby systems
|
||||
systems_json="{"
|
||||
while true; do
|
||||
systems_json+="$(prompt_nearby_system)"
|
||||
read -p "Do you want to add another system? (yes/no): " choice
|
||||
if [[ "$choice" != "yes" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
systems_json="${systems_json%,}" # Remove trailing comma
|
||||
systems_json+="}"
|
||||
|
||||
# Append the created systems to the presets file
|
||||
mkdir -p ./config
|
||||
echo "$systems_json" >> "./config/radioPresets.json"
|
||||
|
||||
echo "Systems added to radioPresets.json."
|
||||
|
||||
# Create a systemd service file
|
||||
# Create a systemd service file for the DRB Client
|
||||
echo "Adding DRB Node service..."
|
||||
service_content="[Unit]
|
||||
Description=Discord-Radio-Bot_v3
|
||||
@@ -239,7 +122,7 @@ echo "$service_content" > /etc/systemd/system/discord-radio-bot.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable discord-radio-bot.service
|
||||
|
||||
echo "\n\n\t\tDiscord Client Node install completed!\n\n"
|
||||
echo "\n\n\t\tDRB Client install completed!\n\n"
|
||||
|
||||
####------------------- OP25 Installation
|
||||
# Clone OP25 from the git repository
|
||||
@@ -290,7 +173,6 @@ echo "\n\n\t\tOP25 installation completed!\n\n"
|
||||
# Setting permissions on the directories created
|
||||
cd $ogPwd
|
||||
chown -R 1000:1000 ./*
|
||||
chown 1000:1000 .env
|
||||
echo "Permissions set on the client directory!"
|
||||
|
||||
echo "\n\n\t\tNode installation Complete!"
|
||||
@@ -301,14 +183,10 @@ read -p "This script has installed all required components for the DRB client. A
|
||||
# Convert user input to lowercase for case-insensitive comparison
|
||||
confirm="${confirm,,}"
|
||||
|
||||
#echo "To configure the app, please go to http://$nodeIP:$nodePort" # TODO - uncomment when webapp is built
|
||||
echo "To configure the app, please go to http://localhost:3000 after the reboot to configure this app"
|
||||
echo "Thank you for joining the network!"
|
||||
|
||||
if [[ "$confirm" == "y" && "$confirm" == "yes" ]]; then
|
||||
# Prompt user to press any key before rebooting
|
||||
read -rsp $'System will now reboot, press any key to continue or Ctrl+C to cancel...\n' -n1 key
|
||||
echo "Rebooting..."
|
||||
reboot
|
||||
else
|
||||
echo "Please restart your device to complete the installation"
|
||||
fi
|
||||
# Prompt user to press any key before rebooting
|
||||
read -rsp $'System will now reboot, press any key to continue or Ctrl+C to cancel...\n' -n1 key
|
||||
echo "Rebooting..."
|
||||
reboot
|
||||
37
src/client.js
Normal file
37
src/client.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { DebugBuilder } from "./modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "client");
|
||||
|
||||
import { ClientNodeConfig } from './modules/clientObjectDefinitions.mjs';
|
||||
import { initSocketConnection } from './modules/socketClient.mjs';
|
||||
import { checkForUpdates } from './modules/selfUpdater.mjs'
|
||||
import startServer from './express/server.mjs';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
var localNodeConfig = new ClientNodeConfig({})
|
||||
|
||||
async function boot() {
|
||||
// Check if there have been any updates
|
||||
await checkForUpdates();
|
||||
|
||||
if (localNodeConfig.node.nuid === undefined || localNodeConfig.node.nuid === '' || localNodeConfig.node.nuid === '0' || localNodeConfig.node.nuid === 0) {
|
||||
return new Promise(res => {res(false)});
|
||||
}
|
||||
|
||||
// Initialize the socket connection with the server
|
||||
return initSocketConnection(localNodeConfig);
|
||||
}
|
||||
|
||||
// Boot the client application
|
||||
boot().then((openSocket) => {
|
||||
// Start the web server
|
||||
startServer(process.env.WEB_SERVER_PORT || 3000, openSocket);
|
||||
|
||||
if (!openSocket) {
|
||||
log.INFO(openSocket, "Waiting for setup");
|
||||
}
|
||||
else {
|
||||
log.INFO(openSocket, "Booted Sucessfully");
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,6 @@
|
||||
// server.js
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "pdabHandler.mjs");
|
||||
import express from 'express';
|
||||
import http from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
@@ -16,35 +18,34 @@ let pdabProcess = false;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const port = process.env.PDAB_PORT || 3000;
|
||||
|
||||
let botCallback;
|
||||
|
||||
export const initDiscordBotClient = (clientId, callback, runPDAB = true) => {
|
||||
botCallback = callback;
|
||||
|
||||
if (runPDAB) launchProcess("python", [join(__dirname, "./pdab/main.py"), process.env.AUDIO_DEVICE_ID, clientId, port], false, join(__dirname, "./pdab"));
|
||||
if (runPDAB) launchProcess("python", [join(__dirname, "../../pdab/main.py"), process.env.AUDIO_DEVICE_ID, clientId, port], false, false, join(__dirname, "../../pdab"));
|
||||
pdabProcess = true; // TODO - Make this more dynamic
|
||||
}
|
||||
|
||||
export const startPdabSocketServer = () => {
|
||||
const port = process.env.PDAB_PORT || 3000;
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('A user connected');
|
||||
log.INFO('A user connected');
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected');
|
||||
log.INFO('User disconnected');
|
||||
});
|
||||
|
||||
// Listen for the discord client ready event
|
||||
socket.on('discord_ready', (message) => {
|
||||
console.log("Message from local client", message);
|
||||
log.INFO("Message from local client", message);
|
||||
botCallback();
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, async () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
log.INFO(`Server is running on port ${port}`);
|
||||
});
|
||||
return
|
||||
}
|
||||
@@ -62,7 +63,7 @@ export const closePdabSocketServer = () => {
|
||||
export const connectToChannel = (channelId) => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('join_server', { channelId: channelId }, (status, value) => {
|
||||
console.log("Status returned from bot:", status, value);
|
||||
log.INFO("Status returned from bot:", status, value);
|
||||
res(value[0]);
|
||||
});
|
||||
});
|
||||
@@ -72,7 +73,7 @@ export const connectToChannel = (channelId) => {
|
||||
export const leaveVoiceChannel = async (guildId) => {
|
||||
return await new Promise((res) => {
|
||||
io.timeout(25000).emit('leave_server', { guildId: guildId }, (status, clientRemainsOpen) => {
|
||||
console.log("Discord client remains open?", clientRemainsOpen);
|
||||
log.INFO("Discord client remains open?", clientRemainsOpen);
|
||||
res(clientRemainsOpen[0])
|
||||
});
|
||||
});
|
||||
@@ -89,13 +90,13 @@ export const setDiscordClientPrsense = (system) => {
|
||||
|
||||
// Placeholder functions (replace with actual implementation)
|
||||
export const checkIfConnectedToVC = async (guildId) => {
|
||||
console.log("Pdab process var:", pdabProcess);
|
||||
log.INFO("Pdab process var:", pdabProcess);
|
||||
|
||||
if (!pdabProcess) return false;
|
||||
|
||||
return await new Promise((res) => {
|
||||
io.timeout(25000).emit('check_discord_vc_connected', { guildId: guildId }, (status, result) => {
|
||||
console.log(`Discord VC connected for guild ${guildId}: ${result}`);
|
||||
log.INFO(`Discord VC connected for guild ${guildId}: ${result}`);
|
||||
res((result[0]));
|
||||
});
|
||||
})
|
||||
@@ -104,7 +105,7 @@ export const checkIfConnectedToVC = async (guildId) => {
|
||||
export const requestDiscordUsername = (guildId) => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('request_discord_username', { guildId: guildId }, (status, result) => {
|
||||
console.log(`Discord username: ${result[0]}`);
|
||||
log.INFO(`Discord username: ${result[0]}`);
|
||||
res(result[0]);
|
||||
});
|
||||
})
|
||||
@@ -113,7 +114,7 @@ export const requestDiscordUsername = (guildId) => {
|
||||
export const checkIfClientIsOpen = () => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('check_client_is_open', (status, result) => {
|
||||
console.log(`Client is open: ${result}`);
|
||||
log.INFO(`Client is open: ${result}`);
|
||||
res(result[0])
|
||||
});
|
||||
});
|
||||
@@ -122,7 +123,7 @@ export const checkIfClientIsOpen = () => {
|
||||
export const requestDiscordID = () => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('request_discord_id', (status, result) => {
|
||||
console.log(`Discord ID: ${result}`);
|
||||
log.INFO(`Discord ID: ${result}`);
|
||||
res(result[0]);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "pdabWrappers");
|
||||
import { connectToChannel, leaveVoiceChannel, checkIfConnectedToVC, initDiscordBotClient, requestDiscordUsername, requestDiscordID, requestDiscordClientClose, closePdabSocketServer, setDiscordClientPrsense, startPdabSocketServer } from './pdabHandler.mjs';
|
||||
import { openOP25, closeOP25 } from '../op25Handler/op25Handler.mjs';
|
||||
|
||||
@@ -8,38 +10,38 @@ let activeDiscordClient = undefined;
|
||||
* @param {object} joinData The object containing all the information to join the server
|
||||
*/
|
||||
export const joinDiscordVC = async (joinData) => {
|
||||
console.log("Join requested: ", joinData);
|
||||
log.INFO("Join requested: ", joinData);
|
||||
const connection = await new Promise(async (res) => {
|
||||
// Check if a client already exists
|
||||
console.log("Checking if there is a client open");
|
||||
log.INFO("Checking if there is a client open");
|
||||
if (!await checkIfClientIsOpen()) {
|
||||
console.log("There is no open client, starting it now");
|
||||
log.INFO("There is no open client, starting it now");
|
||||
await startPdabSocketServer();
|
||||
// Open an instance of OP25
|
||||
console.log("Starting OP25")
|
||||
log.INFO("Starting OP25")
|
||||
openOP25(joinData.system);
|
||||
|
||||
// Open a new client and join the requested channel with the requested ID
|
||||
initDiscordBotClient(joinData.clientID, () => {
|
||||
console.log("Started PDAB");
|
||||
log.INFO("Started PDAB");
|
||||
|
||||
console.log("Setting the presense of the bot");
|
||||
log.INFO("Setting the presense of the bot");
|
||||
setDiscordClientPrsense(joinData.system);
|
||||
|
||||
// Add the client object to the IO instance
|
||||
console.log("Connecting to channel")
|
||||
log.INFO("Connecting to channel")
|
||||
connectToChannel(joinData.channelID, (connectionStatus) => {
|
||||
console.log("Bot Connected to VC:", connectionStatus);
|
||||
log.INFO("Bot Connected to VC:", connectionStatus);
|
||||
res(connectionStatus);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Join the requested channel with the requested ID
|
||||
console.log("There is an open client");
|
||||
log.INFO("There is an open client");
|
||||
|
||||
console.log("Connecting to channel")
|
||||
log.INFO("Connecting to channel")
|
||||
const connection = connectToChannel(joinData.channelID);
|
||||
console.log("Bot Connected to VC::");
|
||||
log.INFO("Bot Connected to VC::");
|
||||
res(connection);
|
||||
}
|
||||
});
|
||||
@@ -52,12 +54,12 @@ export const joinDiscordVC = async (joinData) => {
|
||||
* @param {string} guildId The guild ID to disconnect from VC
|
||||
*/
|
||||
export const leaveDiscordVC = async (guildId) => {
|
||||
console.log("Leave requested");
|
||||
log.INFO("Leave requested");
|
||||
if (await checkIfConnectedToVC(guildId)) {
|
||||
const clientRemainsOpen = await leaveVoiceChannel(guildId);
|
||||
console.log("Client should remain open: ", clientRemainsOpen);
|
||||
log.INFO("Client should remain open: ", clientRemainsOpen);
|
||||
if (!clientRemainsOpen) {
|
||||
console.log("There are no open VC connections");
|
||||
log.INFO("There are no open VC connections");
|
||||
await closeOP25();
|
||||
|
||||
// Close the python client
|
||||
@@ -75,9 +77,9 @@ export const leaveDiscordVC = async (guildId) => {
|
||||
* @returns {boolean} If the node is connected to VC in the given guild
|
||||
*/
|
||||
export const checkIfDiscordVCConnected = async (guildId) => {
|
||||
console.log("Requested status check");
|
||||
log.INFO("Requested status check");
|
||||
if (await checkIfConnectedToVC(guildId)) {
|
||||
console.log("There is an open VC connection");
|
||||
log.INFO("There is an open VC connection");
|
||||
return (true);
|
||||
} else {
|
||||
return (false);
|
||||
@@ -91,7 +93,7 @@ export const checkIfDiscordVCConnected = async (guildId) => {
|
||||
* @returns {string} The username of the bot in the given guild's VC
|
||||
*/
|
||||
export const getDiscordUsername = async (guildId) => {
|
||||
console.log("Requested username");
|
||||
log.INFO("Requested username");
|
||||
if (checkIfClientIsOpen()) {
|
||||
return await requestDiscordUsername(guildId)
|
||||
} else return (undefined);
|
||||
@@ -102,7 +104,7 @@ export const getDiscordUsername = async (guildId) => {
|
||||
* @returns {string} The ID of the active client
|
||||
*/
|
||||
export const getDiscordID = async () => {
|
||||
console.log("Requested ID");
|
||||
log.INFO("Requested ID");
|
||||
if (checkIfClientIsOpen()) {
|
||||
return await requestDiscordID();
|
||||
}
|
||||
11
src/express/routes/dashboardRoutes.js
Normal file
11
src/express/routes/dashboardRoutes.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// dashboardRoutes.js
|
||||
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
|
||||
// Define routes
|
||||
router.get('/', (req, res) => {
|
||||
res.render('dashboard');
|
||||
});
|
||||
|
||||
export default router;
|
||||
181
src/express/routes/setupRoutes.js
Normal file
181
src/express/routes/setupRoutes.js
Normal file
@@ -0,0 +1,181 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "client.express.setupRoutes");
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import express from 'express';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { generateUniqueID, ensureDirectoryExists } from '../../modules/baseUtils.mjs';
|
||||
import { restartApplication } from '../../modules/selfUpdater.mjs'
|
||||
import { launchProcess } from '../../modules/subprocessHandler.mjs'
|
||||
|
||||
const router = express.Router();
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Define __dirname for ESM
|
||||
|
||||
// Array to store information about added systems
|
||||
let systemsData = [];
|
||||
let nodeData = {};
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const output = await launchProcess('python', ['./discordAudioBot/pdab/getDevices.py'], true, true)
|
||||
log.INFO("Device List", output);
|
||||
res.render('setup/setup', { deviceList: output });
|
||||
});
|
||||
|
||||
// Route to serve the page for adding nearby systems
|
||||
router.get('/add-system', (req, res) => {
|
||||
res.render('setup/add_system');
|
||||
});
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
// Handle form submission here
|
||||
const { clientName, clientLocation, clientCapabilities, audioDeviceId } = req.body;
|
||||
|
||||
log.INFO(clientName, clientLocation, clientCapabilities, audioDeviceId);
|
||||
|
||||
nodeData.clientName = clientName;
|
||||
nodeData.clientLocation = clientLocation;
|
||||
nodeData.clientCapabilities = clientCapabilities;
|
||||
nodeData.audioDeviceId = audioDeviceId;
|
||||
|
||||
res.redirect('/setup/add-system');
|
||||
});
|
||||
|
||||
// Route to handle form submission for adding a system
|
||||
router.post('/add-system', (req, res) => {
|
||||
const { systemName, frequencies, mode, trunkFile, whitelistFile } = req.body;
|
||||
|
||||
// Store system information for later use
|
||||
// For now, let's just log the information
|
||||
log.INFO('System Name:', systemName);
|
||||
log.INFO('Frequencies:', frequencies);
|
||||
log.INFO('Mode:', mode);
|
||||
log.INFO('Trunk File:', trunkFile);
|
||||
log.INFO('Whitelist File:', whitelistFile);
|
||||
|
||||
// Store system information in the array
|
||||
systemsData.push({
|
||||
systemName,
|
||||
frequencies,
|
||||
mode,
|
||||
trunkFile,
|
||||
whitelistFile
|
||||
});
|
||||
|
||||
// Prompt user to add another system or proceed
|
||||
res.render('setup/prompt_add_another_system');
|
||||
});
|
||||
|
||||
// Route to write collected information to .env file
|
||||
router.post('/finish-setup', async (req, res) => {
|
||||
// Write collected information to .env file
|
||||
// For now, let's just log the collected information
|
||||
log.INFO('Collected System Information:', nodeData, systemsData);
|
||||
|
||||
if (!await exportCsv(nodeData)) return res.status(500).send('Error writing to .env file');
|
||||
|
||||
if (!await exportSystems(systemsData)) return res.status(500).send('Error writing the systems config file');
|
||||
|
||||
res.send('Setup process completed successfully! Click <a href="/dashboard">here</a> for the dashboard when the service restarts.');
|
||||
restartApplication();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
|
||||
const exportCsv = (nodeData) => {
|
||||
const nuid = generateUniqueID();
|
||||
log.INFO(`Generated a new unique ID for this node: '${nuid}'`);
|
||||
const envData = {
|
||||
CLIENT_NUID: nuid,
|
||||
CLIENT_NAME: nodeData.clientName,
|
||||
CLIENT_LOCATION: nodeData.clientCapabilities,
|
||||
CLIENT_CAPABILITIES: nodeData.clientCapabilities,
|
||||
SERVER_IP: "",
|
||||
SERVER_PORT: 0,
|
||||
OP25_FULL_PATH: path.resolve(__dirname, '../../op25/op25/gr-op25_repeater/apps'),
|
||||
CONFIG_PATH: "./config/radioPresets.json",
|
||||
AUDIO_DEVICE_ID: nodeData.audioDeviceId,
|
||||
PDAB_PORT: 3110,
|
||||
NODE_ENV: "production",
|
||||
};
|
||||
|
||||
// Generate .env file content
|
||||
const envContent = Object.entries(envData)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n');
|
||||
|
||||
// Write to .env file
|
||||
return new Promise(res => {
|
||||
fs.access('.env', fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
// File doesn't exist, create it
|
||||
fs.writeFile('.env', envContent, (writeErr) => {
|
||||
if (writeErr) {
|
||||
log.ERROR('Error writing to .env file:', writeErr);
|
||||
res(false);
|
||||
} else {
|
||||
log.INFO('.env file created successfully');
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// File exists, update it
|
||||
fs.writeFile('.env', envContent, (writeErr) => {
|
||||
if (writeErr) {
|
||||
log.ERROR('Error writing to .env file:', writeErr);
|
||||
res(false);
|
||||
} else {
|
||||
log.INFO('.env file updated successfully');
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const exportSystems = (systemsData) => {
|
||||
// Write systems data to radioPresets.json
|
||||
const radioPresetsPath = './config/radioPresets.json';
|
||||
const radioPresetsData = {};
|
||||
|
||||
systemsData.forEach((system, index) => (
|
||||
radioPresetsData[system.systemName] = {
|
||||
frequencies: system.frequencies.split(','),
|
||||
mode: system.mode,
|
||||
trunkFile: system.trunkFile || '',
|
||||
whitelistFile: system.whitelistFile || ''
|
||||
}
|
||||
));
|
||||
|
||||
// Ensure directory exists
|
||||
ensureDirectoryExists(radioPresetsPath);
|
||||
|
||||
return new Promise(res => {
|
||||
fs.access(radioPresetsPath, fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
// File doesn't exist, create it
|
||||
fs.writeFile(radioPresetsPath, JSON.stringify(radioPresetsData, null, 4), (writeErr) => {
|
||||
if (writeErr) {
|
||||
log.ERROR('Error writing to radioPresets.json:', writeErr);
|
||||
res(false);
|
||||
} else {
|
||||
log.INFO('radioPresets.json created successfully');
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// File exists, update it
|
||||
fs.writeFile(radioPresetsPath, JSON.stringify(radioPresetsData, null, 4), (writeErr) => {
|
||||
if (writeErr) {
|
||||
log.ERROR('Error writing to radioPresets.json:', writeErr);
|
||||
res(false);
|
||||
} else {
|
||||
log.INFO('radioPresets.json updated successfully');
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
62
src/express/server.mjs
Normal file
62
src/express/server.mjs
Normal file
@@ -0,0 +1,62 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "client.express.server");
|
||||
import express from 'express';
|
||||
import http from 'http';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
import setupRoutes from './routes/setupRoutes.js';
|
||||
import dashboardRoutes from './routes/dashboardRoutes.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Define __dirname for ESM
|
||||
|
||||
export var isSetupComplete = false;
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Start the server
|
||||
const startServer = (port, openSocket) => {
|
||||
if (openSocket) isSetupComplete = true;
|
||||
|
||||
server.listen(port, () => {
|
||||
log.INFO(`Server running on port ${port}`);
|
||||
});
|
||||
};
|
||||
|
||||
export default startServer;
|
||||
|
||||
// Set EJS as the default template engine
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
// Set the views directory to express/views
|
||||
const viewsPath = path.join(__dirname, 'views');
|
||||
app.set('views', viewsPath);
|
||||
|
||||
// Use body-parser middleware to parse JSON requests
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Use body-parser middleware to parse URL-encoded form data
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
// Define static folder for serving HTML, CSS, and client-side JavaScript files
|
||||
const staticPath = path.join(__dirname, 'public');
|
||||
app.use(express.static(staticPath));
|
||||
|
||||
// ---------------- Defualt Route
|
||||
app.get('/', (req, res) => {
|
||||
if (!isSetupComplete) {
|
||||
return res.redirect('/setup');
|
||||
}
|
||||
else {
|
||||
return res.redirect('/dashboard');
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------- Routers
|
||||
// Use the setup router for '/setup' routes
|
||||
app.use('/setup', setupRoutes);
|
||||
|
||||
// Use the dashboard router for '/dashboard' routes
|
||||
app.use('/dashboard', dashboardRoutes);
|
||||
15
src/express/views/dashboard.ejs
Normal file
15
src/express/views/dashboard.ejs
Normal file
@@ -0,0 +1,15 @@
|
||||
<!-- dashboard.ejs -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to the Dashboard</h1>
|
||||
<p>This is a very basic dashboard.</p>
|
||||
<!-- Add more content as needed -->
|
||||
</body>
|
||||
</html>
|
||||
34
src/express/views/setup/add_system.ejs
Normal file
34
src/express/views/setup/add_system.ejs
Normal file
@@ -0,0 +1,34 @@
|
||||
<!-- nearby_systems.ejs -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Nearby Systems</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Add Nearby Systems</h1>
|
||||
<form action="/setup/add-system" method="post">
|
||||
<label for="systemName">System Name:</label>
|
||||
<input type="text" id="systemName" name="systemName" required>
|
||||
<br>
|
||||
<label for="frequencies">Frequencies (comma-separated):</label>
|
||||
<input type="text" id="frequencies" name="frequencies" required>
|
||||
<br>
|
||||
<label for="mode">Mode:</label>
|
||||
<select id="mode" name="mode" required>
|
||||
<option value="p25">P25</option>
|
||||
<option value="nbfm">NBFM</option>
|
||||
</select>
|
||||
<br>
|
||||
<label for="trunkFile">Trunk File:</label>
|
||||
<input type="text" id="trunkFile" name="trunkFile">
|
||||
<br>
|
||||
<label for="whitelistFile">Whitelist File:</label>
|
||||
<input type="text" id="whitelistFile" name="whitelistFile">
|
||||
<br>
|
||||
<button type="submit">Add System</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
19
src/express/views/setup/prompt_add_another_system.ejs
Normal file
19
src/express/views/setup/prompt_add_another_system.ejs
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- prompt_add_another_system.ejs -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Another System?</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Add Another System?</h1>
|
||||
<form action="/setup/add-system" method="get">
|
||||
<button type="submit">Add Another System</button>
|
||||
</form>
|
||||
<form action="/setup/finish-setup" method="post">
|
||||
<button type="submit">Finish Setup</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
32
src/express/views/setup/setup.ejs
Normal file
32
src/express/views/setup/setup.ejs
Normal file
@@ -0,0 +1,32 @@
|
||||
<!-- setup.ejs -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Setup</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Setup</h1>
|
||||
<form action="/setup" method="post">
|
||||
<label for="clientName">Client Name:</label>
|
||||
<input type="text" id="clientName" name="clientName" required>
|
||||
<br>
|
||||
<label for="clientLocation">Client Location:</label>
|
||||
<input type="text" id="clientLocation" name="clientLocation" required>
|
||||
<br>
|
||||
<label for="clientCapabilities">Client Capabilities (comma-separated):</label>
|
||||
<input type="text" id="clientCapabilities" name="clientCapabilities" required>
|
||||
<br>
|
||||
<label for="audioDeviceId">Audio Device ID (typically 'default'):</label>
|
||||
<input type="text" id="audioDeviceId" name="audioDeviceId" required>
|
||||
<br>
|
||||
<button type="submit">Next</button>
|
||||
<div>
|
||||
<h3>Audio Devices</h3>
|
||||
<p><%- deviceList.replace(/\n/g, '<br>') %> </p>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "cliHandler");
|
||||
import { spawn } from "child_process";
|
||||
|
||||
/**
|
||||
@@ -18,7 +20,7 @@ export const executeCommand = (command, args) => {
|
||||
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
// Log any errors to stderr
|
||||
console.error(data.toString());
|
||||
log.ERROR(data.toString());
|
||||
});
|
||||
|
||||
childProcess.on('error', (error) => {
|
||||
@@ -38,7 +38,7 @@ export class ClientNodeConfig {
|
||||
_name = process.env.CLIENT_NAME,
|
||||
_location = process.env.CLIENT_LOCATION,
|
||||
_nearbySystems = getAllPresets(),
|
||||
_capabilities = process.env.CLIENT_CAPABILITIES.split(", "),
|
||||
_capabilities = process.env.CLIENT_CAPABILITIES ? process.env.CLIENT_CAPABILITIES.split(", ") : '',
|
||||
_serverIp = process.env.SERVER_IP,
|
||||
_serverPort = process.env.SERVER_PORT,
|
||||
}) {
|
||||
71
src/modules/debugger.mjs
Normal file
71
src/modules/debugger.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
// Import necessary modules
|
||||
import debug from 'debug';
|
||||
import { config } from 'dotenv';
|
||||
config();
|
||||
import { promises as fs } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { inspect } from 'util';
|
||||
|
||||
/**
|
||||
* Write a given message to the log file
|
||||
* @param {any} logMessage The message to write to the log file
|
||||
* @param {string} appName The app name that created the log entry
|
||||
*/
|
||||
const writeToLog = async (logMessage, appName) => {
|
||||
const logLocation = join(process.env.LOG_LOCATION ?? `./logs/${appName}.log`);
|
||||
|
||||
// Ensure the log directory exists
|
||||
try {
|
||||
await fs.mkdir(dirname(logLocation), { recursive: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Ensure the message is a string
|
||||
logMessage = `${String(logMessage)}\n`;
|
||||
|
||||
// Write to the file
|
||||
try {
|
||||
await fs.writeFile(logLocation, logMessage, { encoding: 'utf-8', flag: 'a+' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the different logging methods for a function
|
||||
* Namespace template = ("[app]:[fileName]:['INFO', 'WARNING', 'DEBUG', 'ERROR']")
|
||||
* @param {string} appName The name of the app to be used in the 'app' portion of the namespace
|
||||
* @param {string} fileName The name of the file calling the builder to be used in the 'fileName' portion of the namespace
|
||||
*/
|
||||
export class DebugBuilder {
|
||||
constructor(appName, fileName) {
|
||||
const buildLogger = (level) => (...messageParts) => {
|
||||
const logger = debug(`${appName}:${fileName}:${level}`);
|
||||
logger(messageParts);
|
||||
|
||||
const timeStamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' });
|
||||
const message = `${timeStamp} - ${appName}:${fileName}:${level}\t-\t${messageParts.map(part => inspect(part)).join(' ')}`;
|
||||
|
||||
// Write to console
|
||||
console.log(message);
|
||||
|
||||
// Write to logfile
|
||||
writeToLog(message, appName);
|
||||
};
|
||||
|
||||
this.INFO = buildLogger('INFO');
|
||||
this.DEBUG = buildLogger('DEBUG');
|
||||
this.VERBOSE = buildLogger('VERBOSE');
|
||||
this.WARN = buildLogger('WARNING');
|
||||
this.ERROR = (...messageParts) => {
|
||||
buildLogger('ERROR')(...messageParts);
|
||||
|
||||
if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) {
|
||||
writeToLog("!--- EXITING ---!", appName);
|
||||
const exitDelay = parseInt(process.env.EXIT_ON_ERROR_DELAY, 10) || 0;
|
||||
setTimeout(() => process.exit(1), exitDelay);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
// Modules
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "radioPresetsHandler");
|
||||
import { writeFile, existsSync, readFileSync } from 'fs';
|
||||
import { resolve } from "path";
|
||||
import { ensureDirectoryExists } from "./baseUtils.mjs";
|
||||
@@ -16,12 +18,12 @@ const configFilePath = process.env.CONFIG_PATH;
|
||||
* @param {function} callback The function to be called when this wrapper completes
|
||||
*/
|
||||
const writePresets = async (presets, callback = undefined) => {
|
||||
console.log(`${__dirname}`);
|
||||
log.INFO(`${__dirname}`);
|
||||
await ensureDirectoryExists(configFilePath);
|
||||
writeFile(configFilePath, JSON.stringify(presets), (err) => {
|
||||
// Error checking
|
||||
if (err) throw err;
|
||||
console.log("Write Complete");
|
||||
log.INFO("Write Complete");
|
||||
if (callback) callback(); else return
|
||||
});
|
||||
}
|
||||
@@ -38,7 +40,7 @@ const sanitizeFrequencies = async (frequenciesArray) => {
|
||||
sanitizedFrequencyArray.push(convertFrequencyToHertz(freq));
|
||||
}
|
||||
|
||||
console.log("Sanitized Frequency Array", sanitizedFrequencyArray);
|
||||
log.INFO("Sanitized Frequency Array", sanitizedFrequencyArray);
|
||||
return sanitizedFrequencyArray;
|
||||
}
|
||||
|
||||
@@ -51,19 +53,19 @@ const convertFrequencyToHertz = async (frequency) => {
|
||||
// check if the passed value is a number
|
||||
if (typeof frequency == 'number' && !isNaN(frequency)) {
|
||||
if (Number.isInteger(frequency)) {
|
||||
console.log(`${frequency} is an integer.`);
|
||||
log.INFO(`${frequency} is an integer.`);
|
||||
// Check to see if the frequency has the correct length
|
||||
if (frequency >= 1000000) return frequency
|
||||
if (frequency >= 100 && frequency <= 999) return frequency * 1000000
|
||||
console.log("Frequency hasn't matched filters: ", frequency);
|
||||
log.INFO("Frequency hasn't matched filters: ", frequency);
|
||||
}
|
||||
else {
|
||||
console.log(`${frequency} is a float value.`);
|
||||
log.INFO(`${frequency} is a float value.`);
|
||||
// Convert to a string to remove the decimal in place and then correct the length
|
||||
return parseInt(converter(frequency).from("MHz").to("Hz"));
|
||||
}
|
||||
} else {
|
||||
console.log(`${frequency} is not a number`);
|
||||
log.INFO(`${frequency} is not a number`);
|
||||
frequency = convertFrequencyToHertz(parseFloat(frequency));
|
||||
|
||||
return parseInt(frequency)
|
||||
@@ -75,8 +77,11 @@ const convertFrequencyToHertz = async (frequency) => {
|
||||
* @returns {any} The object containing the different systems the bot is near
|
||||
*/
|
||||
export const getAllPresets = () => {
|
||||
// If the config path hasn't been set by setup
|
||||
if (!configFilePath) return {};
|
||||
|
||||
const presetDir = resolve(configFilePath);
|
||||
console.log(`Getting presets from directory: '${presetDir}'`);
|
||||
log.INFO(`Getting presets from directory: '${presetDir}'`);
|
||||
if (existsSync(presetDir)) return JSON.parse(readFileSync(presetDir));
|
||||
else return {};
|
||||
}
|
||||
63
src/modules/selfUpdater.mjs
Normal file
63
src/modules/selfUpdater.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "selfUpdater");
|
||||
import simpleGit from 'simple-git';
|
||||
import { restartService } from './serviceHandler.mjs';
|
||||
import { launchProcess } from './subprocessHandler.mjs';
|
||||
|
||||
const git = simpleGit();
|
||||
|
||||
// Function to check for updates
|
||||
export const checkForUpdates = async () => {
|
||||
try {
|
||||
// Fetch remote changes
|
||||
await git.fetch();
|
||||
|
||||
// Get the current branch
|
||||
const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
||||
log.INFO(`Current branch is ${currentBranch}`);
|
||||
|
||||
// Get the latest commit hash for the current branch
|
||||
const latestCommitHash = await git.revparse([`${currentBranch}@{u}`]);
|
||||
|
||||
// Compare with the local commit hash
|
||||
const localCommitHash = await git.revparse(['HEAD']);
|
||||
|
||||
if (latestCommitHash !== localCommitHash) {
|
||||
log.INFO(`An update is available on branch ${currentBranch}. Updating...`);
|
||||
|
||||
// Check if there have been any changes to the code
|
||||
const gitStatus = await git.status();
|
||||
log.INFO(gitStatus);
|
||||
if (gitStatus.modified.length > 0){
|
||||
// There is locally modified code
|
||||
log.INFO("There is locally modified code, stashing changes...");
|
||||
await git.stash();
|
||||
}
|
||||
|
||||
// Ensure we are on the correct branch and pull the latest changes
|
||||
await git.checkout(currentBranch);
|
||||
await git.pull('origin', currentBranch);
|
||||
|
||||
// Run the post-update script
|
||||
log.INFO('Running post-update script...');
|
||||
await launchProcess("bash", ['./post-update.sh'], true);
|
||||
|
||||
// Restart the application to apply the updates
|
||||
log.INFO('Update completed successfully. Restarting the application...');
|
||||
restartApplication();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
log.INFO('The application is up to date.');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
log.ERROR('Error checking for updates:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to restart the application
|
||||
export const restartApplication = () => {
|
||||
log.INFO('Restarting the application...');
|
||||
restartService('discord-radio-bot');
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "serviceHandler");
|
||||
import { exec } from 'child_process';
|
||||
|
||||
/**
|
||||
@@ -9,7 +11,7 @@ const executeCommand = (command) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Command failed with error: ${error.message}`);
|
||||
log.ERROR(`Command failed with error: ${error.message}`);
|
||||
resolve({ stdout, stderr });
|
||||
} else {
|
||||
resolve({ stdout, stderr });
|
||||
@@ -27,7 +29,7 @@ export const startService = async (serviceName) => {
|
||||
try {
|
||||
await executeCommand(`sudo systemctl start ${serviceName}.service`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to start service: ${error.message}`);
|
||||
log.ERROR(`Failed to start service: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,7 +42,7 @@ export const restartService = async (serviceName) => {
|
||||
try {
|
||||
await executeCommand(`sudo systemctl restart ${serviceName}.service`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to restart service: ${error.message}`);
|
||||
log.ERROR(`Failed to restart service: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,6 +55,6 @@ export const stopService = async (serviceName) => {
|
||||
try {
|
||||
await executeCommand(`sudo systemctl stop ${serviceName}.service`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to stop service: ${error.message}`);
|
||||
log.ERROR(`Failed to stop service: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "socketClient");
|
||||
import { io } from "socket.io-client";
|
||||
import { logIntoServerWrapper, nodeCheckStatus, nodeJoinServer, nodeLeaveServer, nodeGetUsername, nodeCheckDiscordClientStatus, nodeCheckCurrentSystem, nodeUpdate, nodeGetDiscordID } from "./socketClientWrappers.mjs";
|
||||
|
||||
@@ -14,13 +16,13 @@ export const initSocketConnection = async (localNodeConfig) => {
|
||||
// Socket Events ('system' events persay)
|
||||
// When the socket connects to the node server
|
||||
socket.on('connect', async () => {
|
||||
console.log('Connected to the server');
|
||||
log.INFO('Connected to the server');
|
||||
await logIntoServerWrapper(socket, localNodeConfig);
|
||||
});
|
||||
|
||||
// When the socket disconnects from the node server
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Disconnected from the server');
|
||||
log.INFO('Disconnected from the server');
|
||||
});
|
||||
|
||||
// Node events/commands
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "subprocessHandler");
|
||||
import { spawn } from "child_process";
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
@@ -13,8 +15,10 @@ const runningProcesses = {};
|
||||
* @param {string} processName - The name of the process to launch.
|
||||
* @param {string[]} args - The arguments to pass to the process.
|
||||
* @param {boolean} waitForClose - Set this to wait to return until the process exits
|
||||
* @param {boolean} returnOutput - Set this in addition to 'waitForClose' to return the script output when the process closes
|
||||
* @param {boolean} waitForClose - Set the current working directory of the process being launched
|
||||
*/
|
||||
export const launchProcess = (processName, args, waitForClose = false, pcwd = undefined) => {
|
||||
export const launchProcess = (processName, args, waitForClose = false, returnOutput = false, pcwd = undefined) => {
|
||||
if (!runningProcesses[processName]) {
|
||||
let childProcess;
|
||||
if (pcwd) {
|
||||
@@ -33,34 +37,38 @@ export const launchProcess = (processName, args, waitForClose = false, pcwd = un
|
||||
// Get the stdout from the child process
|
||||
childProcess.stdout.setEncoding('utf8');
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
if (process.env.NODE_ENV === "development") console.log(`Data from ${processName}:`, data);
|
||||
if (process.env.NODE_ENV === "development") log.INFO(`Data from ${processName}:`, data);
|
||||
scriptOutput += data.toString();
|
||||
});
|
||||
|
||||
// Get the stderr from the child process
|
||||
childProcess.stderr.setEncoding('utf8');
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
if (process.env.NODE_ENV === "development") console.log(`Data from ${processName}:`, data);
|
||||
if (process.env.NODE_ENV === "development") log.INFO(`Data from ${processName}:`, data);
|
||||
scriptOutput += data.toString();
|
||||
})
|
||||
|
||||
let code = new Promise(res => {
|
||||
let output = new Promise(res => {
|
||||
childProcess.on('exit', (code, signal) => {
|
||||
// Remove reference to the process when it exits
|
||||
delete runningProcesses[processName];
|
||||
console.log(`${processName} process exited with code ${code} and signal ${signal}`);
|
||||
console.log("Child process console output: ", scriptOutput);
|
||||
res(code);
|
||||
log.INFO(`${processName} process exited with code ${code} and signal ${signal}`);
|
||||
log.INFO("Child process console output: ", scriptOutput);
|
||||
// Return the full script output if requested
|
||||
if (returnOutput === true) {
|
||||
return res(scriptOutput)
|
||||
}
|
||||
return res(code);
|
||||
})
|
||||
});
|
||||
|
||||
if (waitForClose === true) {
|
||||
return code
|
||||
return output
|
||||
}
|
||||
|
||||
console.log(`${processName} process started.`);
|
||||
log.INFO(`${processName} process started.`);
|
||||
} else {
|
||||
console.log(`${processName} process is already running.`);
|
||||
log.INFO(`${processName} process is already running.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +99,9 @@ export const killProcess = (processName) => {
|
||||
const childProcess = runningProcesses[processName];
|
||||
if (childProcess) {
|
||||
childProcess.kill();
|
||||
console.log(`${processName} process killed.`);
|
||||
log.INFO(`${processName} process killed.`);
|
||||
} else {
|
||||
console.log(`${processName} process is not running.`);
|
||||
log.INFO(`${processName} process is not running.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Modules
|
||||
import { DebugBuilder } from "./debugger.mjs";
|
||||
const log = new DebugBuilder("client", "updateConfig");
|
||||
import replace from 'replace-in-file';
|
||||
|
||||
class Options {
|
||||
@@ -19,7 +21,7 @@ class Options {
|
||||
export const updateId = async (updatedId) => {
|
||||
await updateConfig('CLIENT_NUID', updatedId);
|
||||
process.env.CLIENT_NUID = updatedId;
|
||||
console.log("Updated NUID to: ", updatedId);
|
||||
log.INFO("Updated NUID to: ", updatedId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +51,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
||||
this.updateConfig('CLIENT_NAME', newConfigObject.name);
|
||||
updatedKeys.push({ 'CLIENT_NAME': newConfigObject.name });
|
||||
process.env.CLIENT_NAME = newConfigObject.name;
|
||||
console.log("Updated name to: ", newConfigObject.name);
|
||||
log.INFO("Updated name to: ", newConfigObject.name);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("ip")) {
|
||||
@@ -57,7 +59,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
||||
this.updateConfig('CLIENT_IP', newConfigObject.ip);
|
||||
updatedKeys.push({ 'CLIENT_IP': newConfigObject.ip });
|
||||
process.env.CLIENT_IP = newConfigObject.ip;
|
||||
console.log("Updated ip to: ", newConfigObject.ip);
|
||||
log.INFO("Updated ip to: ", newConfigObject.ip);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("port")) {
|
||||
@@ -65,7 +67,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
||||
this.updateConfig('CLIENT_PORT', newConfigObject.port);
|
||||
updatedKeys.push({ 'CLIENT_PORT': newConfigObject.port });
|
||||
process.env.CLIENT_PORT = newConfigObject.port;
|
||||
console.log("Updated port to: ", newConfigObject.port);
|
||||
log.INFO("Updated port to: ", newConfigObject.port);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("location")) {
|
||||
@@ -73,7 +75,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
||||
this.updateConfig('CLIENT_LOCATION', newConfigObject.location);
|
||||
updatedKeys.push({ 'CLIENT_LOCATION': newConfigObject.location });
|
||||
process.env.CLIENT_LOCATION = newConfigObject.location;
|
||||
console.log("Updated location to: ", newConfigObject.location);
|
||||
log.INFO("Updated location to: ", newConfigObject.location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
||||
export function updateConfig (key, value) {
|
||||
const options = new Options(key, value);
|
||||
|
||||
console.log("Options:", options);
|
||||
log.INFO("Options:", options);
|
||||
|
||||
updateConfigFile(options, (updatedFiles) => {
|
||||
// Do Something
|
||||
@@ -102,8 +104,8 @@ export function updateConfig (key, value) {
|
||||
*/
|
||||
function updateConfigFile(options, callback) {
|
||||
replace(options, (error, changedFiles) => {
|
||||
if (error) return console.error('Error occurred:', error);
|
||||
console.log('Updated config file: ', changedFiles);
|
||||
if (error) return log.ERROR('Error occurred:', error);
|
||||
log.INFO('Updated config file: ', changedFiles);
|
||||
callback(changedFiles);
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "op25ConfigGenerator");
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
class OP25ConfigObject {
|
||||
@@ -7,9 +9,9 @@ class OP25ConfigObject {
|
||||
try {
|
||||
const jsonConfig = JSON.stringify(this, null, 2);
|
||||
await fs.writeFile(filename, jsonConfig);
|
||||
console.log(`Config exported to ${filename}`);
|
||||
log.INFO(`Config exported to ${filename}`);
|
||||
} catch (error) {
|
||||
console.error(`Error exporting config to ${filename}: ${error}`);
|
||||
log.ERROR(`Error exporting config to ${filename}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +19,7 @@ class OP25ConfigObject {
|
||||
export class P25ConfigGenerator extends OP25ConfigObject {
|
||||
constructor({ systemName, controlChannels, tagsFile, whitelistFile = undefined }) {
|
||||
super();
|
||||
console.log("Generating P25 Config for:", systemName);
|
||||
log.INFO("Generating P25 Config for:", systemName);
|
||||
const controlChannelsString = controlChannels.join(',');
|
||||
this.channels = [new channelConfig({
|
||||
"channelName": systemName,
|
||||
160
src/op25Handler/op25Handler.mjs
Normal file
160
src/op25Handler/op25Handler.mjs
Normal file
@@ -0,0 +1,160 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("client", "op25Handler");
|
||||
import { P25ConfigGenerator, NBFMConfigGenerator } from './modules/op25ConfigGenerators.mjs';
|
||||
import { getAllPresets } from '../modules/radioPresetHandler.mjs';
|
||||
import { startService, stopService } from '../modules/serviceHandler.mjs';
|
||||
import axios from 'axios'; // Import axios for HTTP requests
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
let currentSystem = undefined;
|
||||
let crashDetectionInterval; // Variable to store the crash detection interval ID
|
||||
|
||||
// Sleep utility to add delays between retries
|
||||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
/**
|
||||
* Checks the health of the OP25 web portal by making an HTTP HEAD request.
|
||||
* If the portal does not respond or there is an issue, retries a specified number of times.
|
||||
* If all retry attempts fail, it restarts the OP25 service.
|
||||
*
|
||||
* @async
|
||||
* @function checkServiceHealth
|
||||
* @returns {Promise<void>} Resolves if the web portal is healthy or after the restart process is triggered.
|
||||
* @throws Will log errors related to the health check or service restart.
|
||||
*/
|
||||
const checkServiceHealth = async () => {
|
||||
try {
|
||||
log.INFO("Checking OP25 web portal health...");
|
||||
// Perform an HTTP HEAD request to the web portal with a 5-second timeout
|
||||
await axios({ method: "get", url: 'http://localhost:8081', timeout: 5000 });
|
||||
log.INFO("Web portal is healthy.");
|
||||
} catch (error) {
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
log.ERROR("Request timed out. The web portal took too long to respond.");
|
||||
} else if (error.response) {
|
||||
log.ERROR(`Web portal responded with status ${error.response.status}: ${error.response.statusText}`);
|
||||
} else if (error.request) {
|
||||
log.ERROR("No response received from web portal.");
|
||||
} else {
|
||||
log.ERROR(`Unexpected error occurred: ${error.message}`);
|
||||
}
|
||||
|
||||
// Retry mechanism
|
||||
const retryAttempts = 3;
|
||||
const delayBetweenRetries = 3000; // 3 seconds delay
|
||||
|
||||
for (let i = 1; i <= retryAttempts; i++) {
|
||||
log.INFO(`Retrying to check web portal health... Attempt ${i}/${retryAttempts}`);
|
||||
try {
|
||||
await sleep(delayBetweenRetries); // Add delay before retrying
|
||||
await axios({ method: "get", url: 'http://localhost:8081', timeout: 5000 });
|
||||
log.INFO("Web portal is healthy on retry.");
|
||||
return;
|
||||
} catch (retryError) {
|
||||
log.ERROR(`Retry ${i} failed: ${retryError.message}`);
|
||||
if (i === retryAttempts) {
|
||||
log.ERROR("All retry attempts failed. Restarting the service...");
|
||||
await restartOp25();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates configuration based on the preset and restarts the OP25 service.
|
||||
* @param {Object} preset The preset object containing system configuration.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const createConfigAndRestartService = async (systemName, preset) => {
|
||||
const { mode, frequencies, trunkFile, whitelistFile } = preset;
|
||||
|
||||
let generator;
|
||||
if (mode === 'p25') {
|
||||
log.INFO("Using P25 Config Generator based on preset mode", systemName, mode);
|
||||
generator = new P25ConfigGenerator({
|
||||
systemName,
|
||||
controlChannels: frequencies,
|
||||
tagsFile: trunkFile,
|
||||
whitelistFile: whitelistFile !== 'none' ? whitelistFile : undefined
|
||||
});
|
||||
} else if (mode === 'nbfm') {
|
||||
log.INFO("Using NBFM Config Generator based on preset mode", systemName, mode);
|
||||
generator = new NBFMConfigGenerator({
|
||||
systemName,
|
||||
frequencies,
|
||||
tagsFile: trunkFile
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unsupported mode: ${mode}`);
|
||||
}
|
||||
|
||||
const op25FilePath = process.env.OP25_FULL_PATH || './'; // Default to current directory if OP25_FULL_PATH is not set
|
||||
const op25ConfigPath = `${op25FilePath}${op25FilePath.endsWith('/') ? 'active.cfg.json' : '/active.cfg.json'}`;
|
||||
await generator.exportToFile(op25ConfigPath);
|
||||
|
||||
await restartOp25();
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the OP25 service for the specified system.
|
||||
* @param {string} systemName The name of the system to open.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const openOP25 = async (systemName) => {
|
||||
currentSystem = systemName;
|
||||
|
||||
// Retrieve preset for the specified system name
|
||||
const presets = await getAllPresets();
|
||||
const preset = presets[systemName];
|
||||
|
||||
log.INFO("Found preset:", preset);
|
||||
|
||||
if (!preset) {
|
||||
throw new Error(`Preset for system "${systemName}" not found.`);
|
||||
}
|
||||
|
||||
await createConfigAndRestartService(systemName, preset);
|
||||
|
||||
// Start OP25 crash detection
|
||||
if (!crashDetectionInterval) {
|
||||
crashDetectionInterval = setInterval(checkServiceHealth, 30000); // Check every 30 seconds
|
||||
log.INFO("Started crash detection.");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restarts the OP25 service without changing the config.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const restartOp25 = async () => {
|
||||
// Restart the service
|
||||
await stopService('op25-multi_rx');
|
||||
await startService('op25-multi_rx');
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the OP25 service.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const closeOP25 = async () => {
|
||||
currentSystem = undefined;
|
||||
await stopService('op25-multi_rx');
|
||||
|
||||
// Stop crash detection
|
||||
if (crashDetectionInterval) {
|
||||
clearInterval(crashDetectionInterval);
|
||||
crashDetectionInterval = null;
|
||||
log.INFO("Stopped crash detection.");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the current system.
|
||||
* @returns {Promise<string | undefined>} The name of the current system.
|
||||
*/
|
||||
export const getCurrentSystem = async () => {
|
||||
return currentSystem;
|
||||
};
|
||||
Reference in New Issue
Block a user