initialize generic rms-software repository

Add the reusable RMS core application (server, web UI, plugins, tests, tools) with generic defaults, GPL licensing, and maintainer context documentation so deployments can consume this repo as software source independent of station-specific overlays.
This commit is contained in:
2026-03-16 03:31:08 +01:00
commit e1a4ce0b8b
58 changed files with 20611 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
const fs = require("fs");
const path = require("path");
async function createPlugin(ctx) {
let activeOwnerEmail = null;
return {
async execute(action, input) {
if (action === "getPolicy") {
return getPolicy(ctx, activeOwnerEmail);
}
if (action === "addPersistentUser") {
const email = normalizeEmail(input && input.email);
if (!email) {
throw new Error("E-Mail fehlt");
}
const persistent = await readPersistentUsers(ctx);
if (!persistent.includes(email)) {
persistent.push(email);
persistent.sort();
await writePersistentUsers(ctx, persistent);
}
await writePolicyFile(ctx, persistent, activeOwnerEmail);
return { ok: true, persistentUsers: persistent, ownerEmail: activeOwnerEmail };
}
if (action === "removePersistentUser") {
const email = normalizeEmail(input && input.email);
if (!email) {
throw new Error("E-Mail fehlt");
}
const persistent = await readPersistentUsers(ctx);
const next = persistent.filter((entry) => entry !== email);
await writePersistentUsers(ctx, next);
await writePolicyFile(ctx, next, activeOwnerEmail);
return { ok: true, persistentUsers: next, ownerEmail: activeOwnerEmail };
}
if (action === "syncOwner") {
activeOwnerEmail = normalizeEmail(input && input.ownerEmail);
const persistent = await readPersistentUsers(ctx);
await writePolicyFile(ctx, persistent, activeOwnerEmail);
return { ok: true, ownerEmail: activeOwnerEmail, persistentUsers: persistent };
}
if (action === "clearOwner") {
activeOwnerEmail = null;
const persistent = await readPersistentUsers(ctx);
await writePolicyFile(ctx, persistent, activeOwnerEmail);
return { ok: true, ownerEmail: null, persistentUsers: persistent };
}
throw new Error(`Unknown action: ${action}`);
},
async getStatus() {
const policy = await getPolicy(ctx, activeOwnerEmail);
return {
ok: true,
ownerEmail: policy.ownerEmail,
persistentCount: policy.persistentUsers.length,
effectiveCount: policy.effectiveUsers.length
};
},
async health() {
return { ok: true };
}
};
}
async function getPolicy(ctx, ownerEmail) {
const persistentUsers = await readPersistentUsers(ctx);
const effectiveUsers = buildEffectiveUsers(persistentUsers, ownerEmail);
return {
ok: true,
ownerEmail: ownerEmail || null,
persistentUsers,
effectiveUsers
};
}
function buildEffectiveUsers(persistentUsers, ownerEmail) {
const set = new Set(persistentUsers);
if (ownerEmail) {
set.add(ownerEmail);
}
return Array.from(set).sort();
}
async function readPersistentUsers(ctx) {
const filePath = persistentFilePath(ctx);
if (!filePath || !fs.existsSync(filePath)) {
return [];
}
const raw = await fs.promises.readFile(filePath, "utf8");
const entries = raw
.split(/\r?\n/)
.map((line) => normalizeEmail(line))
.filter(Boolean);
return Array.from(new Set(entries)).sort();
}
async function writePersistentUsers(ctx, users) {
const filePath = persistentFilePath(ctx);
if (!filePath) {
return;
}
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
const payload = users.length ? `${users.join("\n")}\n` : "";
await fs.promises.writeFile(filePath, payload, "utf8");
}
async function writePolicyFile(ctx, persistentUsers, ownerEmail) {
const filePath = policyFilePath(ctx);
if (!filePath) {
return;
}
const effectiveUsers = buildEffectiveUsers(persistentUsers, ownerEmail);
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
const payload = effectiveUsers.length ? `${effectiveUsers.join("\n")}\n` : "";
await fs.promises.writeFile(filePath, payload, "utf8");
}
function policyFilePath(ctx) {
return resolvePath(String(ctx.getSetting("policyFilePath", ctx.env.OPENWEBRX_ACCESS_POLICY_FILE || "./data/openwebrx-access-policy.txt")));
}
function persistentFilePath(ctx) {
return resolvePath(String(ctx.getSetting("persistentFilePath", ctx.env.OPENWEBRX_PERSISTENT_USERS_FILE || "./data/openwebrx-persistent-users.txt")));
}
function normalizeEmail(value) {
const email = String(value || "").trim().toLowerCase();
if (!email) return "";
if (!email.includes("@")) return "";
return email;
}
function resolvePath(value) {
const trimmed = String(value || "").trim();
if (!trimmed) return "";
if (path.isAbsolute(trimmed)) return trimmed;
return path.resolve(process.cwd(), trimmed);
}
module.exports = {
createPlugin
};

View File

@@ -0,0 +1,69 @@
{
"id": "rms.station.access.policy",
"name": "Station Access Policy",
"version": "1.0.0",
"apiVersion": "1.0",
"capabilities": [
"station.access.policy.read",
"admin.station.access.policy.write"
],
"settingsSchema": {
"type": "object",
"properties": {
"policyFilePath": { "type": "string" },
"persistentFilePath": { "type": "string" }
},
"additionalProperties": false
},
"uiControls": [
{
"controlId": "station-access-policy",
"controlType": "switch.group",
"title": "Station Access Policy",
"capability": "admin.station.access.policy.write",
"actions": [
{
"name": "addPersistentUser",
"inputSchema": {
"type": "object",
"properties": {
"email": { "type": "string" }
},
"required": ["email"],
"additionalProperties": false
}
},
{
"name": "removePersistentUser",
"inputSchema": {
"type": "object",
"properties": {
"email": { "type": "string" }
},
"required": ["email"],
"additionalProperties": false
}
},
{
"name": "syncOwner",
"inputSchema": {
"type": "object",
"properties": {
"ownerEmail": { "type": "string" }
},
"required": ["ownerEmail"],
"additionalProperties": false
}
},
{
"name": "clearOwner",
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": false
}
}
]
}
]
}