Rename reinstall-mode prompt; make security-dashboard's "Full reinstall" a real teardown

Two-part change discussed and scoped in this session before touching
anything:

1. Rename "Reinstall in place" (r) -> "Update" (u) and "Full install" (f)
   -> "Full reinstall" everywhere the prompt appears: lib/common.sh's
   shared prompt_reinstall_mode(), plus the three services that carry
   their own duplicated standalone-stub copy of it for standalone
   execution (asterisk.sh, coturn.sh, wordpress.sh — per this repo's
   documented standalone-bootstrap pattern). Internal state values
   (update/fresh/cancel) are unchanged, so no other service's case
   statement needed touching. docs/anveo-direct-setup-guide.md's `r`
   reference updated to `u` to match. attic/asterisk-digital-ocean.sh
   deliberately left alone — this repo's own policy is to not backport
   fixes into attic/.

2. security-dashboard.sh's "Full reinstall" now does a real teardown
   before reinstalling — stops and removes the systemd unit, sudoers
   grant, Caddy site block, and secdash system user, then proceeds
   through the normal fresh-install flow — instead of just overwriting
   files in place while leaving the old service running underneath.
   Prototype for a pattern discussed for other services later: split the
   destructive question out explicitly ("also delete
   dashboard-admins.conf — per-admin extension scoping?", default n) so
   full reinstall doesn't silently discard state a plain "start over"
   request wouldn't expect to lose. Verified the backup/restore mechanics
   (mktemp, copy out before teardown, copy back after) against a mock
   under `set -u` for both the preserve and wipe paths before shipping.

Update mode was already the strongest existing example of surfacing
newer optional prompts (its "Reconfigure Caddy protection?" /
"Reconfigure per-admin scoping?" sub-prompts already cover every setting
fresh-install offers) — no changes needed there for this service.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
This commit is contained in:
Claude
2026-08-12 12:47:31 +00:00
parent d55a7cc81a
commit 7a1f09a0f7
6 changed files with 69 additions and 18 deletions
+1 -1
View File
@@ -143,7 +143,7 @@ account default in step 6 and skip even that:
sudo ./setup.sh pstn-trunk sudo ./setup.sh pstn-trunk
``` ```
- Existing install → choose **update** (`r`) if you're just changing the - Existing install → choose **update** (`u`) if you're just changing the
DID/server, or the CLI will walk fresh prompts if none exists yet. DID/server, or the CLI will walk fresh prompts if none exists yet.
- Provider quick-pick: **1) Anveo Direct** — pre-fills `sbc.anveo.com` and - Provider quick-pick: **1) Anveo Direct** — pre-fills `sbc.anveo.com` and
Anveo's 4 published signaling IPs (only one of which the hostname Anveo's 4 published signaling IPs (only one of which the hostname
+4 -4
View File
@@ -718,12 +718,12 @@ prompt_reinstall_mode() {
return return
fi fi
echo " Existing install detected. Choose:" echo " Existing install detected. Choose:"
echo " r) Reinstall in place — refresh vendor files/config, keep existing settings" echo " u) Update — refresh vendor files/config, keep existing settings"
echo " f) Full install — re-run every prompt from scratch" echo " f) Full reinstall — re-run every prompt from scratch"
echo " c) Cancel — leave everything as-is [default]" echo " c) Cancel — leave everything as-is [default]"
read -p " Choice [r/f/c, Enter=cancel]: " response read -p " Choice [u/f/c, Enter=cancel]: " response
case "${response,,}" in case "${response,,}" in
r) eval "$varname='update'" ;; u) eval "$varname='update'" ;;
f) eval "$varname='fresh'" ;; f) eval "$varname='fresh'" ;;
*) eval "$varname='cancel'" ;; *) eval "$varname='cancel'" ;;
esac esac
+4 -4
View File
@@ -82,12 +82,12 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
return return
fi fi
echo " Existing install detected. Choose:" echo " Existing install detected. Choose:"
echo " r) Reinstall in place — refresh vendor files/config, keep existing settings" echo " u) Update — refresh vendor files/config, keep existing settings"
echo " f) Full install — re-run every prompt from scratch" echo " f) Full reinstall — re-run every prompt from scratch"
echo " c) Cancel — leave everything as-is [default]" echo " c) Cancel — leave everything as-is [default]"
read -r -p " Choice [r/f/c, Enter=cancel]: " _r read -r -p " Choice [u/f/c, Enter=cancel]: " _r
case "${_r,,}" in case "${_r,,}" in
r) eval "$_var='update'" ;; u) eval "$_var='update'" ;;
f) eval "$_var='fresh'" ;; f) eval "$_var='fresh'" ;;
*) eval "$_var='cancel'" ;; *) eval "$_var='cancel'" ;;
esac esac
+4 -4
View File
@@ -87,12 +87,12 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
local _var="$1" _r local _var="$1" _r
if [[ "${UNATTENDED:-false}" == "true" ]]; then eval "$_var='cancel'"; return; fi if [[ "${UNATTENDED:-false}" == "true" ]]; then eval "$_var='cancel'"; return; fi
echo " Existing install detected. Choose:" echo " Existing install detected. Choose:"
echo " r) Reinstall in place — refresh vendor files, keep existing settings" echo " u) Update — refresh vendor files, keep existing settings"
echo " f) Full install — re-run every prompt from scratch" echo " f) Full reinstall — re-run every prompt from scratch"
echo " c) Cancel — leave everything as-is [default]" echo " c) Cancel — leave everything as-is [default]"
read -r -p " Choice [r/f/c, Enter=cancel]: " _r read -r -p " Choice [u/f/c, Enter=cancel]: " _r
case "${_r,,}" in case "${_r,,}" in
r) eval "$_var='update'" ;; u) eval "$_var='update'" ;;
f) eval "$_var='fresh'" ;; f) eval "$_var='fresh'" ;;
*) eval "$_var='cancel'" ;; *) eval "$_var='cancel'" ;;
esac esac
+52 -1
View File
@@ -184,7 +184,24 @@ install_security-dashboard() {
log_info "Leaving the existing install as-is." log_info "Leaving the existing install as-is."
return 0 return 0
;; ;;
fresh) ;; fresh)
echo ""
log_warning "Full reinstall stops the dashboard and removes its systemd unit,"
log_warning "sudoers grant, Caddy block, service user, and app files, then sets"
log_warning "it up again from scratch — every prompt below runs as if this were"
log_warning "a brand new install."
local _WIPE_ADMINS=""
prompt_yn " Also delete dashboard-admins.conf (per-admin Calls/Texts/Voicemail scoping)? (y/n):" "n" _WIPE_ADMINS
local _ADMINS_BACKUP=""
if [[ ! "$_WIPE_ADMINS" =~ ^[Yy]$ ]] && [ -f "$APP_DIR/dashboard-admins.conf" ]; then
_ADMINS_BACKUP="$(mktemp)"
cp "$APP_DIR/dashboard-admins.conf" "$_ADMINS_BACKUP"
log_info "Preserving dashboard-admins.conf across the reinstall."
fi
_secdash_teardown "$APP_DIR" "$SVC_USER" "$DASHBOARD_PORT"
;;
esac esac
fi fi
@@ -197,6 +214,11 @@ install_security-dashboard() {
_secdash_grant_asterisk_access "$SVC_USER" "$ASTERISK_LOG_DIR" "$ASTERISK_CONFIG_DIR" "$ASTERISK_EA_CONFIG_DIR" "$ASTERISK_SPOOL_DIR" _secdash_grant_asterisk_access "$SVC_USER" "$ASTERISK_LOG_DIR" "$ASTERISK_CONFIG_DIR" "$ASTERISK_EA_CONFIG_DIR" "$ASTERISK_SPOOL_DIR"
mkdir -p "$APP_DIR" mkdir -p "$APP_DIR"
if [ -n "${_ADMINS_BACKUP:-}" ]; then
cp "$_ADMINS_BACKUP" "$APP_DIR/dashboard-admins.conf"
rm -f "$_ADMINS_BACKUP"
log_success "Restored dashboard-admins.conf"
fi
_secdash_write_app "$APP_DIR" _secdash_write_app "$APP_DIR"
chown -R "$SVC_USER:$SVC_USER" "$APP_DIR" chown -R "$SVC_USER:$SVC_USER" "$APP_DIR"
_secdash_write_asn_helper "$APP_DIR" _secdash_write_asn_helper "$APP_DIR"
@@ -974,6 +996,35 @@ _secdash_remove_caddy_block() {
log_info "Removed the existing dashboard Caddy block (regenerating it fresh)." log_info "Removed the existing dashboard Caddy block (regenerating it fresh)."
} }
# Full teardown for "Full reinstall" — stops the service and removes
# everything a fresh install recreates: systemd unit, sudoers grant, Caddy
# site block, the secdash system user, and the app directory. Non-Docker
# service (systemd + /opt, not a container), so lib/common.sh's
# remove_service() (Docker-only, $DOCKER_DIR/<name>) doesn't apply here —
# this is the equivalent for this one service. Callers are responsible for
# backing up/restoring anything under $_app_dir they want to survive (see
# the dashboard-admins.conf handling around the "fresh" case in
# install_security-dashboard() — this function does not know which files,
# if any, the caller wants to keep).
_secdash_teardown() {
local _app_dir="$1" _svc_user="$2" _port="$3"
systemctl stop security-dashboard 2>/dev/null || true
systemctl disable security-dashboard 2>/dev/null || true
rm -f /etc/systemd/system/security-dashboard.service
systemctl daemon-reload
rm -f /etc/sudoers.d/security-dashboard
_secdash_remove_caddy_block "$_port"
id "$_svc_user" &>/dev/null && userdel "$_svc_user" 2>/dev/null
rm -rf "$_app_dir"
log_success "Removed security-dashboard's systemd unit, sudoers grant, Caddy block, user, and app files."
}
# Root-owned helper for editing CrowdSec's Asterisk-scenario YAMLs — the # Root-owned helper for editing CrowdSec's Asterisk-scenario YAMLs — the
# secdash service user (--shell /usr/sbin/nologin, no special file grants) # secdash service user (--shell /usr/sbin/nologin, no special file grants)
# cannot write /etc/crowdsec/scenarios/*.yaml directly (root:root, mode # cannot write /etc/crowdsec/scenarios/*.yaml directly (root:root, mode
+4 -4
View File
@@ -95,12 +95,12 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
local _var="$1" _r local _var="$1" _r
if [[ "${UNATTENDED:-false}" == "true" ]]; then eval "$_var='cancel'"; return; fi if [[ "${UNATTENDED:-false}" == "true" ]]; then eval "$_var='cancel'"; return; fi
echo " Existing install detected. Choose:" echo " Existing install detected. Choose:"
echo " r) Reinstall in place — refresh image/compose, keep database and settings" echo " u) Update — refresh image/compose, keep database and settings"
echo " f) Full install — re-run every prompt from scratch" echo " f) Full reinstall — re-run every prompt from scratch"
echo " c) Cancel — leave everything as-is [default]" echo " c) Cancel — leave everything as-is [default]"
read -r -p " Choice [r/f/c, Enter=cancel]: " _r read -r -p " Choice [u/f/c, Enter=cancel]: " _r
case "${_r,,}" in case "${_r,,}" in
r) eval "$_var='update'" ;; u) eval "$_var='update'" ;;
f) eval "$_var='fresh'" ;; f) eval "$_var='fresh'" ;;
*) eval "$_var='cancel'" ;; *) eval "$_var='cancel'" ;;
esac esac