413 lines
17 KiB
Bash
Executable File
413 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
KUBECONFIG="${KUBECONFIG:-${KUBECONFIG_PATH:-${HOME}/.kube/config}}"
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
GRAFANA_BASE_URL="${LAB_GRAFANA_URL:-https://grafana.${LAB_DOMAIN:-lab2025.duckdns.org}}"
|
|
GRAFANA_CAPACITY_URL="${GRAFANA_BASE_URL%/}/d/homelab-capacity/homelab-capacity?orgId=1"
|
|
GRAFANA_AVAILABILITY_URL="${GRAFANA_BASE_URL%/}/d/homelab-availability/homelab-availability?orgId=1"
|
|
|
|
DETAIL_ARGS=()
|
|
VERBOSE=false
|
|
|
|
while (($# > 0)); do
|
|
case "$1" in
|
|
--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
--details|--json)
|
|
DETAIL_ARGS+=("$1")
|
|
shift
|
|
;;
|
|
--only)
|
|
DETAIL_ARGS+=("$1" "${2:-all}")
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
cat <<'EOF'
|
|
Usage: ./jeannie capacity [--verbose] [--details] [--json] [--only failures|warnings|problems|all]
|
|
|
|
Default output is a compact reusable report. Use --verbose for the older full
|
|
capacity dump.
|
|
EOF
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "unknown capacity option: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
emit() {
|
|
local status="$1"
|
|
local area="$2"
|
|
local check="$3"
|
|
local summary="$4"
|
|
local detail="${5:-}"
|
|
local fix="${6:-}"
|
|
local graph="${7:-}"
|
|
local query="${8:-}"
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "${status}" "${area}" "${check}" "${summary}" "${detail}" "${fix}" "${graph}" "${query}"
|
|
}
|
|
|
|
grafana_explore_url() {
|
|
local expr="$1"
|
|
python3 - "${GRAFANA_BASE_URL%/}" "$expr" <<'PY'
|
|
import json
|
|
import sys
|
|
import urllib.parse
|
|
|
|
base_url, expr = sys.argv[1:3]
|
|
left = {
|
|
"datasource": "prometheus",
|
|
"queries": [
|
|
{
|
|
"refId": "A",
|
|
"expr": expr,
|
|
"range": True,
|
|
}
|
|
],
|
|
"range": {
|
|
"from": "now-6h",
|
|
"to": "now",
|
|
},
|
|
}
|
|
print(f"{base_url}/explore?left={urllib.parse.quote(json.dumps(left, separators=(',', ':')))}")
|
|
PY
|
|
}
|
|
|
|
section() {
|
|
printf '\n== %s ==\n' "$1"
|
|
}
|
|
|
|
run_optional() {
|
|
local label="$1"
|
|
shift
|
|
|
|
printf '\n-- %s\n' "${label}"
|
|
"$@" 2>&1 || true
|
|
}
|
|
|
|
host_capacity() {
|
|
section "Debian Host"
|
|
run_optional "memory" free -h
|
|
run_optional "disk" df -h / /data /var/lib/docker
|
|
if command -v docker >/dev/null 2>&1; then
|
|
run_optional "docker disk usage" sudo docker system df
|
|
run_optional "largest docker volumes" sudo docker system df -v
|
|
else
|
|
printf 'docker not installed\n'
|
|
fi
|
|
}
|
|
|
|
kubernetes_capacity() {
|
|
section "Kubernetes"
|
|
if ! command -v kubectl >/dev/null 2>&1 || [[ ! -s "${KUBECONFIG}" ]]; then
|
|
printf 'kubectl or kubeconfig unavailable\n'
|
|
return 0
|
|
fi
|
|
|
|
run_optional "nodes" kubectl --kubeconfig "${KUBECONFIG}" get nodes -o wide
|
|
run_optional "node resource summary" kubectl --kubeconfig "${KUBECONFIG}" describe nodes
|
|
run_optional "top nodes" kubectl --kubeconfig "${KUBECONFIG}" top nodes
|
|
run_optional "top pods" kubectl --kubeconfig "${KUBECONFIG}" top pods -A --sort-by=memory
|
|
run_optional "PVCs" kubectl --kubeconfig "${KUBECONFIG}" get pvc -A -o wide
|
|
run_optional "PVs" kubectl --kubeconfig "${KUBECONFIG}" get pv -o wide
|
|
|
|
printf '\n-- pods without resource requests or limits\n'
|
|
kubectl --kubeconfig "${KUBECONFIG}" get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{range .spec.containers[*]}{.name}{":cpuReq="}{.resources.requests.cpu}{",memReq="}{.resources.requests.memory}{",memLimit="}{.resources.limits.memory}{" "}{end}{"\n"}{end}' 2>/dev/null |
|
|
awk '/cpuReq=,|memReq=,|memLimit=,/ { print }' || true
|
|
}
|
|
|
|
pimox_capacity() {
|
|
local pimox_host="${LAB_PIMOX_HOST:-${TF_VAR_pimox_host:-192.168.100.80}}"
|
|
local pimox_user="${LAB_PIMOX_USER:-${TF_VAR_pimox_user:-jv}}"
|
|
local pimox_key="${LAB_PIMOX_SSH_KEY_PATH:-${TF_VAR_pimox_ssh_key_path:-${HOME}/.ssh/id_ed25519}}"
|
|
|
|
section "Pimox"
|
|
ssh -i "${pimox_key}" -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${pimox_user}@${pimox_host}" '
|
|
set +e
|
|
echo "-- host memory"
|
|
free -h
|
|
echo
|
|
echo "-- storage"
|
|
df -h / /data 2>/dev/null || df -h
|
|
echo
|
|
echo "-- pvesm"
|
|
sudo pvesm status 2>/dev/null || true
|
|
echo
|
|
echo "-- VMs"
|
|
sudo qm list 2>/dev/null || true
|
|
echo
|
|
echo "-- running VM configs"
|
|
for vmid in $(sudo qm list 2>/dev/null | awk "NR > 1 {print \$1}"); do
|
|
echo "VM ${vmid}"
|
|
sudo qm config "${vmid}" 2>/dev/null | awk "/^(memory|cores|sockets|scsi|virtio|sata|ide)[0-9]*:|^memory:|^cores:|^sockets:/"
|
|
done
|
|
' || printf 'unable to reach Pimox host %s@%s\n' "${pimox_user}" "${pimox_host}"
|
|
}
|
|
|
|
rpi_capacity() {
|
|
local rpi_host="${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}"
|
|
local rpi_user="${LAB_RPI_USER:-${LAB_RASPBERRY_USER:-jv}}"
|
|
local rpi_key="${LAB_RPI_SSH_KEY_PATH:-${LAB_RASPBERRY_SSH_KEY_PATH:-${HOME}/.ssh/id_ed25519}}"
|
|
|
|
section "RPi4"
|
|
ssh -i "${rpi_key}" -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${rpi_user}@${rpi_host}" '
|
|
set +e
|
|
docker_root="$(sudo docker info --format "{{.DockerRootDir}}" 2>/dev/null || true)"
|
|
echo "-- memory"
|
|
free -h
|
|
echo
|
|
echo "-- disk"
|
|
df -h / /nvme-storage "${docker_root:-/nvme-storage/docker}" 2>/dev/null || df -h
|
|
echo
|
|
echo "-- docker root"
|
|
printf "%s\n" "${docker_root:-unknown}"
|
|
echo
|
|
echo "-- docker disk usage"
|
|
sudo docker system df 2>/dev/null || true
|
|
' || printf 'unable to reach RPi host %s@%s\n' "${rpi_user}" "${rpi_host}"
|
|
}
|
|
|
|
summary() {
|
|
section "Capacity Guidance"
|
|
cat <<'EOF'
|
|
Use this report to decide placement:
|
|
- Prefer Kubernetes app workloads on Pimox workers with SSD-backed storage.
|
|
- Keep Debian control-plane headroom for Docker, Gitea, registry, Ollama, and kubeadm.
|
|
- Treat Orange Pi VM disks as rebuildable capacity, not durable backup storage.
|
|
- Keep RPi4 focused on DNS/Uptime Kuma/light services until NVMe stability is proven.
|
|
- Add capacity only when CPU, memory, and disk all have margin; storage alone is not enough.
|
|
EOF
|
|
}
|
|
|
|
verbose_main() {
|
|
host_capacity
|
|
kubernetes_capacity
|
|
pimox_capacity
|
|
rpi_capacity
|
|
summary
|
|
}
|
|
|
|
compact_host_capacity() {
|
|
local mem_total
|
|
local mem_used
|
|
local mem_pct
|
|
local path
|
|
local line
|
|
local use_pct
|
|
local docker_root
|
|
|
|
if command -v free >/dev/null 2>&1; then
|
|
read -r mem_total mem_used < <(free -m | awk '/^Mem:/ { print $2, $3 }')
|
|
if [[ -n "${mem_total:-}" && "${mem_total}" -gt 0 ]]; then
|
|
mem_pct=$((mem_used * 100 / mem_total))
|
|
if ((mem_pct >= 85)); then
|
|
emit warn "Debian" "Memory" "used=${mem_pct}%" "${mem_used}MiB/${mem_total}MiB" "Move workloads to Pimox workers or stop nonessential containers." "${GRAFANA_CAPACITY_URL}&viewPanel=1" "$(grafana_explore_url '100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))')"
|
|
else
|
|
emit ok "Debian" "Memory" "used=${mem_pct}%" "${mem_used}MiB/${mem_total}MiB"
|
|
fi
|
|
else
|
|
emit skip "Debian" "Memory" "unavailable" "free output could not be parsed"
|
|
fi
|
|
else
|
|
emit skip "Debian" "Memory" "free missing" "" "Install procps."
|
|
fi
|
|
|
|
for path in / /data /var/lib/docker; do
|
|
line="$(df -P "${path}" 2>/dev/null | awk 'NR == 2 { print $5 " " $4 }' || true)"
|
|
if [[ -z "${line}" ]]; then
|
|
emit skip "Debian" "Disk ${path}" "unavailable" "path not present"
|
|
continue
|
|
fi
|
|
use_pct="${line%% *}"
|
|
use_pct="${use_pct%%%}"
|
|
if ((use_pct >= 90)); then
|
|
emit fail "Debian" "Disk ${path}" "used=${use_pct}%" "available blocks: ${line#* }" "Free disk or move data before deploying more services." "${GRAFANA_CAPACITY_URL}&viewPanel=2" "$(grafana_explore_url '100 * (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs",mountpoint=~"/|/data|/var/lib/docker|/nvme-storage"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs",mountpoint=~"/|/data|/var/lib/docker|/nvme-storage"}))')"
|
|
elif ((use_pct >= 80)); then
|
|
emit warn "Debian" "Disk ${path}" "used=${use_pct}%" "available blocks: ${line#* }" "Review Docker volumes, backups, and registry usage." "${GRAFANA_CAPACITY_URL}&viewPanel=2" "$(grafana_explore_url '100 * (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs",mountpoint=~"/|/data|/var/lib/docker|/nvme-storage"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs",mountpoint=~"/|/data|/var/lib/docker|/nvme-storage"}))')"
|
|
else
|
|
emit ok "Debian" "Disk ${path}" "used=${use_pct}%" "available blocks: ${line#* }"
|
|
fi
|
|
done
|
|
|
|
if command -v docker >/dev/null 2>&1; then
|
|
docker_root="$(docker info --format '{{.DockerRootDir}}' 2>/dev/null || true)"
|
|
if [[ -n "${docker_root}" ]]; then
|
|
emit ok "Debian" "Docker root" "${docker_root}" "" "Keep Docker on NVMe-backed storage."
|
|
else
|
|
emit warn "Debian" "Docker root" "unavailable" "docker info failed" "Check docker service."
|
|
fi
|
|
else
|
|
emit skip "Debian" "Docker" "not installed"
|
|
fi
|
|
}
|
|
|
|
compact_kubernetes_capacity() {
|
|
local not_ready
|
|
local problem_pods
|
|
local missing_resources
|
|
local top_nodes
|
|
local node_ready_query
|
|
local problem_pods_query
|
|
local resource_policy_query
|
|
local node_pressure_query
|
|
|
|
node_ready_query="$(grafana_explore_url 'kube_node_status_condition{condition="Ready",status="true"} == 0')"
|
|
problem_pods_query="$(grafana_explore_url 'sum by (namespace, pod, phase) (kube_pod_status_phase{phase!~"Running|Succeeded"} == 1)')"
|
|
resource_policy_query="$(grafana_explore_url 'sum by (namespace, pod) (kube_pod_container_resource_requests{resource=~"cpu|memory"})')"
|
|
node_pressure_query="$(grafana_explore_url '100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))')"
|
|
|
|
if ! command -v kubectl >/dev/null 2>&1 || [[ ! -s "${KUBECONFIG}" ]]; then
|
|
emit warn "Kubernetes" "API" "unavailable" "kubectl or kubeconfig missing" "Start the cluster or set KUBECONFIG."
|
|
return 0
|
|
fi
|
|
if ! kubectl --kubeconfig "${KUBECONFIG}" get --raw=/readyz >/dev/null 2>&1; then
|
|
emit warn "Kubernetes" "API" "not reachable" "readyz failed" "./jeannie start-cluster"
|
|
return 0
|
|
fi
|
|
|
|
not_ready="$(kubectl --kubeconfig "${KUBECONFIG}" get nodes --no-headers 2>/dev/null | awk '$2 !~ /Ready/ { count++ } END { print count + 0 }')"
|
|
if ((not_ready > 0)); then
|
|
emit fail "Kubernetes" "Node readiness" "${not_ready} not ready" "" "./jeannie doctor-cluster" "${GRAFANA_AVAILABILITY_URL}&viewPanel=1" "${node_ready_query}"
|
|
else
|
|
emit ok "Kubernetes" "Node readiness" "all ready"
|
|
fi
|
|
|
|
problem_pods="$(kubectl --kubeconfig "${KUBECONFIG}" get pods -A --no-headers 2>/dev/null | awk '$4 != "Running" && $4 != "Completed" { count++ } END { print count + 0 }')"
|
|
if ((problem_pods > 0)); then
|
|
emit warn "Kubernetes" "Problem pods" "${problem_pods}" "pods not Running/Completed" "kubectl get pods -A -o wide" "${GRAFANA_AVAILABILITY_URL}&viewPanel=2" "${problem_pods_query}"
|
|
else
|
|
emit ok "Kubernetes" "Problem pods" "none"
|
|
fi
|
|
|
|
missing_resources="$(kubectl --kubeconfig "${KUBECONFIG}" get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{range .spec.containers[*]}{.name}{":cpuReq="}{.resources.requests.cpu}{",memReq="}{.resources.requests.memory}{",memLimit="}{.resources.limits.memory}{" "}{end}{"\n"}{end}' 2>/dev/null |
|
|
awk '/cpuReq=,|memReq=,|memLimit=,/ { count++ } END { print count + 0 }')"
|
|
if ((missing_resources > 0)); then
|
|
emit warn "Kubernetes" "Resource policy" "${missing_resources} pods missing requests/limits" "" "./jeannie resource-budget" "${GRAFANA_CAPACITY_URL}&viewPanel=3" "${resource_policy_query}"
|
|
else
|
|
emit ok "Kubernetes" "Resource policy" "requests/limits present"
|
|
fi
|
|
|
|
top_nodes="$(kubectl --kubeconfig "${KUBECONFIG}" top nodes 2>/dev/null | awk 'NR > 1 { print $1 ":cpu=" $3 ",mem=" $5 }' | paste -sd ';' - || true)"
|
|
if [[ -n "${top_nodes}" ]]; then
|
|
emit ok "Kubernetes" "Node pressure" "metrics available" "${top_nodes}"
|
|
else
|
|
emit skip "Kubernetes" "Node pressure" "metrics unavailable" "metrics-server may not be installed or ready" "" "${GRAFANA_CAPACITY_URL}&viewPanel=1" "${node_pressure_query}"
|
|
fi
|
|
}
|
|
|
|
compact_pimox_capacity() {
|
|
local pimox_host="${LAB_PIMOX_HOST:-${TF_VAR_pimox_host:-192.168.100.80}}"
|
|
local pimox_user="${LAB_PIMOX_USER:-${TF_VAR_pimox_user:-jv}}"
|
|
local pimox_key="${LAB_PIMOX_SSH_KEY_PATH:-${TF_VAR_pimox_ssh_key_path:-${HOME}/.ssh/id_ed25519}}"
|
|
local worker_storage="${LAB_PIMOX_WORKER_STORAGE:-${TF_VAR_pimox_worker_storage:-opi5_ssd}}"
|
|
local output
|
|
|
|
output="$(ssh -i "${pimox_key}" -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${pimox_user}@${pimox_host}" "set +e
|
|
storage_line=\"\$(sudo pvesm status 2>/dev/null | awk -v storage='${worker_storage}' '\$1 == storage { print \$7 }')\"
|
|
running_vms=\"\$(sudo qm list 2>/dev/null | awk 'NR > 1 && \$3 == \"running\" { count++ } END { print count + 0 }')\"
|
|
total_vms=\"\$(sudo qm list 2>/dev/null | awk 'NR > 1 { count++ } END { print count + 0 }')\"
|
|
echo \"storage_used_pct=\${storage_line:-unknown}\"
|
|
echo \"running_vms=\${running_vms:-0}\"
|
|
echo \"total_vms=\${total_vms:-0}\"
|
|
" 2>/dev/null || true)"
|
|
|
|
if [[ -z "${output}" ]]; then
|
|
emit warn "Pimox" "Host" "unreachable" "${pimox_user}@${pimox_host}" "./jeannie doctor-cluster"
|
|
return 0
|
|
fi
|
|
|
|
local storage_pct
|
|
local running_vms
|
|
local total_vms
|
|
storage_pct="$(printf '%s\n' "${output}" | awk -F= '/storage_used_pct=/ { print $2 }')"
|
|
running_vms="$(printf '%s\n' "${output}" | awk -F= '/running_vms=/ { print $2 }')"
|
|
total_vms="$(printf '%s\n' "${output}" | awk -F= '/total_vms=/ { print $2 }')"
|
|
|
|
if [[ "${storage_pct}" =~ ^[0-9.]+%?$ ]]; then
|
|
storage_pct="${storage_pct%%%}"
|
|
if ((${storage_pct%.*} >= 85)); then
|
|
emit warn "Pimox" "Worker storage" "used=${storage_pct}%" "${worker_storage}" "Clean old VM disks or add storage."
|
|
else
|
|
emit ok "Pimox" "Worker storage" "used=${storage_pct}%" "${worker_storage}"
|
|
fi
|
|
else
|
|
emit warn "Pimox" "Worker storage" "unknown" "${worker_storage}" "Check pvesm status."
|
|
fi
|
|
emit ok "Pimox" "VMs" "running=${running_vms:-0}/${total_vms:-0}"
|
|
}
|
|
|
|
compact_rpi_capacity() {
|
|
local rpi_host="${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}"
|
|
local rpi_user="${LAB_RPI_USER:-${LAB_RASPBERRY_USER:-jv}}"
|
|
local rpi_key="${LAB_RPI_SSH_KEY_PATH:-${LAB_RASPBERRY_SSH_KEY_PATH:-${HOME}/.ssh/id_ed25519}}"
|
|
local expected_docker_root="${LAB_RPI_DOCKER_ROOT:-${LAB_RPI_DOCKER_NVME_ROOT:-/nvme-storage/docker}}"
|
|
local fallback_docker_root="${LAB_RPI_DOCKER_FALLBACK_ROOT:-/var/lib/docker}"
|
|
local output
|
|
|
|
output="$(ssh -i "${rpi_key}" -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${rpi_user}@${rpi_host}" '
|
|
set +e
|
|
docker_root="$(sudo docker info --format "{{.DockerRootDir}}" 2>/dev/null || true)"
|
|
root_used="$(df -P / 2>/dev/null | awk "NR == 2 { print \$5 }")"
|
|
nvme_used="$(df -P /nvme-storage 2>/dev/null | awk "NR == 2 { print \$5 }")"
|
|
docker_root_used="$(df -P "${docker_root:-/nvme-storage/docker}" 2>/dev/null | awk "NR == 2 { print \$5 }")"
|
|
echo "docker_root=${docker_root:-unknown}"
|
|
echo "root_used=${root_used:-unknown}"
|
|
echo "nvme_used=${nvme_used:-unknown}"
|
|
echo "docker_root_used=${docker_root_used:-unknown}"
|
|
' 2>/dev/null || true)"
|
|
|
|
if [[ -z "${output}" ]]; then
|
|
emit warn "RPi4" "Host" "unreachable" "${rpi_user}@${rpi_host}" "./jeannie doctor-rpi"
|
|
return 0
|
|
fi
|
|
|
|
local docker_root
|
|
local root_used
|
|
local nvme_used
|
|
local docker_root_used
|
|
docker_root="$(printf '%s\n' "${output}" | awk -F= '/docker_root=/ { print $2 }')"
|
|
root_used="$(printf '%s\n' "${output}" | awk -F= '/root_used=/ { print $2 }')"
|
|
nvme_used="$(printf '%s\n' "${output}" | awk -F= '/nvme_used=/ { print $2 }')"
|
|
docker_root_used="$(printf '%s\n' "${output}" | awk -F= '/docker_root_used=/ { print $2 }')"
|
|
if [[ "${docker_root}" == "${expected_docker_root}" ]]; then
|
|
emit ok "RPi4" "Docker root" "${docker_root}" "used=${docker_root_used:-unknown}"
|
|
elif [[ "${docker_root}" == "${fallback_docker_root}" ]]; then
|
|
emit warn "RPi4" "Docker root" "${docker_root}" "expected ${expected_docker_root}" "./jeannie doctor-rpi"
|
|
else
|
|
emit warn "RPi4" "Docker root" "${docker_root:-unknown}" "expected ${expected_docker_root}" "./jeannie doctor-rpi"
|
|
fi
|
|
emit ok "RPi4" "Root disk" "${root_used:-unknown}"
|
|
if [[ "${nvme_used:-unknown}" = "unknown" ]]; then
|
|
emit warn "RPi4" "NVMe mount" "unknown" "df /nvme-storage failed" "./jeannie doctor-rpi"
|
|
else
|
|
emit ok "RPi4" "NVMe mount" "${nvme_used}"
|
|
fi
|
|
}
|
|
|
|
compact_guidance() {
|
|
emit ok "Guidance" "Placement" "Prefer app workloads on Pimox workers" "Keep Debian headroom for Gitea, registry, Docker, kubeadm, and Ollama."
|
|
emit ok "Guidance" "Storage" "Treat Pimox VM disks as rebuildable" "Durable data still needs explicit backup/restore paths."
|
|
}
|
|
|
|
compact_main() {
|
|
{
|
|
compact_host_capacity
|
|
compact_kubernetes_capacity
|
|
compact_pimox_capacity
|
|
compact_rpi_capacity
|
|
compact_guidance
|
|
} | "${REPO_ROOT}/scripts/report-render" --title "Homelab Capacity" "${DETAIL_ARGS[@]}"
|
|
}
|
|
|
|
if [[ "${VERBOSE}" == "true" ]]; then
|
|
verbose_main
|
|
else
|
|
compact_main
|
|
fi
|