Merge pull request #346 from outis1one/claude/ionos-script-integration-x32ofw
Claude/ionos script integration x32ofw
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user