services/asterisk-digital-ocean.sh was a near-verbatim copy of
services/asterisk.sh — same vendor refresh, compose template, messaging
dialplan, presence alerts, UFW rules and dashboard/trunk chaining, with the
helper functions renamed _asterisk_do_*. Two copies meant every fix had to
land twice, and several never did.
There is now one `asterisk` service. It reads the DigitalOcean metadata
service and asks either way (so a droplet with metadata blocked, or another
provider's public VM, can still opt in), then gates the genuinely
droplet-specific behaviour on that one answer: swapfile for low-RAM plans,
public-FQDN-only setup with no LAN/VLAN prompts, a Caddy site block pinned
to that FQDN, the remote-Authelia option, and the doctl Cloud Firewall.
Two things that were droplet-only for no real reason now apply everywhere:
the entrypoint patch that writes security-level events to logs/full, and
the logrotate config for that file. Without them the Security Dashboard's
Security Log tab and CrowdSec's Asterisk acquisition were silently empty on
every home/LAN install; crowdsec.sh now detects either install directory.
Existing droplets are left alone: an install at ~/docker/asterisk-digital-ocean
keeps its directory and easy-asterisk-do container names, since its Caddyfile
block, UFW rules, Cloud Firewall, CrowdSec acquisition and PSTN trunk all
name those exactly. New installs use ~/docker/asterisk / easy-asterisk.
`sudo ./setup.sh asterisk-digital-ocean` still works via a new SERVICE_ALIAS
map in setup.sh, without a second menu entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh
- ntfy.sh: auth-default-access was deny-all, silently blocking both publish
and anonymous subscribe on every topic once an auth-file is present.
Default to read-write (same model as public ntfy.sh) so alerts from
crowdsec.sh/pstn-trunk.sh actually get delivered.
- crowdsec.sh: add update/fresh/cancel reinstall gating — reruns previously
re-asked every optional prompt (ASN exempt, geo-allowlist, ntfy, remote
LAPI) unconditionally with no way to just refresh in place.
- asterisk-digital-ocean.sh: add optional ntfy alerts on extension
registration going offline/online, checked every 2 minutes via systemd
timer (cron.d fallback), offered on both fresh install and update.
- security-dashboard.sh: replace the outbound-only Asterisk Web Admin link
with an embedded, lazy-loaded iframe tab, with a best-effort Caddy
frame-ancestors patch and an always-available "open in new tab" fallback.
"cscli scenarios remove crowdsecurity/asterisk_bf crowdsecurity/asterisk_user_enum"
was failing silently (stderr suppressed, "|| true" swallowed the non-zero
exit) because these scenarios are members of the crowdsecurity/asterisk
collection, and cscli refuses to remove/disable a collection member
without --force. The un-exempted hub originals kept running side-by-side
with the ASN-exempt local forks the entire time, independently banning the
same traffic with no ASN awareness at all - confirmed live by a ban
showing scenario "crowdsecurity/asterisk_bf" (the hub original) rather
than "local/asterisk_bf" (the fork) for an ASN that had just been exempted.
Fixed in two places:
- crowdsec.sh's initial setup now passes --force and surfaces a clear
warning (with the manual fix command) if it still fails, instead of
silently continuing.
- security-dashboard's set-asn-exempt.sh helper now re-asserts the forced
disable on every ASN-list save, so an install already affected by this
(from before this fix, or where the step failed for any other reason)
self-heals the next time the ASN list is edited via the dashboard,
without needing to re-run the full CrowdSec installer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
Confirmed live: unquoted integer literals in the ASN exclusion filter
(evt.Enriched.ASNNumber in [21928, 14593]) made CrowdSec fatal-crash-loop
at startup with "cannot use string as type int in array" — ASNNumber is
a string field internally despite printing as a bare number in cscli
output, same as IsoCode in the geo-allowlist scenario. Quote each ASN
as a string to match, exactly like the working geo-allowlist pattern.
Confirmed live: a phone roaming WiFi<->mobile on a CGNAT carrier
(Starlink, T-Mobile home internet) got banned by crowdsecurity/asterisk_bf,
either from its own re-registration burst or collaterally from another
customer sharing the same rotating public IP. Forks asterisk_bf and
asterisk_user_enum locally with an ASN exclusion added to their filter,
disabling the hub originals so events aren't double-processed. Scoped
narrowly to Asterisk auth-failure detection only — SSH, web scanning,
and the geo-allowlist scenario are all unaffected, so this doesn't
broadly exempt the carrier from every protection on the box.
Confirmed live: a legitimate SIP device on a CGNAT ISP (Starlink,
T-Mobile home internet) got collaterally banned by
crowdsecurity/asterisk_bf alongside actual bad actors sharing the same
carrier IP. The alert now includes the exact commands (with the banned
IP substituted in) instead of just naming the ban, so recovering from
this doesn't require remembering or looking up cscli syntax.
Misread the previous request as "add Russia to the allowed list" —
it meant the opposite: Russia should stay excluded, same as the other
high-risk/Eastern Europe entries already left out.
Excludes Bulgaria, Czechia, Hungary, Moldova, Poland, Romania, Slovakia,
and Ukraine per user request, while keeping the Balkans and Baltics
(several of which, e.g. Estonia, don't fit the same risk profile despite
the old Cold-War grouping). Russia added back to the allowed list per
explicit user request.
Opt-in prompt that bans any Caddy-fronted web request from outside an
editable North America + Europe country list, via a local CrowdSec
trigger scenario scoped to the existing "type: caddy" acquisition label.
Uses CrowdSec's bundled GeoLite2 enrichment data (already active with no
extra setup) rather than the firewall bouncer's separate MaxMind-key
country-CIDR feature, so no account signup is needed. SSH is untouched
so a bad edit can't lock out the session running the installer.
Renamed services/asterisk-do.sh -> services/asterisk-digital-ocean.sh
(register_service name, install function, install dir, and all prose/
comments) so the whiptail menu shows a clearer, more discoverable name.
Updated the functional cross-references that depend on the old name:
crowdsec.sh's SIP-log auto-detection path and acquisition filename,
caddy.sh's host.docker.internal comment, and the CLAUDE.md/README.md
docs (services table, directory listing, network-wiring example).
Container names, the Docker Compose project name, and the internal
_asterisk_do_* helper function identifiers are left unchanged since
they aren't user-facing and renaming them would add risk for no
benefit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
Step 7 (ntfy ban alerts) always defaulted straight to the public
ntfy.sh, regardless of whether the box (or a homelab) already had a
real ntfy instance. Confusing in practice: this step runs before
asterisk-do's own ntfy extra is dispatched, so even selecting it
wouldn't have helped at prompt time.
Now checks the local ntfy install's own config/server.yml for a
configured base-url (skipping it if it's still the ntfy.sh-written
placeholder) and uses <base-url>/crowdsec-alerts as the default. If
there's no configured local instance, it says so explicitly and
prompts toward a hosted instance elsewhere (e.g. a homelab) instead of
silently assuming the public service. Verified all three cases
(configured local, unconfigured placeholder, none) in isolation, plus
a full regression run.
Both let the DO droplet lean on services already running on a
homelab instead of duplicating them locally, per the RAM-budget
discussion (Authelia+Redis and a second CrowdSec LAPI+DB add up).
crowdsec.sh: new step lets this agent register against a remote LAPI
(cscli lapi register -u <url>) and disables its own local API server
by removing the api.server block from config.yaml (backed up first;
verified the exact block boundaries against CrowdSec's actual default
config.yaml from upstream before writing the awk removal). Parsers,
scenarios, and the firewall bouncer still run locally regardless —
only banning decisions centralize, and only after the registration is
approved with `cscli machines validate` on the central machine, which
this script can't do since that's a different box. The final restart
step is skipped with an explanation when registration is pending,
instead of showing a misleading "failed to restart" for an expected
state.
asterisk-do.sh: when no local Authelia is installed, the web-admin
Caddy step now offers a remote Authelia option instead, building the
same forward_auth block inline (authelia.sh's shared Caddy snippet
only exists for local installs) targeting either a bare host:port
(e.g. a NetBird mesh IP) or a full https:// URL. Documents that this
couples web-admin availability to the remote instance's reachability,
while SIP/calling on the droplet stays unaffected either way.
Both changes verified: the config.yaml block-removal awk logic tested
against CrowdSec's real upstream default file structure, the remote
Authelia forward_auth block construction tested in isolation, and
full regression runs confirm the default (declined) path through both
new prompts is unchanged.
Vendor's logger.conf only sent Asterisk's security-level log lines
(auth failures, SIP registration scanning) to the console, i.e.
Docker's stdout — not a file CrowdSec could tail. asterisk-do.sh now
patches its copy of entrypoint.sh (vendor/ untouched) to also write
those events to /var/log/asterisk/full, which is bind-mounted to
~/docker/asterisk-do/logs/full on the host.
crowdsec.sh now detects that directory and, if present, installs the
crowdsecurity/asterisk collection (asterisk_bf + asterisk_user_enum
scenarios) with a matching log acquisition — mirroring the existing
Caddy detection pattern. Order-independent: asterisk-do's install
summary tells the user to rerun crowdsec if it's already installed,
since detection only runs during crowdsec's own install step.
New site config key: CADDY_REMOTE_HOST (set via 'sudo ./setup.sh configure').
When set, configure_caddy_for_service operates in "remote" mode instead of
writing to a local Caddyfile:
- Upstream uses CADDY_REMOTE_HOST:PORT (host IP, not container name)
- Snippet saved to ~/docker/caddy-snippets/<subdomain>.caddy
- User is shown scp/rsync commands to copy it to the Caddy machine
Three modes in configure_caddy_for_service (lib/common.sh and inline stubs):
local: ~/docker/caddy/ exists → write Caddyfile + reload (existing behavior)
remote: CADDY_REMOTE_HOST set → save snippet, print copy instructions
none: neither configured → silent return (unchanged)
All 31 service standalone bootstrap stubs updated with the new logic.
CADDY_REMOTE_HOST global added to all 42 standalone bootstrap sections.
setup.sh configure now prompts for CADDY_REMOTE_HOST with a clear explanation.
wolf.sh: add missing stubs (configure_caddy_for_service, write_readme,
prompt_yn, ensure_docker_dir_ownership) and the Authelia/Caddy/start calls
that were missing from the install function.
https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
Each service can now be run directly with sudo bash <service>.sh on any
machine with Docker installed, without needing the full post-install repo.
Uses the shared bootstrap pattern from docs/standalone-template.sh.
https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt