Compare commits
9 Commits
851c0cfc47
...
#17-lintin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
911142a8aa | ||
| 0ff11589e9 | |||
| a2abe7e71d | |||
| b23a0768e3 | |||
| 27516a0a25 | |||
|
|
bc0fc23fb0 | ||
|
|
821e4f6a64 | ||
| 0f5ee3b3fb | |||
|
|
54aca6e401 |
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,86 +0,0 @@
|
|||||||
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 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') {
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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];
|
|
||||||
|
|
||||||
log.INFO("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;
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "drb-client",
|
"name": "drb-client",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "client.js",
|
"main": "src/client.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha --timeout 10000"
|
"test": "mocha --timeout 10000"
|
||||||
},
|
},
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.17.0",
|
"@discordjs/voice": "^0.17.0",
|
||||||
|
"axios": "^1.7.7",
|
||||||
"convert-units": "^2.3.4",
|
"convert-units": "^2.3.4",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
|||||||
4
setup.sh
4
setup.sh
@@ -94,8 +94,8 @@ rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED
|
|||||||
|
|
||||||
# Getting the Python DAB
|
# Getting the Python DAB
|
||||||
echo "Installing PDAB and Dependencies"
|
echo "Installing PDAB and Dependencies"
|
||||||
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./discordAudioBot/pdab
|
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./pdab
|
||||||
pip3 install -r ./discordAudioBot/pdab/requirements.txt
|
pip3 install -r ./pdab/requirements.txt
|
||||||
|
|
||||||
# Create a systemd service file for the DRB Client
|
# Create a systemd service file for the DRB Client
|
||||||
echo "Adding DRB Node service..."
|
echo "Adding DRB Node service..."
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ let botCallback;
|
|||||||
export const initDiscordBotClient = (clientId, callback, runPDAB = true) => {
|
export const initDiscordBotClient = (clientId, callback, runPDAB = true) => {
|
||||||
botCallback = callback;
|
botCallback = callback;
|
||||||
|
|
||||||
if (runPDAB) launchProcess("python", [join(__dirname, "./pdab/main.py"), process.env.AUDIO_DEVICE_ID, clientId, port], false, 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
|
pdabProcess = true; // TODO - Make this more dynamic
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { DebugBuilder } from "./debugger.mjs";
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
const log = new DebugBuilder("client", "selfUpdater");
|
const log = new DebugBuilder("client", "selfUpdater");
|
||||||
import simpleGit from 'simple-git';
|
import simpleGit from 'simple-git';
|
||||||
import { restartService } from './serviceHandler.mjs'
|
import { restartService } from './serviceHandler.mjs';
|
||||||
import { launchProcess } from './subprocessHandler.mjs'
|
import { launchProcess } from './subprocessHandler.mjs';
|
||||||
|
|
||||||
const git = simpleGit();
|
const git = simpleGit();
|
||||||
|
|
||||||
@@ -12,27 +12,31 @@ export const checkForUpdates = async () => {
|
|||||||
// Fetch remote changes
|
// Fetch remote changes
|
||||||
await git.fetch();
|
await git.fetch();
|
||||||
|
|
||||||
// Get the latest commit hash
|
// Get the current branch
|
||||||
const latestCommitHash = await git.revparse(['@{u}']);
|
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
|
// Compare with the local commit hash
|
||||||
const localCommitHash = await git.revparse(['HEAD']);
|
const localCommitHash = await git.revparse(['HEAD']);
|
||||||
|
|
||||||
if (latestCommitHash !== localCommitHash) {
|
if (latestCommitHash !== localCommitHash) {
|
||||||
log.INFO('An update is available. Updating...');
|
log.INFO(`An update is available on branch ${currentBranch}. Updating...`);
|
||||||
|
|
||||||
// Check if there have been any changes to the code
|
// Check if there have been any changes to the code
|
||||||
const gitStatus = await git.status()
|
const gitStatus = await git.status();
|
||||||
log.INFO(gitStatus);
|
log.INFO(gitStatus);
|
||||||
if (gitStatus.modified.length > 0){
|
if (gitStatus.modified.length > 0){
|
||||||
// There is locally modified code
|
// There is locally modified code
|
||||||
log.INFO("There is locally modified code, resetting...");
|
log.INFO("There is locally modified code, stashing changes...");
|
||||||
await git.stash();
|
await git.stash();
|
||||||
await git.reset('hard', ['origin/master']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the latest changes from the remote repository
|
// Ensure we are on the correct branch and pull the latest changes
|
||||||
await git.pull();
|
await git.checkout(currentBranch);
|
||||||
|
await git.pull('origin', currentBranch);
|
||||||
|
|
||||||
// Run the post-update script
|
// Run the post-update script
|
||||||
log.INFO('Running post-update script...');
|
log.INFO('Running post-update script...');
|
||||||
@@ -42,10 +46,10 @@ export const checkForUpdates = async () => {
|
|||||||
log.INFO('Update completed successfully. Restarting the application...');
|
log.INFO('Update completed successfully. Restarting the application...');
|
||||||
restartApplication();
|
restartApplication();
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} else {
|
} else {
|
||||||
log.INFO('The application is up to date.');
|
log.INFO('The application is up to date.');
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.ERROR('Error checking for updates:', error);
|
log.ERROR('Error checking for updates:', error);
|
||||||
@@ -56,4 +60,4 @@ export const checkForUpdates = async () => {
|
|||||||
export const restartApplication = () => {
|
export const restartApplication = () => {
|
||||||
log.INFO('Restarting the application...');
|
log.INFO('Restarting the application...');
|
||||||
restartService('discord-radio-bot');
|
restartService('discord-radio-bot');
|
||||||
}
|
};
|
||||||
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