diff --git a/bash/README.md b/bash/README.md index 5ba9e09..e211ce9 100644 --- a/bash/README.md +++ b/bash/README.md @@ -11,12 +11,12 @@ same behavior — no PowerShell required. ## Requirements -- bash 4+, `jq`, `curl`, `zip`, OpenSSH (`ssh`/`scp`) +- bash 3.2+ (the stock macOS bash works), `jq`, `curl`, `zip`, OpenSSH (`ssh`/`scp`) - `lsof` (for `zkill`/`zrestart`), `rsync` (for `zsync ` mirror mode) - Docker + docker compose on the remote host for the deploy scripts Debian/Ubuntu: `sudo apt install jq curl zip lsof rsync` -macOS: `brew install jq` (the rest ships with the OS) +macOS: `brew install jq` (bash, curl, zip, ssh, lsof and rsync already ship with macOS) ## Install diff --git a/bash/zbackup b/bash/zbackup index 68b3d2c..021cd52 100644 --- a/bash/zbackup +++ b/bash/zbackup @@ -32,7 +32,7 @@ done BACKUP_ROOT="$(z_path "$(zq '.paths.backupsLocal')")" include_scripts=0 if [ "${#projects[@]}" -eq 0 ]; then - mapfile -t projects < <(zproj_keys) + projects=(); while IFS= read -r _zl || [ -n "$_zl" ]; do projects+=("$_zl"); done < <(zproj_keys) include_scripts=1 else filtered=() @@ -92,7 +92,7 @@ backup_project() { # warn " [2/3] Archiving source..." local excl=() - mapfile -t excl < <(z_archive_excludes "$key" 1) + excl=(); while IFS= read -r _zl || [ -n "$_zl" ]; do excl+=("$_zl"); done < <(z_archive_excludes "$key" 1) if ! z_archive "$root" "$zip_path" 0 "$extra" "${excl[@]}"; then rm -rf "$dump_dir"; return 1 fi diff --git a/bash/zbackup_ec2 b/bash/zbackup_ec2 index ddd54af..be3df82 100644 --- a/bash/zbackup_ec2 +++ b/bash/zbackup_ec2 @@ -22,7 +22,8 @@ z_require ssh scp projects=("$@") if [ "${#projects[@]}" -eq 0 ]; then - mapfile -t projects < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.remote.path != null) | .key' "$ZCONFIG") + projects=(); while IFS= read -r _zl || [ -n "$_zl" ]; do projects+=("$_zl"); done \ + < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.remote.path != null) | .key' "$ZCONFIG") fi EC2_BACKUP_ROOT="$(z_path "$(zq '.paths.backupsEc2')")" diff --git a/bash/zdeploy b/bash/zdeploy index 1c68cdd..7bd0c33 100644 --- a/bash/zdeploy +++ b/bash/zdeploy @@ -46,8 +46,10 @@ mkdir -p "$TEMP_ROOT" # 'all' -> every project, edge kinds first (the proxy must route before apps ship). if printf '%s\n' "${projects[@]}" | grep -qx 'all'; then - mapfile -t edge_keys < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.kind == "edge") | .key' "$ZCONFIG") - mapfile -t rest_keys < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.kind != "edge") | .key' "$ZCONFIG") + edge_keys=(); while IFS= read -r _zl || [ -n "$_zl" ]; do edge_keys+=("$_zl"); done \ + < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.kind == "edge") | .key' "$ZCONFIG") + rest_keys=(); while IFS= read -r _zl || [ -n "$_zl" ]; do rest_keys+=("$_zl"); done \ + < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.kind != "edge") | .key' "$ZCONFIG") projects=("${edge_keys[@]+"${edge_keys[@]}"}" "${rest_keys[@]+"${rest_keys[@]}"}") info "Deploying all projects: $(printf '%s ' "${projects[@]}")" fi @@ -203,7 +205,7 @@ deploy_python() { # printf '\n'; info "--- [1] Zipping $label ---" find "$root" -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null - mapfile -t excl < <(z_archive_excludes "$key" 0) + excl=(); while IFS= read -r _zl || [ -n "$_zl" ]; do excl+=("$_zl"); done < <(z_archive_excludes "$key" 0) z_archive "$root" "$zip_local" 0 "-" "${excl[@]}" || return 1 preflight_cleanup "$REMOTE_HOME/$zip_name" || { rm -f "$zip_local"; return 1; } @@ -289,7 +291,7 @@ deploy_vite() { # pre_bn="$(jq -r '.buildNumber // empty' "$root/build-version.json")" [ -n "$pre_pv" ] && dim " Pre-zip build label: v${pre_pv}.${pre_bn} (server-side build will bump +1)" fi - mapfile -t excl < <(z_archive_excludes "$key" 0) + excl=(); while IFS= read -r _zl || [ -n "$_zl" ]; do excl+=("$_zl"); done < <(z_archive_excludes "$key" 0) z_archive "$root" "$zip_local" 0 "-" "${excl[@]}" || return 1 preflight_cleanup "$REMOTE_HOME/$zip_name" || { rm -f "$zip_local"; return 1; } @@ -347,7 +349,7 @@ deploy_next() { # fi printf '\n'; info "--- [1] Zipping project files ---" - mapfile -t excl < <(z_archive_excludes "$key" 0) + excl=(); while IFS= read -r _zl || [ -n "$_zl" ]; do excl+=("$_zl"); done < <(z_archive_excludes "$key" 0) z_archive "$root" "$zip_local" 0 "-" "${excl[@]}" || return 1 preflight_cleanup "$REMOTE_HOME/$zip_name" || { rm -f "$zip_local"; return 1; } diff --git a/bash/zec2 b/bash/zec2 index 37513ec..a991bee 100644 --- a/bash/zec2 +++ b/bash/zec2 @@ -28,7 +28,7 @@ done HOST="${host_override:-$(zec2_ip)}" if [ "${#projects[@]}" -eq 0 ]; then - mapfile -t projects < <(zproj_keys_with_domain) + projects=(); while IFS= read -r _zl || [ -n "$_zl" ]; do projects+=("$_zl"); done < <(zproj_keys_with_domain) if [ "${#projects[@]}" -eq 0 ]; then warn "No projects with a 'domain' configured in zconfig.json." exit 1 diff --git a/bash/zec2online b/bash/zec2online index e4ed6d1..e85c246 100644 --- a/bash/zec2online +++ b/bash/zec2online @@ -32,7 +32,7 @@ HOST="${host_override:-$(zec2_ip)}" EDGE_KEY="$(zedge_key)" if [ "${#projects[@]}" -eq 0 ]; then - mapfile -t projects < <(zproj_keys_with_domain) + projects=(); while IFS= read -r _zl || [ -n "$_zl" ]; do projects+=("$_zl"); done < <(zproj_keys_with_domain) if [ "${#projects[@]}" -eq 0 ]; then warn "No projects with a 'domain' configured in zconfig.json." exit 1 diff --git a/bash/zhelpers.sh b/bash/zhelpers.sh index 59eb611..81d2ae6 100644 --- a/bash/zhelpers.sh +++ b/bash/zhelpers.sh @@ -311,8 +311,16 @@ z_track_stop() { Z_TRACK_FILE="" } -# Portable thousands separators (avoids locale-dependent printf %'d). -_z_commafy() { printf '%s' "$1" | sed -E ':a;s/([0-9])([0-9]{3})($|[^0-9])/\1,\2\3/;ta'; } +# Portable thousands separators for a non-negative integer string (avoids both +# locale-dependent printf %'d and the GNU-only `sed :a;...;ta` idiom, which +# errors on BSD/macOS sed). +_z_commafy() { + awk -v n="$1" 'BEGIN{ + s = n ""; out = "" + while (length(s) > 3) { out = "," substr(s, length(s) - 2) out; s = substr(s, 1, length(s) - 3) } + printf "%s%s", s, out + }' +} # Append a run to the ztokens JSONL store. Data dir precedence: $ZTOKENS_DATA, # then config "ztokens.dataDir", then the sibling ../../ztokens/data if present diff --git a/bash/zkill b/bash/zkill index 1a29ad2..eed91fd 100644 --- a/bash/zkill +++ b/bash/zkill @@ -31,6 +31,10 @@ if [ "${#projects[@]}" -eq 0 ]; then exit 1 fi +# --kill-all (kill stray project processes beyond the dev-port listener) isn't +# ported yet; announce it rather than silently accepting the flag and doing nothing. +[ "$kill_all" -eq 1 ] && warn "Note: --kill-all isn't implemented in the bash port yet - killing dev-port listeners only." + for key in "${projects[@]}"; do zproj_require "$key" label="$(zproj "$key" .label)"; [ -n "$label" ] || label="$key" diff --git a/bash/zrepair b/bash/zrepair index 58a0181..ab2bf4e 100644 --- a/bash/zrepair +++ b/bash/zrepair @@ -60,7 +60,7 @@ for key in "$@"; do "cd $compose_dir || exit 1; if ! sudo docker compose ps | grep -q Up; then echo ' [Action] Stack is down. Starting...'; sudo docker compose up -d; else echo ' [OK] Stack is active and UP.'; fi" || exit 1 if [ -n "$domain" ]; then - warn " [3/3] Smoke test for https://$domain..." + warn " [3/3] Smoke test for $domain (via http://$HOST)..." code="$(http_code "http://$HOST/" "$domain")" if [ "$code" = "000" ]; then err " FAIL - smoke test failed: no HTTP response"