Add prompt_reinstall_mode helper and document the update/fresh/cancel convention
Replaces the y/n "update in place?" prompt in asterisk.sh/asterisk-do.sh with an explicit r/f/c choice — (r)einstall in place, (f)ull install, (c)ancel — defaulting to cancel on a bare Enter (or Ctrl-D) instead of falling through to a destructive full reinstall. Adds prompt_reinstall_mode to lib/common.sh (plus matching standalone stubs in both asterisk scripts for when they run without the full repo) and documents the convention in CLAUDE.md: any service with a persistent install directory should offer this choice on rerun instead of re-asking every prompt just to pick up a script fix.
This commit is contained in:
@@ -125,9 +125,12 @@ log_error "message" # red [ERROR]
|
||||
```bash
|
||||
prompt_yn "Question? (y/n):" "default_y_or_n" VARNAME
|
||||
prompt_text "Question? [default]:" "default" VARNAME
|
||||
prompt_reinstall_mode VARNAME # sets VARNAME to: update | fresh | cancel
|
||||
```
|
||||
|
||||
When `UNATTENDED=true` both functions skip the prompt and use the default.
|
||||
When `UNATTENDED=true` all three skip the prompt; `prompt_yn`/`prompt_text` use
|
||||
their given default, `prompt_reinstall_mode` always resolves to `cancel`. See
|
||||
**Update vs. fresh reinstall on rerun** below for how to use the latter.
|
||||
|
||||
### Pre-flight
|
||||
|
||||
@@ -262,6 +265,51 @@ fi
|
||||
Put the check early — after any pure-display output (banners, info text)
|
||||
but before the first write.
|
||||
|
||||
## Update vs. fresh reinstall on rerun
|
||||
|
||||
Every service should detect an existing install at the top of its
|
||||
`install_<name>()` — after the `DRY_RUN` check, before any prompts — and
|
||||
offer `prompt_reinstall_mode` instead of silently re-running every prompt
|
||||
(domain, secrets, firewall, Authelia, extras...) from scratch. What counts
|
||||
as "already installed" is service-specific: usually `docker-compose.yml` and
|
||||
`.env` both existing in the service's `$DOCKER_DIR/<name>` directory.
|
||||
|
||||
```bash
|
||||
if [[ -f "$DIR/docker-compose.yml" && -f "$DIR/.env" ]]; then
|
||||
local MODE=""
|
||||
prompt_reinstall_mode MODE
|
||||
case "$MODE" in
|
||||
update)
|
||||
# Refresh vendor files / config templates, rebuild, done.
|
||||
# Do NOT touch .env, firewall rules, or Caddy/Authelia config.
|
||||
...
|
||||
return 0
|
||||
;;
|
||||
cancel)
|
||||
log_info "Leaving the existing install as-is."
|
||||
return 0
|
||||
;;
|
||||
fresh) ;; # fall through to the full install flow below
|
||||
esac
|
||||
fi
|
||||
```
|
||||
|
||||
`update` should be genuinely non-destructive: refresh whatever the service
|
||||
vendors or templates (Docker image sources, config templates,
|
||||
`docker-compose.yml`) and rebuild/restart, but never touch `.env`, firewall
|
||||
rules, or reverse-proxy/SSO config that's already in place. If the
|
||||
vendor-copy or `docker-compose.yml`-generation logic is more than a few
|
||||
lines, factor it into a helper function so the fresh-install path and the
|
||||
update path share one copy instead of drifting apart — see
|
||||
`_asterisk_do_refresh_vendor_files`/`_asterisk_do_write_compose` in
|
||||
`services/asterisk-do.sh` (and their `_asterisk_*` counterparts in
|
||||
`services/asterisk.sh`) for the reference pattern.
|
||||
|
||||
`cancel` must leave the install completely untouched — it's the default for
|
||||
a reason (a stray Enter on a service you're just checking on shouldn't
|
||||
trigger anything). `fresh` runs the exact same flow a first-time install
|
||||
would, prompts included.
|
||||
|
||||
## .env files and secrets
|
||||
|
||||
Generate passwords with `generate_password` (never hardcode them).
|
||||
|
||||
@@ -362,6 +362,32 @@ prompt_text() {
|
||||
eval "$varname='${response:-$default}'"
|
||||
}
|
||||
|
||||
# Prompt for how to handle a service that's already installed, honoring
|
||||
# unattended. prompt_reinstall_mode VARNAME
|
||||
# Sets VARNAME to one of: update | fresh | cancel
|
||||
# Enter (no input) and any unrecognized input both resolve to "cancel" — this
|
||||
# guards a destructive full reinstall behind a deliberate keypress instead of
|
||||
# a stray Enter. Unattended mode always resolves to "cancel" too: never
|
||||
# silently touch an existing install when nobody's watching the prompt.
|
||||
prompt_reinstall_mode() {
|
||||
local varname="$1" response
|
||||
if [ "$UNATTENDED" = true ]; then
|
||||
eval "$varname='cancel'"
|
||||
echo "Existing install detected — leaving it as-is [auto: cancel, unattended mode]"
|
||||
return
|
||||
fi
|
||||
echo " Existing install detected. Choose:"
|
||||
echo " r) Reinstall in place — refresh vendor files/config, keep existing settings"
|
||||
echo " f) Full install — re-run every prompt from scratch"
|
||||
echo " c) Cancel — leave everything as-is [default]"
|
||||
read -p " Choice [r/f/c, Enter=cancel]: " response
|
||||
case "${response,,}" in
|
||||
r) eval "$varname='update'" ;;
|
||||
f) eval "$varname='fresh'" ;;
|
||||
*) eval "$varname='cancel'" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Per-service README generation ────────────────────────────────────────────
|
||||
# Write <dir>/README.md from stdin (markdown). Every module is encouraged to
|
||||
# call this so each ~/docker/<service>/ folder is self-documenting.
|
||||
|
||||
+57
-30
@@ -61,6 +61,25 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
eval "$_var='${_r:-$_def}'"
|
||||
}
|
||||
|
||||
prompt_reinstall_mode() {
|
||||
local _var="$1" _r
|
||||
if [[ "${UNATTENDED:-false}" == "true" ]]; then
|
||||
eval "$_var='cancel'"
|
||||
echo "Existing install detected — leaving it as-is [auto: cancel, unattended mode]"
|
||||
return
|
||||
fi
|
||||
echo " Existing install detected. Choose:"
|
||||
echo " r) Reinstall in place — refresh vendor files/config, keep existing settings"
|
||||
echo " f) Full install — re-run every prompt from scratch"
|
||||
echo " c) Cancel — leave everything as-is [default]"
|
||||
read -r -p " Choice [r/f/c, Enter=cancel]: " _r
|
||||
case "${_r,,}" in
|
||||
r) eval "$_var='update'" ;;
|
||||
f) eval "$_var='fresh'" ;;
|
||||
*) eval "$_var='cancel'" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
configure_caddy_for_service() {
|
||||
local _name="$1" _upstream="$2" _subdomain="$3" _extra="${4:-}"
|
||||
local _caddy_dir="$DOCKER_DIR/caddy"
|
||||
@@ -353,39 +372,47 @@ install_asterisk-do() {
|
||||
if [[ -f "$EA_DIR/docker-compose.yml" && -f "$EA_DIR/.env" ]]; then
|
||||
echo ""
|
||||
log_info "Existing install found at $EA_DIR."
|
||||
local UPDATE_INPLACE=""
|
||||
prompt_yn "Update in place (refresh vendor files + docker-compose.yml, keep your existing domain/firewall/Authelia settings) instead of a full fresh reinstall? (y/n):" "y" UPDATE_INPLACE
|
||||
if [[ "$UPDATE_INPLACE" =~ ^[Yy]$ ]]; then
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
local REINSTALL_MODE=""
|
||||
prompt_reinstall_mode REINSTALL_MODE
|
||||
case "$REINSTALL_MODE" in
|
||||
update)
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
_asterisk_do_refresh_vendor_files
|
||||
_asterisk_do_write_compose
|
||||
_asterisk_do_refresh_vendor_files
|
||||
_asterisk_do_write_compose
|
||||
|
||||
log_info "Rebuilding and restarting containers..."
|
||||
if docker compose up -d --build --force-recreate; then
|
||||
log_success "Update complete — vendor files and docker-compose.yml refreshed."
|
||||
else
|
||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||
fi
|
||||
log_info "Rebuilding and restarting containers..."
|
||||
if docker compose up -d --build --force-recreate; then
|
||||
log_success "Update complete — vendor files and docker-compose.yml refreshed."
|
||||
else
|
||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||
fi
|
||||
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||
echo ""
|
||||
log_success "Existing .env, UFW rules, Cloud Firewall, and Caddy/Authelia config were left untouched."
|
||||
if [[ -n "$_EXISTING_DOMAIN" ]]; then
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
echo " Web admin: http://<droplet-ip>:${_EXISTING_PORT:-8081}"
|
||||
fi
|
||||
echo " Logs: docker compose -f $EA_DIR/docker-compose.yml logs -f"
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
log_info "Proceeding with a full fresh reinstall — existing config will be reused where prompts match, everything else re-asked."
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||
echo ""
|
||||
log_success "Existing .env, UFW rules, Cloud Firewall, and Caddy/Authelia config were left untouched."
|
||||
if [[ -n "$_EXISTING_DOMAIN" ]]; then
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
echo " Web admin: http://<droplet-ip>:${_EXISTING_PORT:-8081}"
|
||||
fi
|
||||
echo " Logs: docker compose -f $EA_DIR/docker-compose.yml logs -f"
|
||||
echo ""
|
||||
return 0
|
||||
;;
|
||||
cancel)
|
||||
log_info "Leaving the existing install as-is — nothing changed."
|
||||
return 0
|
||||
;;
|
||||
fresh)
|
||||
log_info "Proceeding with a full fresh reinstall — every prompt below runs from scratch."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ── Bring in base first, if this is a genuinely fresh box ─────────────────
|
||||
|
||||
+57
-30
@@ -59,6 +59,25 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
eval "$_var='${_r:-$_def}'"
|
||||
}
|
||||
|
||||
prompt_reinstall_mode() {
|
||||
local _var="$1" _r
|
||||
if [[ "${UNATTENDED:-false}" == "true" ]]; then
|
||||
eval "$_var='cancel'"
|
||||
echo "Existing install detected — leaving it as-is [auto: cancel, unattended mode]"
|
||||
return
|
||||
fi
|
||||
echo " Existing install detected. Choose:"
|
||||
echo " r) Reinstall in place — refresh vendor files/config, keep existing settings"
|
||||
echo " f) Full install — re-run every prompt from scratch"
|
||||
echo " c) Cancel — leave everything as-is [default]"
|
||||
read -r -p " Choice [r/f/c, Enter=cancel]: " _r
|
||||
case "${_r,,}" in
|
||||
r) eval "$_var='update'" ;;
|
||||
f) eval "$_var='fresh'" ;;
|
||||
*) eval "$_var='cancel'" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
configure_caddy_for_service() {
|
||||
local _name="$1" _upstream="$2" _subdomain="$3" _extra="${4:-}"
|
||||
local _caddy_dir="$DOCKER_DIR/caddy"
|
||||
@@ -333,39 +352,47 @@ install_asterisk() {
|
||||
if [[ -f "$EA_DIR/docker-compose.yml" && -f "$EA_DIR/.env" ]]; then
|
||||
echo ""
|
||||
log_info "Existing install found at $EA_DIR."
|
||||
local UPDATE_INPLACE=""
|
||||
prompt_yn "Update in place (refresh vendor files + docker-compose.yml, keep your existing domain/VLAN/Authelia settings) instead of a full fresh reinstall? (y/n):" "y" UPDATE_INPLACE
|
||||
if [[ "$UPDATE_INPLACE" =~ ^[Yy]$ ]]; then
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
local REINSTALL_MODE=""
|
||||
prompt_reinstall_mode REINSTALL_MODE
|
||||
case "$REINSTALL_MODE" in
|
||||
update)
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
_asterisk_refresh_vendor_files
|
||||
_asterisk_write_compose
|
||||
_asterisk_refresh_vendor_files
|
||||
_asterisk_write_compose
|
||||
|
||||
log_info "Rebuilding and restarting containers..."
|
||||
if docker compose up -d --build --force-recreate; then
|
||||
log_success "Update complete — vendor files and docker-compose.yml refreshed."
|
||||
else
|
||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||
fi
|
||||
log_info "Rebuilding and restarting containers..."
|
||||
if docker compose up -d --build --force-recreate; then
|
||||
log_success "Update complete — vendor files and docker-compose.yml refreshed."
|
||||
else
|
||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||
fi
|
||||
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||
echo ""
|
||||
log_success "Existing .env, UFW rules, and Caddy/Authelia config were left untouched."
|
||||
if [[ -n "$_EXISTING_DOMAIN" ]]; then
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
echo " Web admin: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):${_EXISTING_PORT:-8081}"
|
||||
fi
|
||||
echo " Logs: docker compose -f $EA_DIR/docker-compose.yml logs -f"
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
log_info "Proceeding with a full fresh reinstall — existing config will be reused where prompts match, everything else re-asked."
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||
echo ""
|
||||
log_success "Existing .env, UFW rules, and Caddy/Authelia config were left untouched."
|
||||
if [[ -n "$_EXISTING_DOMAIN" ]]; then
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
echo " Web admin: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):${_EXISTING_PORT:-8081}"
|
||||
fi
|
||||
echo " Logs: docker compose -f $EA_DIR/docker-compose.yml logs -f"
|
||||
echo ""
|
||||
return 0
|
||||
;;
|
||||
cancel)
|
||||
log_info "Leaving the existing install as-is — nothing changed."
|
||||
return 0
|
||||
;;
|
||||
fresh)
|
||||
log_info "Proceeding with a full fresh reinstall — every prompt below runs from scratch."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
mkdir -p "$EA_DIR"
|
||||
|
||||
Reference in New Issue
Block a user