#!/usr/bin/env bash # ============================================================================= # test-shell-safety.sh — jaga "jebakan set -e + grep" di skrip deploy # ----------------------------------------------------------------------------- # JEBAKAN: pipeline yang memuat `grep` mengembalikan 1 saat TIDAK ada baris cocok. # Di skrip ber-`set -euo pipefail`, hasil pipeline itu MEMATIKAN skrip DIAM-DIAM. # # Regresi nyata: e099b95 membuat `external_services()` di install.sh berakhir # ... | grep -v '^$' | sort -u | paste ... # sehingga `./install.sh` (single-host tanpa service eksternal) keluar exit 1 # TEPAT setelah banner. Perbaikan: `awk 'NF'` (buang baris kosong, selalu exit 0). # # YANG DIPERIKSA (hanya skrip ber-`set -e`): # 1. `| grep -v` / `| grep -c` — mode FILTER data yang lazim exit 1 saat kosong, # dan jarang dipakai sebagai uji boolean. Lain halnya `grep -q`/`-E` yang # memang sering dipakai di `if`, sehingga TIDAK ditandai. # 2. Idiom `grep -v '^$'` — pemicu langsung regresi di atas. # # Baik (1) maupun (2) DILEWATI bila barisnya jelas kondisional — `if/elif/while/ # until/!` atau memuat `&&`/`||` — karena status exit-nya memang dikonsumsi. # # Jalankan: ./scripts/test-shell-safety.sh # ============================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" GRN=$'\e[32m'; RED=$'\e[31m'; BLD=$'\e[1m'; NC=$'\e[0m' PASS_N=0; FAIL_N=0 ok() { printf " ${GRN}✅${NC} %s\n" "$1"; PASS_N=$((PASS_N+1)); } bad() { printf " ${RED}❌${NC} %s\n" "$1"; FAIL_N=$((FAIL_N+1)); } # Baris ini "aman" dari set -e karena status exit-nya dikonsumsi (kondisi operator)? is_conditional() { local s="$1" case "${s}" in if\ *|elif\ *|while\ *|until\ *|'!'*) return 0 ;; esac [[ "${s}" == *"&&"* || "${s}" == *"||"* || "${s}" == *"|| true"* ]] && return 0 return 1 } echo -e "${BLD}Uji keamanan shell (jebakan set -e + grep)${NC}" echo checked=0 while IFS= read -r f; do # Hanya skrip ber-`set -e` (opsi memuat huruf 'e'); `set -uo pipefail` tanpa -e # tidak berisiko karena tak ada yang mematikan skrip. grep -qE '^[[:space:]]*set -[a-zA-Z]*e' "$f" 2>/dev/null || continue checked=$((checked+1)) rel="${f#"${DEPLOY_DIR}/"}" file_bad=0 # (1) pipe ke grep -v / grep -c while IFS=: read -r ln text; do [[ -z "${ln}" ]] && continue stripped="$(printf '%s' "${text}" | sed 's/^[[:space:]]*//')" is_conditional "${stripped}" && continue bad "${rel}:${ln} — pipe ke 'grep -v/-c' (exit 1 saat kosong → set -e mematikan)" printf ' %s\n' "${stripped}" file_bad=1 done < <(grep -nE '\|[[:space:]]*grep[[:space:]]+-[a-zA-Z]*[vc]' "$f" 2>/dev/null || true) # (2) idiom terlarang grep -v '^$' (baris komentar & kondisional dikecualikan) while IFS=: read -r ln text; do [[ -z "${ln}" ]] && continue stripped="$(printf '%s' "${text}" | sed 's/^[[:space:]]*//')" [[ "${stripped}" == '#'* ]] && continue is_conditional "${stripped}" && continue bad "${rel}:${ln} — idiom terlarang grep -v '^\$' (pakai awk 'NF')" file_bad=1 done < <(grep -nF "grep -v '^$'" "$f" 2>/dev/null || true) [[ ${file_bad} -eq 0 ]] && ok "${rel}: bersih" done < <(find "${DEPLOY_DIR}" -name '*.sh' -not -path '*/generated/*' | sort) echo echo " skrip ber-set -e diperiksa: ${checked}" if [[ ${FAIL_N} -eq 0 ]]; then echo -e " ${GRN}${BLD}SEMUA LULUS${NC} (${PASS_N} cek)" exit 0 fi echo -e " ${RED}${BLD}GAGAL${NC}: ${FAIL_N} masalah · ${PASS_N} lulus" exit 1