From c7f9e5caa10059105e672ed2a5de24000d45a659 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 16:03:19 +0000 Subject: [PATCH 1/3] Add a * marker next to the checkbox for already-installed services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The [installed] text label (previous commit) confirmed working from a screenshot, but the checkbox itself stays unchecked for installed items by design — checking it means "install/reinstall this on ", so pre-checking every already-installed service would risk a mass reinstall from just hitting Ok without manually unchecking each one. Add a second, more immediate cue right next to the checkbox instead: prefix the item's own tag with "*" when installed (whiptail's checklist tag is the first column, directly after the checkbox). The "*" is display-only — stripped back off the selected values before they reach run_service, so dispatch is unaffected. --- setup.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/setup.sh b/setup.sh index 879a3ef..c961ab1 100755 --- a/setup.sh +++ b/setup.sh @@ -402,6 +402,7 @@ while true; do svc_items=() for name in "${SVCS[@]}"; do tag="${SERVICE_DESC[$name]}" + svc_tag="$name" # Marker goes at the FRONT, not appended after the description. # whiptail hard-truncates each row to the dialog's fixed width # (78 here) with no ellipsis or other sign it happened — @@ -412,13 +413,24 @@ while true; do # anything was cut. A long description can still lose its own # tail this way, but that's harmless — the install status is # what actually matters and now always survives. - is_installed "$name" && tag="[installed] $tag" - svc_items+=("$name" "$tag" "OFF") + # Also prefix the name itself with "*" — the checkbox's own + # [ ]/[*] state is reserved for "install this now", so an + # already-installed item stays unchecked by design (checking it + # would reinstall on ); this "*" sits right next to the + # checkbox as a second, harder-to-miss cue distinct from that + # selection state. Stripped back off below before dispatch. + if is_installed "$name"; then + tag="[installed] $tag" + svc_tag="*$name" + fi + svc_items+=("$svc_tag" "$tag" "OFF") done CHOICE=$(whiptail --title "${CHOSEN_CAT^^}" --checklist \ - "Space to select, Enter to install. Already-installed are marked:" "$_box_h" 78 "$_list_h" \ + "Space to select, Enter to install. Already-installed are marked with *:" "$_box_h" 78 "$_list_h" \ "${svc_items[@]}" 3>&1 1>&2 2>&3 Date: Mon, 10 Aug 2026 16:10:31 +0000 Subject: [PATCH 2/3] Replace "[installed]" text with an install count "[N]" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "[installed]" was 11 characters of an already-tight 78-column checklist row, most of the reason the marker had so little room to spare before whiptail's width truncation silently dropped it (previous commit). "[N]" says the same thing in 3 characters — and for services that support CLAUDE.md's multi-instance pattern (a base install plus any number of "-" siblings, e.g. two separate mattermost instances), it's more informative than a flat "installed": N > 1 means several instances exist, not just one. Add install_count() alongside is_installed() in setup.sh: the default case counts $DOCKER_DIR/ plus any $DOCKER_DIR/-* siblings; the specially-cased services (asterisk, wordpress, etc.) either already count sites directly (wordpress) or aren't part of the multi-instance pattern, so they just mirror is_installed() as 0 or 1. Wired into the whiptail checklist, the non-whiptail plain-text fallback, and --status. --- setup.sh | 61 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/setup.sh b/setup.sh index c961ab1..2d8c41b 100755 --- a/setup.sh +++ b/setup.sh @@ -109,6 +109,28 @@ is_installed() { esac } +# How many instances of a service are installed — most services here are +# single-instance, but several support the multi-instance pattern from +# CLAUDE.md (a base install plus any number of "-" siblings, +# e.g. mattermost + mattermost-team-b). Only the default case knows that +# naming convention; the specially-cased services above aren't part of the +# multi-instance pattern (wordpress is the one exception and already counts +# sites directly), so for those this just mirrors is_installed() as 0 or 1. +install_count() { + case "$1" in + base|glow|crowdsec|security-dashboard|kdeconnect|silent-send|sync-cc|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config) + is_installed "$1" && echo 1 || echo 0 ;; + wordpress) + find "$DOCKER_DIR" -mindepth 1 -maxdepth 1 -name 'wordpress-*' -type d 2>/dev/null | wc -l ;; + *) + local c=0 + [ -e "$DOCKER_DIR/$1" ] && c=1 + c=$((c + $(find "$DOCKER_DIR" -mindepth 1 -maxdepth 1 -name "$1-*" -type d 2>/dev/null | wc -l))) + echo "$c" + ;; + esac +} + run_service() { local name="$1" if [ -n "${SERVICE_ALIAS[$name]:-}" ]; then @@ -139,13 +161,14 @@ list_services() { # the "[installed]" suffix off-screen without it being obvious that's what # happened. print_status() { - local g name marker + local g name marker count while IFS= read -r g; do echo ""; echo "── ${g^^} ──" while IFS= read -r name; do + count="$(install_count "$name")" marker="not installed" - is_installed "$name" && marker="INSTALLED" - printf " %-20s %-12s %s\n" "$name" "$marker" "${SERVICE_DESC[$name]}" + [ "$count" -gt 0 ] && marker="INSTALLED (x$count)" + printf " %-20s %-16s %s\n" "$name" "$marker" "${SERVICE_DESC[$name]}" done < <(services_in_group "$g") done < <(groups_present) echo "" @@ -410,23 +433,28 @@ while true; do # description (fmd's is 68 chars; +13 for the suffix is 81, # past the width) silently drops the marker off the end, making # an installed service look uninstalled with no visual cue - # anything was cut. A long description can still lose its own - # tail this way, but that's harmless — the install status is - # what actually matters and now always survives. - # Also prefix the name itself with "*" — the checkbox's own - # [ ]/[*] state is reserved for "install this now", so an - # already-installed item stays unchecked by design (checking it - # would reinstall on ); this "*" sits right next to the - # checkbox as a second, harder-to-miss cue distinct from that - # selection state. Stripped back off below before dispatch. - if is_installed "$name"; then - tag="[installed] $tag" + # anything was cut. "[installed]" itself also ate a lot of the + # already-tight width just by being 11 characters wide — "[N]" + # (install count) says the same thing in 3, and for the + # multi-instance services (CLAUDE.md's pattern — a base install + # plus any number of "-" siblings) it's more + # informative than a flat "installed", since N > 1 means several + # separate instances exist. + _count="$(install_count "$name")" + if [ "$_count" -gt 0 ]; then + tag="[$_count] $tag" + # The checkbox's own [ ]/[*] state is reserved for "install + # this now", so an already-installed item stays unchecked by + # design (checking it would reinstall on ); prefixing + # the name itself with "*" gives a second, harder-to-miss + # cue right next to the checkbox, distinct from that + # selection state. Stripped back off below before dispatch. svc_tag="*$name" fi svc_items+=("$svc_tag" "$tag" "OFF") done CHOICE=$(whiptail --title "${CHOSEN_CAT^^}" --checklist \ - "Space to select, Enter to install. Already-installed are marked with *:" "$_box_h" 78 "$_list_h" \ + "Space to select, Enter to install. [N] = N instance(s) already installed:" "$_box_h" 78 "$_list_h" \ "${svc_items[@]}" 3>&1 1>&2 2>&3 Date: Mon, 10 Aug 2026 16:18:34 +0000 Subject: [PATCH 3/3] Fake dedicated "installed"/"#" columns in the checklist via a fixed-width tag prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested: separate, non-interactive "installed" (x) and "#" (instance count) columns ahead of the actual selectable checkbox, with the description no longer carrying any install-status text at all. whiptail's checklist only has one interactive element per row — the checkbox — so there's no such thing as a real extra column, tabbable or not; the tag and item fields are always just inert display text regardless of what's in them. The closest real equivalent: bake a fixed-width "x" (installed) + count prefix into the tag field itself. whiptail pads every row's tag field to the same width, so it lines up visually like columns even though it's one string underneath. Extract the plain name back out before dispatch by taking the last whitespace-separated token, since service names never contain spaces — robust regardless of the exact prefix width. Description field is back to plain SERVICE_DESC text now that install status lives in the tag prefix instead. --- setup.sh | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/setup.sh b/setup.sh index 2d8c41b..5ec5103 100755 --- a/setup.sh +++ b/setup.sh @@ -422,43 +422,35 @@ while true; do _box_h=$((_list_h + 8)) [ "$_box_h" -gt "$((_term_lines - 2))" ] && _box_h=$((_term_lines - 2)) + # whiptail's checklist has exactly one interactive element per row — + # the checkbox itself. The "tag" field (what's displayed right after + # it) and the "item" field (description) are both just inert display + # text; there is no such thing as a separately tabbable/selectable + # sub-field within a row. So a fixed-width "installed" (x) and "#" + # (instance count) prefix baked into the tag field is the closest + # equivalent to real extra columns whiptail can render — it lines up + # visually because whiptail pads every row's tag field to the same + # width, but it's still one string under the hood. Extracted back + # into just the plain service name below before dispatch. svc_items=() for name in "${SVCS[@]}"; do - tag="${SERVICE_DESC[$name]}" - svc_tag="$name" - # Marker goes at the FRONT, not appended after the description. - # whiptail hard-truncates each row to the dialog's fixed width - # (78 here) with no ellipsis or other sign it happened — - # confirmed live: appending " [installed]" after a long enough - # description (fmd's is 68 chars; +13 for the suffix is 81, - # past the width) silently drops the marker off the end, making - # an installed service look uninstalled with no visual cue - # anything was cut. "[installed]" itself also ate a lot of the - # already-tight width just by being 11 characters wide — "[N]" - # (install count) says the same thing in 3, and for the - # multi-instance services (CLAUDE.md's pattern — a base install - # plus any number of "-" siblings) it's more - # informative than a flat "installed", since N > 1 means several - # separate instances exist. + _inst_mark=" " + _count_str=" " _count="$(install_count "$name")" if [ "$_count" -gt 0 ]; then - tag="[$_count] $tag" - # The checkbox's own [ ]/[*] state is reserved for "install - # this now", so an already-installed item stays unchecked by - # design (checking it would reinstall on ); prefixing - # the name itself with "*" gives a second, harder-to-miss - # cue right next to the checkbox, distinct from that - # selection state. Stripped back off below before dispatch. - svc_tag="*$name" + _inst_mark="x" + _count_str="$(printf "%-2d" "$_count")" fi - svc_items+=("$svc_tag" "$tag" "OFF") + svc_tag="$(printf "%s %s %s" "$_inst_mark" "$_count_str" "$name")" + svc_items+=("$svc_tag" "${SERVICE_DESC[$name]}" "OFF") done CHOICE=$(whiptail --title "${CHOSEN_CAT^^}" --checklist \ - "Space to select, Enter to install. [N] = N instance(s) already installed:" "$_box_h" 78 "$_list_h" \ + "installed(x) #instances name Space=select to install, Enter=go:" "$_box_h" 78 "$_list_h" \ "${svc_items[@]}" 3>&1 1>&2 2>&3