#!/usr/bin/env node import fs from "node:fs"; const args = process.argv.slice(2); function getArg(name) { const index = args.indexOf(name); return index >= 0 ? args[index + 1] : undefined; } function hasFlag(name) { return args.includes(name); } function usage() { console.log(`Usage: node scripts/check-mqtt-acl.mjs --file /etc/mosquitto/acl node scripts/check-mqtt-acl.mjs --print-template Required Mosquitto ACL rules: pattern readwrite devices/%u/uplink/# pattern read devices/%u/downlink/# user qris-backend topic read devices/+/uplink/# topic write devices/+/downlink/# `); } const requiredLines = [ "pattern readwrite devices/%u/uplink/#", "pattern read devices/%u/downlink/#", "user qris-backend", "topic read devices/+/uplink/#", "topic write devices/+/downlink/#" ]; if (hasFlag("--help") || hasFlag("-h")) { usage(); process.exit(0); } if (hasFlag("--print-template")) { console.log(`# QRIS Soundbox Mosquitto ACL # Device username must equal platform device_id. ${requiredLines.join("\n")} `); process.exit(0); } const aclFile = getArg("--file") || process.env.MOSQUITTO_ACL_FILE; if (!aclFile) { usage(); process.exit(1); } const content = fs.readFileSync(aclFile, "utf8"); const normalized = content .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line && !line.startsWith("#")); const missing = requiredLines.filter((line) => !normalized.includes(line)); const result = { file: aclFile, ok: missing.length === 0, missing, required: requiredLines }; console.log(JSON.stringify(result, null, 2)); process.exit(result.ok ? 0 : 1);