Merge pull request #346 from outis1one/claude/ionos-script-integration-x32ofw

Claude/ionos script integration x32ofw
This commit is contained in:
Outis
2026-08-16 23:03:59 -04:00
committed by GitHub
2 changed files with 54 additions and 2 deletions
+37
View File
@@ -704,6 +704,30 @@ case "$cmd" in
echo "working directory followed the OLD directory when it got renamed" echo "working directory followed the OLD directory when it got renamed"
echo "aside). Run 'cd $HERE' again (or open a new shell) to see the" echo "aside). Run 'cd $HERE' again (or open a new shell) to see the"
echo "restored files." echo "restored files."
# The line above replaced $HERE's whole directory tree with a fresh
# extraction from the archive — every file in it is a brand-new
# inode, so any POSIX ACL grants the Security Dashboard holds on it
# (setfacl access to .env, config/, logs/, spool/ — see
# _secdash_grant_asterisk_access in services/security-dashboard.sh)
# went away with the old directory and were never on the new one to
# begin with. Confirmed live: this shows up as "No permission to
# read .../.env" in the dashboard's Extensions tab right after a
# restore, on a box where the dashboard was already installed and
# working fine before. It isn't a bug in the dashboard, and it isn't
# anything this restore script itself can safely fix from here (it
# runs standalone, with none of services/*.sh's functions or the
# dashboard's service-user name available to it) — the one working
# fix is re-running the dashboard's own installer, which regrants
# every ACL against whatever is on disk right now.
if systemctl list-unit-files 2>/dev/null | grep -q '^security-dashboard\.service'; then
echo ""
echo "The Security Dashboard is installed on this box. This restore replaced"
echo "every file under $HERE with fresh copies from the archive, which drops"
echo "the dashboard's file-read permissions on them (Extensions tab TURN/domain"
echo "details, etc.). Re-run to restore those:"
echo " sudo ./setup.sh security-dashboard"
fi
;; ;;
*) *)
@@ -1833,6 +1857,19 @@ this box's own directory — never leaving a second, wrongly-named directory
behind that would confuse every service that resolves Asterisk's layout behind that would confuse every service that resolves Asterisk's layout
(Security Dashboard, PSTN trunk, CrowdSec's Asterisk acquisition, Caddy). (Security Dashboard, PSTN trunk, CrowdSec's Asterisk acquisition, Caddy).
If the Security Dashboard is installed, \`restore\` prints a reminder to
re-run \`sudo ./setup.sh security-dashboard\` afterward. Every file under
\`${EA_DIR}\` gets replaced with a fresh extraction from the archive — new
inodes, none of which carry the dashboard's POSIX ACL grants (read access
to \`.env\`, \`config/\`, \`logs/\`, \`spool/\` — see
\`_secdash_grant_asterisk_access\` in \`services/security-dashboard.sh\`).
Confirmed live: this shows up as "No permission to read .../.env" in the
Extensions tab right after a restore, on a box where the dashboard was
already working fine beforehand. Re-running the dashboard's own installer
regrants everything against whatever's on disk now; this restore script
can't do it itself since it runs standalone, with none of \`services/*.sh\`'s
functions or the dashboard's service-user name available to it.
## VLANs / other subnets ## VLANs / other subnets
\`.env\` → \`HAS_VLANS\`/\`VLAN_SUBNETS\` lists extra networks (space-separated \`.env\` → \`HAS_VLANS\`/\`VLAN_SUBNETS\` lists extra networks (space-separated
+17 -2
View File
@@ -3999,6 +3999,7 @@ INDEX_HTML = """<!doctype html>
<div class="card"> <div class="card">
<div class="card-head"><h3>Active bans</h3></div> <div class="card-head"><h3>Active bans</h3></div>
<div class="card-body"> <div class="card-body">
<input type="text" id="dec-search" placeholder="Search by IP, network/carrier, country, scenario..." style="width:24rem;margin-bottom:0.75rem">
<div class="table-wrap"> <div class="table-wrap">
<table id="dec-table"><thead><tr> <table id="dec-table"><thead><tr>
<th class="sortable" data-sort="value">IP/Range</th> <th class="sortable" data-sort="value">IP/Range</th>
@@ -5060,8 +5061,20 @@ function decSortValue(d, key) {
} }
} }
// Matches against every column shown in the table (IP/range, scenario,
// carrier name/ASN, country, origin) rather than just the IP, since "search
// by network/carrier" was asked for explicitly -- a phone's ISP/mobile
// carrier name is often more memorable than its current IP.
function decMatchesSearch(d, term) {
if (!term) return true;
const haystack = [d.value, d.scenario, d.as_number, d.as_name, d.country, d.origin]
.filter(Boolean).join(" ").toLowerCase();
return haystack.includes(term);
}
function renderDecisions() { function renderDecisions() {
let rows = lastDecisions.slice(); const term = (document.getElementById("dec-search").value || "").trim().toLowerCase();
let rows = lastDecisions.filter(d => decMatchesSearch(d, term));
if (decSort.key) { if (decSort.key) {
rows.sort((a, b) => { rows.sort((a, b) => {
const av = decSortValue(a, decSort.key), bv = decSortValue(b, decSort.key); const av = decSortValue(a, decSort.key), bv = decSortValue(b, decSort.key);
@@ -5087,7 +5100,7 @@ function renderDecisions() {
<button class="action" onclick="unban(${d.id})">Unban</button> <button class="action" onclick="unban(${d.id})">Unban</button>
${d.as_number ? `<button class="action" onclick="exemptAsn('${esc(d.as_number)}')">Exempt ASN</button>` : ""} ${d.as_number ? `<button class="action" onclick="exemptAsn('${esc(d.as_number)}')">Exempt ASN</button>` : ""}
</td> </td>
</tr>`).join("") || "<tr><td colspan=7 class=muted>No active bans.</td></tr>"; </tr>`).join("") || `<tr><td colspan=7 class=muted>${term ? "No bans match that search." : "No active bans."}</td></tr>`;
} }
document.querySelectorAll("#dec-table th.sortable").forEach(th => { document.querySelectorAll("#dec-table th.sortable").forEach(th => {
@@ -5099,6 +5112,8 @@ document.querySelectorAll("#dec-table th.sortable").forEach(th => {
}); });
}); });
document.getElementById("dec-search").addEventListener("input", renderDecisions);
async function loadDecisions() { async function loadDecisions() {
const res = await fetch("/api/decisions"); const res = await fetch("/api/decisions");
lastDecisions = await res.json(); lastDecisions = await res.json();